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("測試")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章