Android Crash 笔记整理

内容摘抄自:《Android 开发艺术探索》


Crash 即奔溃,一般是由于程序发生了异常,却没有捕获而导致的(即用 try-catch 语句捕获),crash 时,系统会 kill 掉对应的正在运行的程序,导致闪退或者提示用户程序已经停止运行的现象。

Android 提供了关于处理该问题的方法,即 Thread.setDefaultUncaughtExceptionHandler()

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
     defaultUncaughtExceptionHandler = eh;
}

public interface UncaughtExceptionHandler {
    /**
     * Method invoked when the given thread terminates due to the
     * given uncaught exception.
     * <p>Any exception thrown by this method will be ignored by the
     * Java Virtual Machine.
     * @param t the thread
     * @param e the exception
     */
    void uncaughtException(Thread t, Throwable e);
}

当发生 crash 的时候,系统会回调 UncaughtExceptionHandler 接口的 uncaughtException() 方法,从而在该方法中收集异常信息,保存在本地,方便查看,也可以上传到服务器,供开发人员分析,同时还可以在 crash 发生时,弹出对话框提醒用户,再退出应用,提升交互体验。

获取应用 crash 信息的简单实现:

1、实现自定义的 UncaughtExceptionHandler 类型对象,在其 uncaughtException() 方法中获取异常信息并进行处理(保存到本地,上传到服务器);

2、然后调用 Thread.setDefaultUncaughtExceptionHandler() 将其设置为线程默认的异常处理器(因为对应的变量为 Thread 的静态变量,因此它的作用是当前进程的所有线程

具体的代码(kotlin 实现):

(1)先实现 UncaughtExceptionHandler 接口

// Thread.UncaughtExceptionHandler 是一个接口
// 以单例模式实现
object CrashHandler : Thread.UncaughtExceptionHandler {

    private const val TAG = "CreashHandler"

    var DEBUG = true

    private var mDefaultCrashHandler: Thread.UncaughtExceptionHandler? = null

    private const val PATH = "storage/emulated/0/Crash/log/"
    private const val FILE_NAME = "crash"
    private const val FILE_NAME_SUFFIX = ".trace"

    private var context: Context? = null

    fun init(context: Context) {
        Log.d("$TAG", "CrashHandler PATH = $PATH")

        this.context = context.applicationContext

        // 先获取系统默认的异常处理器,通过默认的处理器去处理发生的异常
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
        // 然后设置成自定义的
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    // 当程序存在为捕获的异常时,系统将会自动调用该回调方法
    override fun uncaughtException(t: Thread?, e: Throwable?) {
        try {
            if (e == null) {
                return
            }
            dumpExceptionToSDCard(e)
            uploadToServer()
        } catch (e: Exception) {
            Log.d(TAG, "dump crash info failed 1 \n" + e.printStackTrace())
        }

        // 打印异常
        e?.printStackTrace()

        // 如果系统存在默认的异常处理器,则交由系统去结束程序,否则手动结束应用进程
        if (mDefaultCrashHandler != null) {
            mDefaultCrashHandler!!.uncaughtException(t, e)
        } else {
            Process.killProcess(Process.myPid())
        }
    }

    private fun dumpExceptionToSDCard(ex: Throwable) {
        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
            if (DEBUG) {
                return
            }
        }

        val dir = File(PATH)
        if (!dir.exists()) {
            dir.mkdirs()
        }

        val cur = System.currentTimeMillis()
        val time = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date(cur))
        val fileName = PATH + (FILE_NAME + time + FILE_NAME_SUFFIX)
        Log.d(TAG, "fileName = $fileName")
        val file = File(fileName)

        try {
            val pw = PrintWriter(BufferedWriter(FileWriter(file)))
            pw.println(time)
            // 省略了打印手机信息的代码
            pw.println()
            ex.printStackTrace(pw)
            pw.close()
        } catch (e: Exception) {
            Log.d(TAG, "dump crash info failed 2 \n" + e.printStackTrace())
        }

    }

    private fun uploadToServer() {
        // TODO upload the info to Server
    }
}

然后在 Application 初始化:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        CrashHandler.init(this)
    }
}

需要注意:
1、记得在 Manifest 文件中注册 Application
2、因为要对内存卡进行读写,因此需要先获取相关的权限

测试的时候,可以手动抛出一个异常,或者书写错误的代码来导致异常,且不进行捕获:

// 触发数组越界异常
val array = emptyArray<Int>()
array[1]

// 手动抛出一个异常
throw RuntimeException("测试")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章