注意事项:
- 建好的文件有时候看不到,需要重启手机或者是调用 MediaScannerConnection.scanFile 方法。scanFile 方法在测试三星机型上有时候没有找到文件夹,需要重新启动手机,当其有文件夹就可以找到该文件了。
- 输出日期时间 需要HH,如果为 hh,则下午时间和早上时间区分不开
- 通过下面方法将异常捕获在 public void uncaughtException(Thread thread, Throwable ex) 中处理
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this); - 考虑文件拼接,printwrite 不直接输入文件内容,其会覆盖之前内容,输出编码格式为utf-8,需要OutputStreamWriter 转码。
- addUserInfo 可以自己定义需要传递的用户信息。
- 服务器上传 可以考虑与后台配合完成
public class CrashHandler implements Thread.UncaughtExceptionHandler {
/** 文件包名称*/
private static final String TAG = "TxxCrash";
/** 文件包名称*/
private static final String SP_NAME = "TxxCrash";
/** 存储位置文件夹*/
private String logdir;
/** 文件名称*/
protected static final String FILE_NAME = "txx_crash_log";
/** 文件后缀*/
private static final String FORMAT = ".txt";
/** 系统默认的UncaughtException处理类*/
private Thread.UncaughtExceptionHandler mDefaultHandler;
/** 程序的Context对象*/
private Context mContext;
/** 用来存储设备信息和异常信息*/
private Map<String, String> infos = new HashMap<String, String>();
/** 根据时间,定期上传日志*/
public final static int TIME = 1;
/** 根据日志大小,上传日志*/
public final static int MEMORY = 2;
/** 根据,时间,日志大小,上传日志*/
public final static int TIME_AND_MEMORY = 3;
//type
private int TYPE = 0;
//first init
private boolean isFirstInit;
//first init string
private final static String IS_FIRST_INIT = "is_first_init";
//first init time
private final static String FIRST_INIT_TIME = "first_init_time";
//时间差
private int TIME_SIZE;
//设置的时间间隔
private int DAYS = 7;
/**
* 获取CrashHandler实例 ,单例模式
*/
private CrashHandler() {
}
public static CrashHandler getInstance() {
return Bulid.single;
}
private static class Bulid {
private static final CrashHandler single = new CrashHandler ();
}
/**
* 初始化
*
* @param context
* @param type 上传模式
*/
public void init(Context context, int type) {
mContext = context.getApplicationContext ();
//获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
//设置文件保存路径
logdir = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + TAG;
//设置样式
this.TYPE = type;
//时间存储
saveOrGetTimeType();
}
/**
* 初始化
*
* @param context 默认按照日志大小,控制日志上传
*/
public void init(Context context) {
this.init (context,3);
}
/**
* 获取保存时间信息
*/
private void saveOrGetTimeType() {
SharedPreferences crash = mContext.getSharedPreferences(SP_NAME,MODE_PRIVATE );
isFirstInit = crash.getBoolean(IS_FIRST_INIT, true);
if (isFirstInit) {
SharedPreferences.Editor edit = crash.edit();
edit.putBoolean(IS_FIRST_INIT, false);
edit.putLong(FIRST_INIT_TIME, getTimeToLong());
edit.commit();
} else {
long firstTime = crash.getLong(FIRST_INIT_TIME, 0);
long currentTime = getTimeToLong();
TIME_SIZE = (int) ((currentTime - firstTime) / 86400);
}
}
/**
* 获取时间
*/
private long getTimeToLong() {
try {
long time = Calendar.getInstance().getTimeInMillis();
return time;
} catch (Exception e) {
return 0;
}
}
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
/** 已经保存数据,不做退出处理*/
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// Log.i(TAG, "uncaughtException: " + e.toString());
// }
// //退出程序
// android.os.Process.killProcess(android.os.Process.myPid());
// System.exit(1);
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException(Throwable ex) {
Log.e (TAG,"-----------------------------------app 出错了 start----------------------------------------");
//如果没有SD卡,直接返回
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Log.e (TAG,"没有外置存储挂载");
return false;
}
if (ex == null) {
Log.e (TAG,"ex + null");
return false;
}else {
Log.e (TAG,"ex "+ex.toString ());
}
//收集设备参数信息
collectDeviceInfo(mContext);
//保存日志文件
saveCrashInfoToFile(ex);
Log.e (TAG,"-----------------------------------app 出错了 end----------------------------------------");
return true;
}
/**
* 并不应该每次崩溃都进行日志上传
*
* @param file
*/
private void svaeCrashInfoToServer(File file,StringBuffer stringBuilder) {
Log.e (TAG,"日志已经很大了,应该上传服务器");
//上传服务器后 清空数据
WriteContentTypt(file,stringBuilder.toString (),true);
// new Thread() {
// @Override
// public void run() {
// Looper.prepare();
// Toast.makeText(mContext, "日志已经很大了,应该上传服务器", Toast.LENGTH_LONG).show();
// Looper.loop();
// }
// }.start();
}
/**
* 收集设备参数信息
*
* @param ctx
*/
public void collectDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionName = pi.versionName == null ? "null" : pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("App Version", versionName);
infos.put("App versionCode", versionCode);
}
//android版本号
infos.put("OS Version: ",Build.VERSION.RELEASE+"___"+Build.VERSION.SDK_INT);
//手机制造商
infos.put(" 产品品牌--Vendor:: ",Build.MANUFACTURER+"_____"+Build.BRAND);
//手机型号
infos.put(" Model:: ",Build.MODEL);
infos.put(" 设备序列号: ",Build.SERIAL);
//cpu架构
infos.put(" CPU ABI: ",Build.CPU_ABI);
infos.put("产品型号 : ",Build.PRODUCT);
} catch (NameNotFoundException e) {
Log.i(TAG, "collectDeviceInfo error NameNotFoundException: " + e.toString());
}
Log.e (TAG,"设备信息 :"+infos.toString ());
}
/**
* 保存错误信息到文件中,应该清楚过长时间的错误信息
*
* @param ex
* @return 返回文件名称, 便于将文件传送到服务器
*/
private String saveCrashInfoToFile(Throwable ex) {
// 先获取文件
File file = getFilePath(logdir, FILE_NAME + FORMAT);
if(file==null){
Log.e (TAG,"File 文件 创建失败");
return "";
}
final StringBuffer sb = new StringBuffer();
Date date = Calendar.getInstance().getTime();
//获取24 小时制,如果写为hh 获取的下午3点 也是 3点
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String times = sdf.format(date);
sb.append("---------------------txx start--------------------------"+"\n");
//可以追加用户信息
sb.append(addUserInfo());
sb.append("crash at time: " + times+"\n");
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}
try {
Log.e("错误日志文件路径",""+file.getAbsolutePath());
// Writer writer = new BufferedWriter (new FileWriter (file));
Writer writer = new StringWriter ();
PrintWriter printWriter = new PrintWriter(writer,true);
// printWriter.println (sb.toString ());
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
/** 原因不存在或者未知*/
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.println ("--------------------end---------------------------");
String result = writer.toString();
sb.append(result);
printWriter.close();
switch (TYPE) {
case 1:
//写入本地,清空文本
WriteContentTypt(file,sb.toString (),true);
case 2:
if (checkIsTimeToPush()) {
//保存日志到服务器
svaeCrashInfoToServer(file,sb);
}
case 3:
//检查日志是否过大
if (checkFileIsToBig(file)) {
//写入本地,清空文本
// WriteContentTypt(file,sb.toString (),false);
//保存日志到服务器
svaeCrashInfoToServer(file,sb);
} else {
//写入本地,追加文本
WriteContentTypt(file,sb.toString (),false);
}
break;
default:
break;
}
}catch (Exception e){
Log.e (TAG,e.getMessage ().toString ());
}finally {
//放开以上注释,请删除此行
MediaScannerConnection.scanFile(mContext.getApplicationContext (), new String[]{ logdir }, null, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(final String path, final Uri uri) {
Log.i(TAG, "MediaScannerConnection----- onScanCompleted ");
}
});
}
return null;
}
/**
* 按照时间周期上传日志
*/
private boolean checkIsTimeToPush() {
if (TIME_SIZE >= DAYS) {
return true;
} else {
return false;
}
}
private String addUserInfo() {
return "UserInfo: 无用户信息"+"\n";
}
private boolean checkFileIsToBig(File file) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
int fileSize = fis.available();
if (fileSize > 1024 * 1024 * 5) {
return true;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 创建文件
*
* @param filePath 文件路径
* @param fileName 文件名称
* @return
*/
public File getFilePath(String filePath,
String fileName) {
File file = new File(filePath, fileName);
if (file.exists()) {
return file;
} else {
file = null;
makeRootDirectory(filePath);//创建文件夹
try {
file = new File(filePath + File.separator + fileName);
} catch (Exception e) {
e.printStackTrace();
Log.e (TAG,"getFilePathy Exception :"+e.toString ());
}
return file;
}
}
/**
* 创建根目录
*
* @param filePath
*/
public void makeRootDirectory(String filePath) {
File file = null;
try {
file = new File(filePath);
if (!file.exists()) {
file.mkdir();
}
} catch (Exception e) {
Log.e (TAG,"makeRootDirectory Exception :"+e.toString ());
}
}
/**
* 写入本地文件
*
* @param file 文件
* @param sb 内容
* @param isClean 是否清空,true:清空,false:保留
*/
public void WriteContentTypt(File file, String sb, boolean isClean) {
try {
// FileOutputStream fos = new FileOutputStream(file, !isClean);
// fos.flush();
// for (char c : sb.toString().toCharArray()) {
// fos.write(c);
// }
// fos.flush ();
// fos.close();
OutputStreamWriter oStreamWriter = new OutputStreamWriter(new FileOutputStream(file,!isClean), "utf-8");
oStreamWriter.append(sb);
oStreamWriter.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i(TAG, "WriteContentTypt FileNotFoundException" );
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "WriteContentTypt IOException" );
}
Log.i(TAG, "WriteContentTypt: " + sb.toString());
}
private void zip(String src, String dest) throws IOException { //压缩文件夹,为上传做准备。节省流量。
ZipOutputStream out = null;
File outFile = new File(dest);
File fileOrDirectory = new File(src);
out = new ZipOutputStream(new FileOutputStream(outFile));
if (fileOrDirectory.isFile()) {
zipFileOrDirectory(out, fileOrDirectory, "");
}else {
File[] entries = fileOrDirectory.listFiles();
for (int i = 0; i < entries.length; i++) {
zipFileOrDirectory(out, entries[i], "");
}
}
if(null != out){
out.close();
}
}
private void zipFileOrDirectory(ZipOutputStream out, File fileOrDirectory, String curPath) throws IOException {
FileInputStream in = null;
if (!fileOrDirectory.isDirectory()){
byte[] buffer = new byte[4096];
int bytes_read;
in = new FileInputStream(fileOrDirectory);
ZipEntry entry = new ZipEntry (curPath + fileOrDirectory.getName());
out.putNextEntry(entry);
while ((bytes_read = in.read(buffer)) != -1) {
out.write(buffer, 0, bytes_read);
}
out.closeEntry();
}else{
File[] entries = fileOrDirectory.listFiles();
for (int i = 0; i < entries.length; i++) {
zipFileOrDirectory(out, entries[i], curPath + fileOrDirectory.getName() + "/");
}
}
if (null != in){
in.close();
}
}
public void unZip(File srcFile,File desFile) throws IOException {
GZIPInputStream zis= null;
FileOutputStream fos = null;
try {
//创建压缩输入流,传入源文件
zis = new GZIPInputStream(new FileInputStream(srcFile));
//创建文件输出流,传入目标文件
fos = new FileOutputStream(desFile);
byte[] buffer= new byte[1024];
int len= -1;
//利用IO流写入写出的形式将压缩源文件解压到目标文件中
while ((len= (zis.read(buffer)))!= -1) {
fos.write(buffer,0, len);
}
}finally{
close(zis);
close(fos);
}
}
public void close(Closeable... closeIO) {
for(Closeable clo:closeIO) {
if(clo!=null) {
try {
clo.close();
} catch (IOException e) {
e.printStackTrace ();
Log.e (TAG,"关闭发生异常"+e);
}
}
}
}
}
网友评论