Android捕獲異常監(jiān)控應(yīng)用運行異常的可以使用企鵝家的Bugly等第三方SDK,但是當(dāng)應(yīng)用運行于內(nèi)網(wǎng)或無法連接外網(wǎng)時,bugly等第三方就無用武之地了。這個時候如果本地實現(xiàn)Thread.UncaughtExceptionHandler
接口就可以輕松捕獲異常信息,下面來看一下Android官方介紹:
Interface for handlers invoked when aThreadabruptly terminates due to an uncaught exception.When a thread is about to terminate due to an uncaught exception the Java Virtual Machine will query the thread for its UncaughtExceptionHandlerusinggetUncaughtExceptionHandler() and will invoke the handler'suncaughtExceptionmethod, passing the thread and the exception as arguments. If a thread has not had itsUncaughtExceptionHandlerexplicitly set, then itsThreadGroupobject acts as itsUncaughtExceptionHandler. If theThreadGroupobject has no special requirements for dealing with the exception, it can forward the invocation to thedefault uncaught exception handler.
當(dāng)一個線程由于未捕獲的異常而突然終止時調(diào)用的處理程序的接口。
當(dāng)一個線程終止由于未捕獲的異常Java虛擬機將為其UncaughtExceptionHandler查詢線程使用getUncaughtExceptionHandler(),并將調(diào)用處理程序的uncaughtException方法作為參數(shù)傳遞和異常的線程。如果一個線程沒有UncaughtExceptionHandler顯式地設(shè)置,那么它的ThreadGroup對象作為其UncaughtExceptionHandler。如果ThreadGroup對象沒有處理異常的特殊要求,它可以將調(diào)用轉(zhuǎn)發(fā)給默認(rèn)的未捕獲異常處理程序。
話不多說,下面貼出代碼:
public class CrashCat implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
StackTraceElement[] stackTraceElements = e.getStackTrace();
StringBuffer sb = new StringBuffer(e.toString()+"\n");
for (int i=0,size = stackTraceElements.length;i<size;i++){
sb.append(stackTraceElements[i].toString()+"\n");
}
Log.e("error",sb.toString());
handlerException(sb.toString());
}
}
實現(xiàn)Thread.UncaughtExceptionHandler
需要實現(xiàn)uncaughtException方法,方法中e.getStackTrace()
返回的是堆棧跟蹤信息,即造成應(yīng)用Crash的方法調(diào)用關(guān)系,接下來使用StringBuffer
拼接詳細(xì)信息。
private void handlerException(String exception) {
if (exception !=null){
try{
writeLog(DEVICE_INFO+exception.toString());
}finally {
try{
mContext.startActivity(intent);
Process.killProcess(Process.myPid());
System.exit(1);
}catch (Exception e){
Log.e("App can not restart",e.toString());
}
}
}
}
handlerException
方法進行日志的輸出以及應(yīng)用重啟。整個CrashCat完整代碼如下:
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author WangZH
* Created by WangZH on 2016/12/22.
*
* 使用方法:Application級別的類中
* CrashCat.getInstance(ApplicationContext, PathDirectory,PathName).start();
* e.g.
* CrashCat.getInstance(getApplicationContext(), Environment.getExternalStorageDirectory(),"/Log.txt").start();
*
*/
public class CrashCat implements Thread.UncaughtExceptionHandler {
private static CrashCat crashCat;
private Context mContext;
private Thread.UncaughtExceptionHandler mDefaultHandler;
private static String DEVICE_INFO="";
private File path;
private File fileName;
private FileOutputStream fileOutputStream;
private BufferedOutputStream bufferedOutputStream;
private static String FILE_NAME = "";
private Intent intent;
private PackageManager packageManager;
private PackageInfo packageInfo;
private CrashCat(Context context, String filePath, String fileName){
init(context,filePath,fileName);
}
public static CrashCat getInstance(Context context, String filePath, String fileName){
crashCat = new CrashCat(context,filePath,fileName);
return crashCat;
}
private void init(Context context, String filePath, String fileName){
this.mContext = context;
this.FILE_NAME = fileName;
try {
packageManager = mContext.getPackageManager();
packageInfo = packageManager.getPackageInfo(mContext.getPackageName(),0);
intent = packageManager.getLaunchIntentForPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
} catch (Exception e) {
writeLog(e.toString());
intent = null;
}
path = new File(filePath);
if (!path.exists()){
path.mkdirs();
}
StringBuffer sb = new StringBuffer();
sb.append("DeviceID="+ Build.ID+"\n");
sb.append("AndroidApi="+ Build.VERSION.SDK_INT+"\n");
sb.append("AndroidVersion="+ Build.VERSION.RELEASE+"\n");
sb.append("Brand="+ Build.BRAND+"\n");
sb.append("ManuFacture="+ Build.MANUFACTURER+"\n");
sb.append("Model="+ Build.MODEL+"\n");
sb.append("PackageName="+mContext.getPackageName()+"\n");
sb.append("CurrentVersionName="+packageInfo.versionName+"\n");
DEVICE_INFO = sb.toString();
writeLog("Application Start");
}
public void start(){
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
private void writeLog(String log){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log = "----------"+simpleDateFormat.format(new Date(System.currentTimeMillis())).toString()+"----------"+"\n"+log+"\n";
try {
fileName = new File(path+FILE_NAME);
if (fileName.exists() && fileName.length() > 10485760){
fileName.delete();
}
fileOutputStream = new FileOutputStream(fileName,true);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
bufferedOutputStream.write(log.getBytes());
bufferedOutputStream.flush();
fileOutputStream.close();
bufferedOutputStream.close();
} catch (Exception e) {
Log.e("IO Exception",e.toString());
}
}
private void handlerException(String exception) {
if (exception !=null){
try{
writeLog(DEVICE_INFO+exception.toString());
}finally {
try{
mContext.startActivity(intent);
Process.killProcess(Process.myPid());
System.exit(1);
}catch (Exception e){
Log.e("App can not restart",e.toString());
}
}
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
StackTraceElement[] stackTraceElements = e.getStackTrace();
StringBuffer sb = new StringBuffer(e.toString()+"\n");
for (int i=0,size = stackTraceElements.length;i<size;i++){
sb.append(stackTraceElements[i].toString()+"\n");
}
Log.e("error",sb.toString());
handlerException(sb.toString());
}
}
每次應(yīng)用啟動首先輸出一次啟動時間,應(yīng)用崩潰時輸出日志同時輸出該設(shè)備硬件信息,方便日后排查。
----------2017-03-26 21:54:29----------
Application Start
----------2017-03-26 21:54:41----------
DeviceID=LMY47I
AndroidApi=22
AndroidVersion=5.1
Brand=Xiaomi
ManuFacture=Xiaomi
Model=MI PAD 2
PackageName=com.xxx.xxx.xxx
java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Set java.util.Map.entrySet()' on a null object reference
com.xxxxxxxxxxxxxxxxxxxxx(xxxxxx.java:446)
com.xxxxxxxx$1$1.run(xxxxxx.java:478)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5275)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:707)
----------2017-03-26 21:56:52----------
Application Start
使用方法很簡單,只需要在Application中調(diào)用如下:
CrashCat.getInstance(getApplicationContext(), Environment.getExternalStorageDirectory().getPath()+ "/Log","log.txt");
本項目已傳至Github: Github ??走過路過,歡迎賞個star哦,????????