欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Crash 笔记整理

程序员文章站 2022-04-15 18:41:39
...

内容摘抄自:《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("测试")
相关标签: Android Crash