Android之UncaughtExceptionHandler异常捕获

概要

在Android开发中,目前没有工业级的bug收集工具,不比java后台服务有各式各样的收集工具,Android用的更多一些
第三方的如bugly或者友盟等。那如何自己去收集呢?其实安卓原生提供了异常收集类UncaughtExceptionHandler。

UncaughtExceptionHandler正如其名称所示,用于收集未捕获的异常(简单的理解可以认为是崩溃异常)。
具体使用分为两步:

  1. 实现自定义的异常处理类:
    1
    2
    3
    4
    5
    6
    public class CrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    //异常具体的处理,记录亦或上传到服务器
    }
    }

实现Thread.UncaughtExceptionHandler接口当有未捕获的异常的时候会回调uncaughtException(Thread thread, Throwable ex)方法

  1. 设置自定的异常处理类为系统默认的
    1
    Thread.setDefaultUncaughtExceptionHandler(crashHandler);

以上就是大致的使用步骤

具体代码逻辑

  1. 在application中初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class AppApplication extends Application {


    private static Context mContext;

    @Override
    public void onCreate() {
    super.onCreate();
    this.mContext = this;
    CrashHandler.getInstance().init(this);
    }

    public static Context getContext(){
    return mContext;
    }
    }
  2. CrashHandler 实现类如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    public class CrashHandler implements Thread.UncaughtExceptionHandler {

    /**
    * 系统默认UncaughtExceptionHandler
    */
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    /**
    * context
    */
    private Context mContext;

    /**
    * 存储异常和参数信息
    */
    private Map paramsMap = new HashMap<>();

    /**
    * 格式化时间
    */
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

    private String TAG = this.getClass().getSimpleName();

    private static CrashHandler mInstance;

    private CrashHandler() {

    }

    /**
    * 获取CrashHandler实例
    */
    public static synchronized CrashHandler getInstance(){
    if(null == mInstance){
    mInstance = new CrashHandler();
    }
    return mInstance;
    }

    public void init(Context context){
    mContext = context;
    mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

    Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
    * 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.e(TAG, "error : ", e);
    }

    AppManager.getAppManager().AppExit(mContext);
    }

    }

    /**
    * 收集错误信息.发送到服务器
    * @return 处理了该异常返回true,否则false
    */
    private boolean handleException(Throwable ex) {
    if (ex == null) {
    return false;
    }

    collectDeviceInfo(mContext);

    new Thread() {
    @Override
    public void run() {
    Looper.prepare();
    Toast.makeText(mContext, "程序开小差了呢..", Toast.LENGTH_SHORT).show();
    Looper.loop();
    }
    }.start();

    saveCrashInfo2File(ex);
    return true;
    }


    /**
    * 收集设备参数信息
    * @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 + "";
    paramsMap.put("versionName", versionName);
    paramsMap.put("versionCode", versionCode);
    }
    } catch (PackageManager.NameNotFoundException e) {
    Log.e(TAG, "an error occured when collect package info", e);
    }

    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
    try {
    field.setAccessible(true);
    paramsMap.put(field.getName(), field.get(null).toString());
    } catch (Exception e) {
    Log.e(TAG, "an error occured when collect crash info", e);
    }
    }
    }

    /**
    * 保存错误信息到文件中
    *
    * @param ex
    * @return 返回文件名称,便于将文件传送到服务器
    */
    private String saveCrashInfo2File(Throwable ex) {

    StringBuffer sb = new StringBuffer();
    for (Map.Entry entry : paramsMap.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    sb.append(key + "=" + value + "\n");
    }

    Writer writer = new StringWriter();
    PrintWriter printWriter = new PrintWriter(writer);
    ex.printStackTrace(printWriter);
    Throwable cause = ex.getCause();
    while (cause != null) {
    cause.printStackTrace(printWriter);
    cause = cause.getCause();
    }
    printWriter.close();
    String result = writer.toString();
    sb.append(result);
    try {
    long timestamp = System.currentTimeMillis();
    String time = format.format(new Date());
    String fileName = "crash-" + time + "-" + timestamp + ".log";
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crash/";
    File dir = new File(path);
    if (!dir.exists()) {
    dir.mkdirs();
    }
    FileOutputStream fos = new FileOutputStream(path + fileName);
    fos.write(sb.toString().getBytes());
    fos.close();
    }
    return fileName;
    } catch (Exception e) {
    Log.e(TAG, "an error occured while writing file...", e);
    }
    return null;
    }
    }

⚠️此处并未使用常用的退出应用代码

1
2
3
4
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
//区别在于:system.exit(0):正常退出,程序正常执行结束退出,system.exit(1):是非正常退出,就是说无论程序正在执行与否,都退出,
//System.exit(status)不管status为何值都会退出程序。和return 相比有以下不同点:return是回到上一层,而System.exit(status)是回到最上层 */

具体原因为:在4.4即以上系统使用该方法杀进程的时候app会重启,类似内存不足app挂了,系统会发广播拉起app的那种情况,那么就有可能会导致一个异常多次上报的情况,所以这里实现的activity栈来管理退出.

  1. activity管理类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    public class AppManager {  


    private static Stack activityStack;

    private static AppManager instance;

    private AppManager() {
    }

    /**
    * 单一实例
    */
    public static AppManager getAppManager() {
    if (instance == null) {
    instance = new AppManager();
    }
    return instance;
    }

    /**
    * 添加Activity到堆栈
    */
    public void addActivity(Activity activity) {
    if (activityStack == null) {
    activityStack = new Stack();
    }
    activityStack.add(activity);
    }

    /**
    * 获取当前Activity(堆栈中最后一个压入的)
    */
    public Activity currentActivity() {
    Activity activity = activityStack.lastElement();
    return activity;
    }

    /**
    * 结束当前Activity(堆栈中最后一个压入的)
    */
    public void finishActivity() {
    Activity activity = activityStack.lastElement();
    finishActivity(activity);
    }

    /**
    * 结束指定的Activity
    */
    public void finishActivity(Activity activity) {
    if (activity != null) {
    activityStack.remove(activity);
    activity.finish();
    activity = null;
    }
    }

    /**
    * 结束指定类名的Activity
    */
    public void finishActivity(Class cls) {
    for (Activity activity : activityStack) {
    if (activity.getClass().equals(cls)) {
    finishActivity(activity);
    break;
    }
    }
    }

    /**
    * 结束所有Activity
    */
    public void finishAllActivity() {
    for (int i = 0; i < activityStack.size(); i++) {
    if (null != activityStack.get(i)) {
    activityStack.get(i).finish();
    }
    }
    activityStack.clear();
    }

    /**
    * 退出应用程序
    */
    public void AppExit(Context context) {
    try {
    finishAllActivity();
    ActivityManager activityMgr = (ActivityManager) context
    .getSystemService(Context.ACTIVITY_SERVICE);
    activityMgr.killBackgroundProcesses(context.getPackageName());
    System.exit(0);
    } catch (Exception e) {
    }
    }
    }