1. 概述

本文主要讲解如何自定义 Android 全局异常捕获,以及如何通过 Dialog 展示异常信息并将异常信息上传至服务器。

下面是最终的效果图:





主要涉及的知识点有:

  • Thread.UncaughtExceptionHandler 原理分析
  • Android 6.0 权限
  • 自定义Dialog
2. 实现原理分析

Thread.UncaughtExceptionHandler 是 Android 实现全局异常捕获功能的核心,尽管此类在实现 Android 全局捕获异常中有举足轻重的地位,但它并不是 Google 的开发工程师创建的,它属于 Java lang 包。

Thread.UncaughtExceptionHandler 是 Java 虚拟机处理由于未捕获异常而导致程序停止运行的接口——当一个线程由于未捕获异常而停止运行的时候,Java 虚拟机就会通过 Thread 的 getUncaughtExceptionHandler() 方法拿到当前 Thread 的 Thread.UncaughtExceptionHandler,并调用该 Thread.UncaughtExceptionHandler 的 uncaughtException 方法。

uncaughtException 方法的具体定义如下:

uncaughtException 方法的第二个参数是导致程序终止运行的异常对象,异常对象都拿到了,至于后面怎么处理,IT IS UP TO YOU!

异常执行的具体流程:

3. 具体实现步骤
3.1 青铜版——直接在 Application 中实现 Thread.UncaughtExceptionHandler 接口
3.1.1 创建 MainActivity 类

创建 MainActivity 类,在对应的布局文件(activity_main)中添加 Button 按钮,为 Button 添加点击事件,在 Button 点击事件中手动抛出一个未捕获异常。

PS:XML 布局文件中关于 Dimension、String、Color 等的定义都在相应的文件中(dimens.xml、strings.xml、colors.xml 等),由于这些资源文件在此处为非关键必要内容,故不贴出。

建议大家将 Dimension、String、Color 等数据定义在相应的资源文件中,进行统一管理,以便将来需要修改数据的时候容易查找。

//源码 MainActivitypublic class MainActivity extends AppCompatActivity implements View.OnClickListener{        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void throwRuntimeException(View view){        throw new RuntimeException(CommonConstant.TIAN_WANG_GAI_DI_HU);    }}//源码 activity_main<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:orientation="vertical"    android:paddingBottom="@dimen/padding_small"    android:paddingLeft="@dimen/padding_medium"    android:paddingRight="@dimen/padding_medium"    android:paddingTop="@dimen/padding_small"    tools:context=".MainActivity">    
3.1.2 自定义 Application 类

自定义 Application 类,并实现 Thread.UncaughtExceptionHandler 接口中定义的方法 uncaughtException,并在 uncaughtException 方法中将异常信息输出。

在 Application 的 onCreate 方法中通过 Thread.setDefaultUncaughtExceptionHandler() 方法将实现了 Thread.UncaughtExceptionHandler 接口的 Application 设置为当前 Application 默认的 Thread.UncaughtExceptionHandler。

//源码 BaseApplicationpublic class BaseApplication extends Application implements Thread.UncaughtExceptionHandler{    @Override    public void onCreate() {        super.onCreate();        Thread.setDefaultUncaughtExceptionHandler(this);    }    @Override    public void uncaughtException(final Thread thread, final Throwable throwable) {        Writer writer = new StringWriter();        PrintWriter printWriter = new PrintWriter(writer);        throwable.printStackTrace(printWriter);        try {            String result = writer.toString();            Log.e(CommonConstant.TAG,"\r\nCurrentThread:" + Thread.currentThread() + "\r\nException:\r\n" + result);            printWriter.close();            writer.close();        }catch (Exception e){            e.printStackTrace();        }        //Toast 提示用户        new Thread() {            @Override            public void run() {                Looper.prepare();                Toast.makeText(BaseApplication.this, getString(R.string.exception_occurs_prompt), Toast.LENGTH_LONG).show();                Looper.loop();            }        }.start();        //关闭应用程序        SystemClock.sleep(1000);        android.os.Process.killProcess(android.os.Process.myPid());    }}复制代码
3.1.3 在 AndroidManifest 文件中声明自定义的 Application
//源码 AndroidManifest<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    package="com.smart.exceptionanalyse">    ".BaseApplication"        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:roundIcon="@mipmap/ic_launcher_round"        android:supportsRtl="true"        android:theme="@style/AppTheme">        ".MainActivity">                            "android.intent.action.MAIN" />                "android.intent.category.LAUNCHER" />                        复制代码

最终的效果就是下面这个样子:

//执行结果08-18 18:45:18.878 5045-5045/com.smart.exceptionanalyse E/TAG: CurrentThread:Thread[main,5,main]    Exception:    java.lang.IllegalStateException: Could not execute method for android:onClick        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:390)        at android.view.View.performClick(View.java:5610)        at android.view.View$PerformClick.run(View.java:22265)        at android.os.Handler.handleCallback(Handler.java:751)        at android.os.Handler.dispatchMessage(Handler.java:95)        at android.os.Looper.loop(Looper.java:154)        at android.app.ActivityThread.main(ActivityThread.java:6077)        at java.lang.reflect.Method.invoke(Native Method)        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)     Caused by: java.lang.reflect.InvocationTargetException        at java.lang.reflect.Method.invoke(Native Method)        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)        at android.view.View.performClick(View.java:5610)         at android.view.View$PerformClick.run(View.java:22265)         at android.os.Handler.handleCallback(Handler.java:751)         at android.os.Handler.dispatchMessage(Handler.java:95)         at android.os.Looper.loop(Looper.java:154)         at android.app.ActivityThread.main(ActivityThread.java:6077)         at java.lang.reflect.Method.invoke(Native Method)         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)      Caused by: java.lang.RuntimeException: 天王盖地虎        at com.smart.exceptionanalyse.MainActivity.throwRuntimeException(MainActivity.java:68)        at java.lang.reflect.Method.invoke(Native Method)         at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)         at android.view.View.performClick(View.java:5610)         at android.view.View$PerformClick.run(View.java:22265)         at android.os.Handler.handleCallback(Handler.java:751)         at android.os.Handler.dispatchMessage(Handler.java:95)         at android.os.Looper.loop(Looper.java:154)         at android.app.ActivityThread.main(ActivityThread.java:6077)         at java.lang.reflect.Method.invoke(Native Method)         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) 复制代码

到此青铜版的 Android 异常捕获功能就实现了,是不是很简单?

上面程序中的异常是我们手动抛出的,异常信息为“天王盖地虎”,异常抛出的位置在异常信息中已经明确标出,至于后面怎么处理,IT IS UP TO YOU!

3.1.4 青铜版之番外篇——当异常出现在子线程中时会出现什么情况?

上面异常抛出的地方在主线程中,那如果异常出现在子线程中,Thread.UncaughtExceptionHandler 还会不会被调用呢?

将 MainActivity 类中的 throwRuntimeException 方法做部分修改,其他均不变:

//源码 MainActivitypublic class MainActivity extends AppCompatActivity implements View.OnClickListener{    private Button mExecute;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void throwRuntimeException(View view){        new Thread(){            @Override            public void run() {                super.run();                throw new RuntimeException(CommonConstant.TIAN_WANG_GAI_DI_HU);            }        }.start();    }}//执行结果08-18 19:24:01.978 6599-6626/com.smart.exceptionanalyse E/TAG: CurrentThread:Thread[Thread-4,5,main]    Exception:    java.lang.RuntimeException: 天王盖地虎        at com.smart.exceptionanalyse.MainActivity$1.run(MainActivity.java:72)复制代码

由上面的执行结果可知:

即使异常发生在子线程中,Thread.UncaughtExceptionHandler 依然会被调用,只不过异常信息没有那么详细了,而且当前异常线程为 Thread-4 而不是 main。

3.2 白银版——自定义 Thread.UncaughtExceptionHandler 实现类,并将异常信息保存至 SD 卡
3.2.1 创建 MainActivity 类

因为需要将异常信息保存至 SD 卡,因此在 MainActivity 初始化的时候需要申请手机存储权限(android.permission.WRITE_EXTERNAL_STORAGE)。

//源码 MainActivitypublic class MainActivity extends AppCompatActivity implements View.OnClickListener{    private Button mExecute;    private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 0x0001024;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initData();    }    private void initData(){        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                        PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);            }        }    }        @Override    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {        if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) {            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {                showToast(getString(R.string.permission_granted));            } else {                showToast(getString(R.string.permission_denied));            }        }        super.onRequestPermissionsResult(requestCode, permissions, grantResults);    }    private void showToast(String string){        Toast.makeText(MainActivity.this,string,Toast.LENGTH_SHORT).show();    }    public void throwRuntimeException(View view){        throw new RuntimeException(CommonConstant.TIAN_WANG_GAI_DI_HU);    }}//源码 activity_main<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:orientation="vertical"    android:paddingBottom="@dimen/padding_small"    android:paddingLeft="@dimen/padding_medium"    android:paddingRight="@dimen/padding_medium"    android:paddingTop="@dimen/padding_small"    tools:context=".MainActivity">    
3.2.2 自定义 Thread.UncaughtExceptionHandler 实现类——CrashHandler

自定义 Thread.UncaughtExceptionHandler 实现类——CrashHandler,在 CrashHandler 中实现 uncaughtException 方法。在 uncaughtException 方法中将异常信息存储至 SD 卡。

//源码 CrashHandlerpublic class CrashHandler implements Thread.UncaughtExceptionHandler {    private static CrashHandler INSTANCE = new CrashHandler();    private Context mContext;    private Thread.UncaughtExceptionHandler mDefaultHandler;    private CrashHandler() {}    public static CrashHandler getInstance() {        return INSTANCE;    }    public void initCrashHandler(Context context) {        mContext = context;        //获取系统默认的 UncaughtExceptionHandler        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();        //设置该 CrashHandler 为程序的默认 UncaughtExceptionHandler        Thread.setDefaultUncaughtExceptionHandler(this);    }    @Override    public void uncaughtException(Thread thread, Throwable throwable) {        if (!handleException(throwable) && mDefaultHandler != null) {            //如果用户没有处理则让系统默认的 UncaughtExceptionHandler 来处理            mDefaultHandler.uncaughtException(thread, throwable);        } else {            SystemClock.sleep(1000);            //退出程序            android.os.Process.killProcess(android.os.Process.myPid());        }    }    /**     * Des: 将异常信息保存至 SD 卡     *     * Time: 2018/8/15 上午12:33     */    private boolean handleException(Throwable throwable){        if(throwable == null){            return false;        }        //使用Toast来显示异常信息        new Thread() {            @Override            public void run() {                Looper.prepare();                Toast.makeText(mContext, mContext.getString(R.string.exception_occurs_prompt), Toast.LENGTH_LONG).show();                Looper.loop();            }        }.start();        Writer writer = new StringWriter();        PrintWriter printWriter = new PrintWriter(writer);        throwable.printStackTrace(printWriter);        Throwable cause = throwable.getCause();        while (cause != null) {            cause.printStackTrace(printWriter);            cause = cause.getCause();        }        printWriter.close();        String result = writer.toString();        try {            writer.close();        }catch (Exception e){            e.printStackTrace();        }        try {            StringBuffer sb = new StringBuffer();            PackageManager pm = mContext.getPackageManager();            PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);            if (pi != null) {                sb.append("应用版本:" + pi.versionName + "\n");                sb.append("应用版本号:" + pi.versionCode + "\n");                sb.append("品牌:" + Build.MANUFACTURER + "\n");                sb.append("机型:" + Build.MODEL + "\n");                sb.append("Android 版本:" + Build.VERSION.RELEASE + "\n");                sb.append("系统版本:" + Build.DISPLAY + "\n");                long timestamp = System.currentTimeMillis();                DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");                String time = formatter.format(new Date());                sb.append("报错时间:" + time + "\n");                sb.append("异常信息:" + "\n" + result);                String fileName = "crash_" + timestamp + ".log";                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {                    String path = Environment.getExternalStorageDirectory()+ File.separator + mContext.getPackageName() + File.separator + "Crash";                    File dir = new File(path);                    if (!dir.exists()) {                        dir.mkdirs();                    }                    File file = new File(path,fileName);                    if(!file.exists()){                        file.createNewFile();                    }                    FileOutputStream fos = new FileOutputStream(file.getPath());                    fos.write(sb.toString().getBytes());                    fos.close();                }                mExceptionMessage = sb.toString();            }        } catch (Exception e) {            Log.e(CommonConstant.TAG, "An error occured when collect package info", e);        }        return true;    }}复制代码
3.2.3 自定义 Application——BaseApplication

自定义 Application——BaseApplication,并在 BaseApplication 中初始化 CrashHandler。

//源码 BaseApplicationpublic class BaseApplication extends Application{    @Override    public void onCreate() {        super.onCreate();        CrashHandler crashHandler = CrashHandler.getInstance();        crashHandler.init(this);    }}复制代码
3.2.4 在 AndroidManifest 文件中声明自定义的 Application 及存储权限
//源码 AndroidManifest<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    package="com.smart.exceptionanalyse">    "android.permission.WRITE_EXTERNAL_STORAGE" />    ".BaseApplication"        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:roundIcon="@mipmap/ic_launcher_round"        android:supportsRtl="true"        android:theme="@style/AppTheme">        ".MainActivity">                            "android.intent.action.MAIN" />                "android.intent.category.LAUNCHER" />                        复制代码

最终的效果就是下面这个样子:





3.3 黄金版——自定义 Thread.UncaughtExceptionHandler 实现类,将异常信息展示在 Dialog 中,并将异常信息提交至服务器
3.3.1 创建 MainActivity 类

同 3.2.1 一样,故在此不赘述。

3.3.2 自定义 Thread.UncaughtExceptionHandler 实现类——CrashHandler

自定义 Thread.UncaughtExceptionHandler 实现类——CrashHandler,在 CrashHandler 中实现 uncaughtException 方法。在 uncaughtException 方法中将异常信息存储至 SD 卡、通过 Intent 将异常信息发送至新的 Activity——CrashInfoActivity。

//源码 CrashHandlerpublic class CrashHandler implements Thread.UncaughtExceptionHandler {    private static CrashHandler INSTANCE = new CrashHandler();    private Context mContext;    private Thread.UncaughtExceptionHandler mDefaultHandler;    private String mExceptionMessage;    private CrashHandler() {}    public static CrashHandler getInstance() {        return INSTANCE;    }    public void initCrashHandler(Context context) {        mContext = context;        //获取系统默认的 UncaughtExceptionHandler        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();        //设置该 CrashHandler 为程序的默认 UncaughtExceptionHandler        Thread.setDefaultUncaughtExceptionHandler(this);    }    @Override    public void uncaughtException(Thread thread, Throwable throwable) {        if (!handleException(throwable) && mDefaultHandler != null) {            //如果用户没有处理则让系统默认的 UncaughtExceptionHandler 来处理            mDefaultHandler.uncaughtException(thread, throwable);        } else {            Intent intent = new Intent(mContext, CrashInfoActivity.class);            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            intent.putExtra(CommonConstant.EXCEPTION_MESSAGE, mExceptionMessage);            mContext.startActivity(intent);            SystemClock.sleep(1000);            //退出程序            android.os.Process.killProcess(android.os.Process.myPid());        }    }    /**     * Des: 将异常信息保存至 SD 卡     *     * Time: 2018/8/15 上午12:33     */    private boolean handleException(Throwable throwable){        if(throwable == null){            return false;        }        //Toast 提示用户        new Thread() {            @Override            public void run() {                Looper.prepare();                Toast.makeText(mContext, mContext.getString(R.string.exception_occurs_prompt), Toast.LENGTH_LONG).show();                Looper.loop();            }        }.start();        Writer writer = new StringWriter();        PrintWriter printWriter = new PrintWriter(writer);        throwable.printStackTrace(printWriter);        Throwable cause = throwable.getCause();        while (cause != null) {            cause.printStackTrace(printWriter);            cause = cause.getCause();        }        printWriter.close();        String result = writer.toString();        try {            writer.close();        }catch (Exception e){            e.printStackTrace();        }        try {            StringBuffer sb = new StringBuffer();            PackageManager pm = mContext.getPackageManager();            PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);            if (pi != null) {                sb.append("应用版本:" + pi.versionName + "\n");                sb.append("应用版本号:" + pi.versionCode + "\n");                sb.append("品牌:" + Build.MANUFACTURER + "\n");                sb.append("机型:" + Build.MODEL + "\n");                sb.append("Android 版本:" + Build.VERSION.RELEASE + "\n");                sb.append("系统版本:" + Build.DISPLAY + "\n");                long timestamp = System.currentTimeMillis();                DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");                String time = formatter.format(new Date());                sb.append("报错时间:" + time + "\n");                sb.append("异常信息:" + "\n" + result);                String fileName = "crash_" + timestamp + ".log";                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {                    String path = Environment.getExternalStorageDirectory()+ File.separator + mContext.getPackageName() + File.separator + "Crash";                    File dir = new File(path);                    if (!dir.exists()) {                        dir.mkdirs();                    }                    File file = new File(path,fileName);                    if(!file.exists()){                        file.createNewFile();                    }                    FileOutputStream fos = new FileOutputStream(file.getPath());                    fos.write(sb.toString().getBytes());                    fos.close();                }                mExceptionMessage = sb.toString();            }        } catch (Exception e) {            Log.e(CommonConstant.TAG, "An error occured when collect package info", e);        }        return true;    }}复制代码
3.3.3 创建 CrashInfoActivity

创建 CrashInfoActivity,在对应的布局文件(activity_crash_info)中创建两个按钮——重启应用、异常信息。重启应用按钮用于重启 App,异常信息按钮用于弹出对话框。此处的对话框并不是系统自带的,而是自定义的,这样的目的是为了控制对话框的尺寸,因为对话框要显式异常信息,有时异常信息会特别长,如果用系统自带的对话框,就会占满整个屏幕。

PS:

  • 自定义 Dialog 在此处为非关键必要因素,故不贴出
  • 此处的上传功能并未真正实现,是用 Handler 通过倒计时模拟的
//源码 CrashInfoActivitypublic class CrashInfoActivity extends AppCompatActivity implements View.OnClickListener{    private Button mRestartApp,mShowExceptionInfo;    private String mExceptionMessageInfo;    private SmartProgressDialog mSmartDialog;    private static final int UPLOAD_EXCEPTION_MESSAGE = 0X00001;    private static final int COUNT_DOWN = 5;    private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case UPLOAD_EXCEPTION_MESSAGE:                    int countDown = (Integer) msg.obj;                    countDown--;                    if(countDown == 4){                        uploadExceptionMessage();                    }else if(countDown == 0){                        uploadSucceed();                    }                    if(countDown > 0){                        Message message = new Message();                        message.what = UPLOAD_EXCEPTION_MESSAGE;                        message.obj = countDown;                        mHandler.sendMessageDelayed(message,1000);                    }                    break;            }        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_crash_info);        initView();        initData();    }    private void initView(){        mRestartApp = findViewById(R.id.app_restart);        mShowExceptionInfo = findViewById(R.id.show_exception_info);        mRestartApp.setOnClickListener(this);        mShowExceptionInfo.setOnClickListener(this);    }    private void initData(){        Intent intent = getIntent();        mExceptionMessageInfo = intent.getStringExtra(CommonConstant.EXCEPTION_MESSAGE);    }    @Override    public void onClick(View v) {        int id = v.getId();        switch (id){            case R.id.app_restart:                Intent intent = new Intent(this, MainActivity.class);                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                startActivity(intent);                this.finish();                break;            case R.id.show_exception_info:                final SmartNormalDialog smartDialog = new SmartNormalDialog(this);                smartDialog.setTitle(getResources().getString(R.string.exception_dialog_title));                smartDialog.setContent(mExceptionMessageInfo);                smartDialog.setCancelButtonText(getResources().getString(R.string.exception_dialog_cancel));                smartDialog.setConfirmButtonText(getResources().getString(R.string.exception_dialog_confirm));                smartDialog.setOnDialogClickListener(new SmartDialog.OnDialogClickListener() {                    @Override                    public void onConfirmClick() {                        smartDialog.dismiss();                    }                    @Override                    public void onCancelClick() {                        smartDialog.dismiss();                        Message message = new Message();                        message.what = UPLOAD_EXCEPTION_MESSAGE;                        message.obj = COUNT_DOWN;                        mHandler.sendMessageDelayed(message,300);                    }                });                smartDialog.show();                break;        }    }    private void uploadExceptionMessage(){        if(mSmartDialog == null){            mSmartDialog = new SmartProgressDialog(this);        }        mSmartDialog.setContent(getResources().getString(R.string.exception_upload_dialog_content));        mSmartDialog.show();    }    private void uploadSucceed(){        if(mSmartDialog != null){            mSmartDialog.dismiss();        }        Toast.makeText(this, getString(R.string.exception_upload_succeed), Toast.LENGTH_SHORT).show();    }}//源码 activity_crash_info<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:orientation="vertical"    tools:context=".CrashInfoActivity">    "@+id/exception_message"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:src="@drawable/customactivityoncrash_error_image" />    "match_parent"        android:layout_height="wrap_content"        android:gravity="center"        android:paddingBottom="@dimen/padding_icon"        android:paddingTop="@dimen/padding_icon"        android:text="@string/exception_occurs"        android:textSize="@dimen/font_medium"        android:textStyle="bold" />    


3.3.4 自定义 Application——BaseApplication

同 3.2.3 一样,故在此不赘述。

3.3.5 在 AndroidManifest 文件中声明自定义的 Application 、存储权限以及 CrashInfoActivity
//源码 AndroidManifest<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android"    package="com.smart.exceptionanalyse">    "android.permission.WRITE_EXTERNAL_STORAGE" />    ".BaseApplication"        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:roundIcon="@mipmap/ic_launcher_round"        android:supportsRtl="true"        android:theme="@style/AppTheme">        ".MainActivity">                            "android.intent.action.MAIN" />                "android.intent.category.LAUNCHER" />                            ".CrashInfoActivity"            android:label="@string/exception_message" />    复制代码

最终的效果就是下面这个样子:



到此黄金版的 Android 异常捕获功能就实现了,是不是很简单?

如果你足够的细心的话,一定会发现我们的 Dialog 显式、异常信息上传都是在新的 Activity 中进行,那能不能直接在 CrashHandler 中显式呢?这个问题就交给你们了。

4. 应用

全局异常捕获是 Android 应用程序不可或缺的一个功能,因为它可以方便将异常信息收集起来,以便开发者在后期的开发工作中改掉一些测试工作人员未发现但用户在使用的过程中出现的 Bug。鉴于此,建议大家将此功能封装进自己的 BaseLibrary,这样就可以避免重复造轮子了。

5. 关联知识

除了自定义全局异常捕获之外,我们还可以使用第三方提供的库,如:

  • Bugly
  • ACRA

之所以不用此类库是因为它们里面会包含其他不是我所需要的服务,但这些第三方可能比我们自定义的异常捕获要全面,毕竟它们已经迭代了那么多次。所以到底要如何选择,IT IS UP TO YOU!


参考文档

1)Thread.UncaughtExceptionHandler Java
2)Thread.UncaughtExceptionHandler Android Developer
3)Android开发之全局异常捕获
4)Android 6.0 权限申请
5)CustomActivityOnCrash

转载于:https://juejin.im/post/5b7916c2f265da438223af94

更多相关文章

  1. android 程序开发的插件化 模块化方法 之一
  2. android studio项目嵌入到Android系统源码
  3. Handler源码详解及导致内存泄漏的分析
  4. Android中几种关闭Activity或app的方法
  5. android 获取 apk mainfest.xml中的信息
  6. Android系统基础(02) 系统源码环境搭建
  7. Windows下离线安装Android SDK的简单方法
  8. 如何将android2.1源码添加到自己的项目当中
  9. android源码下载备注

随机推荐

  1. Android向Http服务器发送Http请求异常-Un
  2. ### Android(安卓)判断app的状态 重启app
  3. Android程序——退出程序的时候杀死所有
  4. Android自动播放图片功能实现
  5. Android(安卓)Activity (1)
  6. (Butterknife 注解配置)android studio 3.0
  7. Android数据缓存
  8. android image最常用操作
  9. Android: R cannot be resolved to a var
  10. Android之ListView与自定义adapter简单实