#### **應用發生Crash在所難免**
Android 應用不可避免地會發生crash,也稱之為崩潰,無論你的程序寫得多么完美,總是無法完全避免crash 的發生,可能是由于Android 系統底層的bug,也可能是由于不充分的機型適配或者是槽糕的網絡狀況。當crash 發生時,系統會kill 掉正在執行的程序,現象就是閃退或者提示用戶程序已停止運行,這對用戶來說是很不友好的,也是開發者所不愿意看到的。更糟糕的是,當用戶發生了crash,開發者卻無法得知程序為何crash ,即使開發人員想去解決這個crash ,但是由于無法知道用戶當時的crash 信息,所以往往也無能為力。
#### **如何采集crash信息以供后續開發處理這類問題呢?**
利用Thread類的setDefaultUncaughtExceptionHandler方法!defaultUncaughtHandler是Thread類的靜態成員變量,所以如果我們將自定義的UncaughtExceptionHandler設置給Thread的話,那么當前進程內的所有線程都能使用這個UncaughtExceptionHandler來處理異常了。
~~~
/**
* Sets the default uncaught exception handler. This handler is invoked in
* case any Thread dies due to an unhandled exception.
*
* @param handler
* The handler to set or null.
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
Thread.defaultUncaughtHandler = handler;
}
~~~
當crash 發生的時候,系統就會回調UncaughtExceptionHandler 的uncaughtException }j法,在uncaughtException 方法中就可以獲取到異常信息,可以選擇把異常信息存儲到SD卡中,然后在合適的時機通過網絡將crash信息上傳到服務器上,這樣開發人員就可以分析用戶crash 的場景從而在后面的版本中修復此類crash 。我們還可以在crash 發生時,彈出一個對話框告訴用戶程序crash 了,然后再退出,這樣做比閃退要溫和一點。
UncaughtExceptionHandler是Thread類的一個接口
**Thread:UncaughtExceptionHandler**
~~~
/**
* Implemented by objects that want to handle cases where a thread is being
* terminated by an uncaught exception. Upon such termination, the handler
* is notified of the terminating thread and causal exception. If there is
* no explicit handler set then the thread's group is the default handler.
*/
public static interface UncaughtExceptionHandler {
/**
* The thread is being terminated by an uncaught exception. Further
* exceptions thrown in this method are prevent the remainder of the
* method from executing, but are otherwise ignored.
*
* @param thread the thread that has an uncaught exception
* @param ex the exception that was thrown
*/
void uncaughtException(Thread thread, Throwable ex);
}
~~~
#### **獲取應用crash 信息的方式了**
首先需要實現一個UncaughtExceptionHandler 對象,在它的uncaughtException 方法中獲取異常信息并將其存儲在SD 卡中或者上傳到服務器供開發人員分析,然后調用Thread 的setDefaultUncaughtExceptionHandler方法將它設置為線程默認的異常處理器,由于默認異常處理器是Thread類的靜態成員,因此它的作用對象是當前進程的所有線程。
**示例**:
作者實現了一個簡易版本的UncaughtExceptionHandler類的子類CrashHandler,[源碼傳送門](https://gitee.com/Alexwsc/androiddevelopartistic/tree/master/Chapter_13),本示例中并沒有實現將異常信息上傳到服務器,但是在實際開發中一般都需要將異常信息上傳到服務器。
**CrashHandler**
~~~
package com.ryg.crashtest;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log;
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true;
private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/CrashTest/log/";
private static final String FILE_NAME = "crash";
private static final String FILE_NAME_SUFFIX = ".trace";
private static CrashHandler sInstance = new CrashHandler();
private UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;
private CrashHandler() {
}
public static CrashHandler getInstance() {
return sInstance;
}
public void init(Context context) {
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mContext = context.getApplicationContext();
}
/**
* 這個是最關鍵的函數,當程序中有未被捕獲的異常,系統將會自動調用#uncaughtException方法
* thread為出現未捕獲異常的線程,ex為未捕獲的異常,有了這個ex,我們就可以得到異常信息。
*
* 這個方法的回調,該方法運行在發生crash的那個線程。如果UI線程crash,就運行在UI線程
* 如果發生在子線程,就運行在子線程;因此不可以在這個方法中彈出toast或者dialog(因為這個線程的消息循環已經被破壞了)
* 但是可以跳轉到一個Activity,在這個Acitivty中彈dialog或者toast
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
//導出異常信息到SD卡中
dumpExceptionToSDCard(ex);
uploadExceptionToServer();
//這里可以通過網絡上傳異常信息到服務器(完成下面的方法uploadExceptionToServer),便于開發人員分析日志從而解決bug
} catch (IOException e) {
e.printStackTrace();
}
ex.printStackTrace();
// 發生crash之后,需要將進程殺掉,因為此時程序不能繼續往下運行,程序狀態已不對
//如果系統提供了默認的異常處理器,則交給系統去結束我們的程序,否則就由我們自己結束自己
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, ex);
} else {
Process.killProcess(Process.myPid());
}
}
private void dumpExceptionToSDCard(Throwable ex) throws IOException {
//如果SD卡不存在或無法使用,則無法把異常信息寫入SD卡
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.w(TAG, "sdcard unmounted,skip dump exception");
return;
}
}
File dir = new File(PATH);
if (!dir.exists()) {
dir.mkdirs();
}
long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);
try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
pw.println(time);
dumpPhoneInfo(pw);
pw.println();
ex.printStackTrace(pw);
pw.close();
} catch (Exception e) {
Log.e(TAG, "dump crash info failed");
}
}
private void dumpPhoneInfo(PrintWriter pw) throws NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.print("App Version: ");
pw.print(pi.versionName);
pw.print('_');
pw.println(pi.versionCode);
//android版本號
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);
//手機制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
//手機型號
pw.print("Model: ");
pw.println(Build.MODEL);
//cpu架構
pw.print("CPU ABI: ");
pw.println(Build.CPU_ABI);
}
private void uploadExceptionToServer() {
//TODO Upload Exception Message To Your Web Server
}
}
~~~
從上面的代碼可以看出,當應用崩潰時, CrashHandler 會將異常信息以及設備信息寫入SD 卡,接著將異常交給系統處理,系統會幫我們中止程序,如果系統沒有默認的異常處理機制,那么就自行中止CrashHandler的使用方式就是在Application的onCreate方法中設置一下即可。
如何使用上面的CrashHandler 呢?也很簡單,可以在Application 初始化的時候為線程設置CrashHandler,如下所示。
**MyApplication**
~~~
package com.ryg.crashtest;
import android.app.Application;
public class MyApplication extends Application {
private static MyApplication sInstance;
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
//在這里為應用設置異常處理程序,然后我們的程序才能捕獲未處理的異常
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
public static MyApplication getInstance() {
return sInstance;
}
}
~~~
**CrashActivity**
~~~
package com.ryg.crashtest;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;
/**
* @author wsc
*/
public class CrashActivity extends Activity implements OnClickListener {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash);
initView();
}
private void initView() {
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == mButton) {
// 在這里模擬異常拋出情況,人為拋出一個運行時異常
throw new RuntimeException("自定義異常:這是自己拋出的異常");
}
}
}
~~~
在上面的測試代碼中,給按鈕加一個單擊事件,在onClick 中人為拋出一個運行時異常,這個時候程序就crash 了,看看異常處理器為在們做了什么。從圖2中可以看出,異常處理器為我們創建了一個日志文件,打開日志文件圖3,可以看到手機的信息以及異常發生時的調用枝, 有了這些內容,開發人員就很容易定位問題了。
如下圖1所示

點擊按鈕,就會崩潰crash
下圖2、圖3是采集到的crash信息

圖2 存儲崩潰信息文件的位置

圖3 采集到的崩潰信息
- 前言
- 第一章Activity的生命周期和啟動模式
- 1.1 Activity生命周期全面分析
- 1.2 Activity的啟動模式
- 1.3 IntentFilter的匹配規則
- 第二章IPC
- 轉 chapter IPC
- 轉IPC1
- 轉IPC2
- Binder講解
- binder
- Messenger
- 一、Android IPC簡介
- 二、Android中的多進程模式
- 三、IPC基礎概念介紹
- 四、Android中的IPC方式
- 五、Binder連接池
- 第三章
- 第九章四大組件的工作過程
- 第十章
- 第13章 綜合技術
- 使用CrashHandler 來獲取應用的crash 信息
- 使用Multidex來解決方法數越界
- Android的動態加載技術