大体思路
“当一个线程突然终止,java虚拟机通过getUncaughtExceptionHandler可以得到这个线程的UncaughtExceptionHandler,并进入这个handler的uncaughtException方法。”所以我们
定义一个类实现当前线程的UncaughtExceptionHandler,并修改uncaughtException方法实现自己需要的功能。
定义一个CrashManager类实现Thread.UncaughtExceptionHandler,需要override uncaughtException:
public class CrashManager implements Thread.UncaughtExceptionHandler {
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
CMLog.i_ui(CLASS_TAG, "uncaughtException called...");
}
}
需要做这几步:
- 获取线程默认handler;
- 设置当前类为当前线程的异常处理handler;
- 修改uncaughtException,实现自己的异常处理,然后调用系统的默认处理方式。
获取系统异常处理handler,只需要下面一行代码:
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
设置当前实现类为线程handler:
Thread.setDefaultUncaughtExceptionHandler(this);
修改uncaughtException中:
@Override
public void uncaughtException(Thread thread, Throwable ex) {
CMLog.i_ui(CLASS_TAG, "uncaughtException called...");
myHandleException();//添加自己的方法
mDefaultHandler.uncaughtException(thread, ex);//之后继续调用系统的异常处理方法
}
具体实现
实现当前线程的异常处理:
public class CrashManager implements Thread.UncaughtExceptionHandler {
public final String CLASS_TAG = getClass().getSimpleName();
// 程序的Context对象
private Application mContext;
// 系统默认的UncaughtException处理类
private Thread.UncaughtExceptionHandler mDefaultHandler;
private CrashManager(Context context) {
mContext = (Application) context.getApplicationContext();
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
public static void initManager(Context context){
new CrashManager(context.getApplicationContext());
}
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
CMLog.i_ui(CLASS_TAG, "uncaughtException called...");
if (!handleException(ex) && mDefaultHandler != null){
mDefaultHandler.uncaughtException(thread, ex);
}else {
}
}
/**
* 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
*
* @param ex
* @return true:如果处理了该异常信息;否则返回 false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
// 使用 Toast 来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出。", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
CMLog.i_ui(CLASS_TAG, "save crash log...");
String fileName = Utils.setLogFileName(mContext);//设置日志文件名
String logStr = Utils.collectLogCatToString();//搜集logcat,保存为Striing
int ret;
ret = Utils.writeStringToAppStorage(mContext, logStr, fileName);//把String存入文件
CMLog.i_ui(CLASS_TAG, "saveLogOnDevice ret:" + ret);
return false;
}
}
定义一个保存日志的开关在build.gradle中:
android {
defaultConfig {
buildConfigField "boolean", "HANDLE_UNCAUGHT_EXCEPTION", "true"
}
}
在Application类中初始化:
@Override
public void onCreate() {
//初始化 错误日志系统
if (BuildConfig.HANDLE_UNCAUGHT_EXCEPTION){
CrashManager.initManager(getApplicationContext());
}
把工具类中三个方法代码也贴上来:
/**
* 设置文件名,返回imei_crashLog_时间.log
*/
public static String setLogFileName(Context ctx){
CMLog.i_ui(CLASS_TAG, "setLogFileName called...");
String fileName = "";
String imei = "";
String time = "";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
time = sdf.format(new Date());
try {
imei = getImei(ctx);
} catch (GetImeiException e) {
e.printStackTrace();
}
fileName = imei +"_" + "crashLog_"+ time + ".log";
return fileName;
}
public static String getImei(Context context) throws GetImeiException {
if (context == null) {
return null;
}
String imei;
TelephonyManager mTelephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
imei = mTelephonyMgr.getDeviceId();
CMLog.i_ui(CLASS_TAG, "imei:" + imei);
return imei;
}
/**
* 收集logcat
* @return
*/
public static String collectLogCatToString() {
ArrayList commandLine = new ArrayList();
String[] defaultValues = new String[]{"logcat", "-d", "threadtime"};
ArrayList logcatArgumentsList = new ArrayList(Arrays.asList(defaultValues));
commandLine.addAll(logcatArgumentsList);
CMLog.i_ui(CLASS_TAG, "commandLine: " + commandLine);
String logcatBuf = "";
try {
final java.lang.Process e = Runtime.getRuntime().exec((String[])commandLine.toArray(new String[commandLine.size()]));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(e.getInputStream()), 8192);
while(true) {
String line;
line = bufferedReader.readLine();
logcatBuf = logcatBuf + line + "\n";
if(line == null) {
return logcatBuf;
}
}
} catch (IOException var11) {
return null;
}
}
/**
* 保存文件到app文件夹
*/
public static int writeStringToAppStorage(Context context, String message, String fileName) {
CMLog.i_ui(CLASS_TAG,"writeStringToInnerSDCardFile called...");
try {
//以Mate9为例,得到Android/data/package/files
File file = new File(context.getExternalFilesDir(null),
fileName);
FileOutputStream fos = new FileOutputStream(file);
fos.write(message.getBytes());
fos.close();
CMLog.i_ui(CLASS_TAG,"save logcat success");
CMLog.i_ui(CLASS_TAG, "logcat is:" + file.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
return CREAT_LOGFILE_SUCC;
}
获取imei需要申明权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
由于日志路径是在应用本身包名下,不需要读写权限。
最初以为需要重新开一个进程来保存崩溃日志,后来发现没必要,exception和crash是两回事,当前进程进入uncaughtException,系统默认处理方式就是终止进程,在这之前进程是一直存在的。