Android应用崩溃日志记录-Thread.UncaughtExceptionHandler

Android捕获异常监控应用运行异常的可以使用企鹅家的Bugly等第三方SDK,但是当应用运行于内网或无法连接外网时,bugly等第三方就无用武之地了。这个时候如果本地实现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.

当一个线程由于未捕获的异常而突然终止时调用的处理程序的接口。
当一个线程终止由于未捕获的异常Java虚拟机将为其UncaughtExceptionHandler查询线程使用getUncaughtExceptionHandler(),并将调用处理程序的uncaughtException方法作为参数传递和异常的线程。如果一个线程没有UncaughtExceptionHandler显式地设置,那么它的ThreadGroup对象作为其UncaughtExceptionHandler。如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发给默认的未捕获异常处理程序。

话不多说,下面贴出代码:

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());
    }
}

实现Thread.UncaughtExceptionHandler需要实现uncaughtException方法,方法中e.getStackTrace()返回的是堆栈跟踪信息,即造成应用Crash的方法调用关系,接下来使用StringBuffer拼接详细信息。

  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方法进行日志的输出以及应用重启。整个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());
    }
}

每次应用启动首先输出一次启动时间,应用崩溃时输出日志同时输出该设备硬件信息,方便日后排查。

———-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中调用如下:

CrashCat.getInstance(getApplicationContext(), Environment.getExternalStorageDirectory().getPath()+ "/Log","log.txt");

本项目已传至Github: Github   走过路过,欢迎赏个star哦,😁😁😁😁

点赞