內容摘抄自:《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("測試")