前言
本篇屬于《Android開發藝術探索》第13章使用CrashHandler獲取crash信息的學習筆記,如有錯誤,請指教,不甚感激。
沒有一個App是完美的,都是在不斷的完善修改bug中變的更好。特別是一些上線的app出現一些crash情況后,我們并不能及時的看到,所以為了了解用戶在使用過程中出現了哪些crash信息,我們需要掌握crash信息收集技能。
基本思想
Google考慮到此情況,在Thread類中提供了一個接口,UncaughtExecptionHandler來提供我們使用,當應用發生了未捕獲的異常時,系統會自動調用這個接口的uncaughtException方法。我們在這個方法中可以保存異常信息到SD卡,然后在特定的時間將保存的文件上傳到web服務器,開發人員可以從服務器拉取文件,分析并修改相關的crash。
Demo代碼
自定義CrashHandler類并實現Thread.UncaughtExceptionHandler接口
public class CrashHandler implements Thread.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 sCrashHandler = new CrashHandler();
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;
private CrashHandler(){}
// 單例模式
public static CrashHandler getInstance(){
return sCrashHandler;
}
public void init(Context context){
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this); //這兩句代碼不是很明白,有清楚的希望指教下
mContext = context.getApplicationContext();
}
/**
* 這是最關鍵的函數,當程序中有未捕獲的異常,系統將會自動調用此方法
* @param thread 為出現未捕獲異常的線程
* @param exception 未捕獲的異常,有了此異常,我們就能得到異常信息
*/
@Override
public void uncaughtException(Thread thread, Throwable exception) {
try {
//保存異常信息到sd卡
saveExceptionToSDCard(exception);
//上傳異常信息到服務器
uploadExceptionToServer(exception);
} catch (IOException e) {
e.printStackTrace();
}
// 如果系統提供了默認的異常處理器,就交給系統自己處理,否則就自己結束掉自己
if (mDefaultCrashHandler!=null){
mDefaultCrashHandler.uncaughtException(thread,exception);
}else {
Process.killProcess(Process.myPid());
}
}
// 將異常信息保存到SDCard
private void saveExceptionToSDCard(Throwable ex) throws IOException {
// 如果SD卡不存在或無法使用,則無法寫入異常信息,給與提示
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
if (DEBUG){
Log.w(TAG,"sdcard unmounted , skip save exception"); //sd卡未安裝好,跳出存儲異常
}
return;
}
// 文件存儲路徑
File dir = new File(PATH);
if (!dir.exists()){
dir.mkdirs(); // 這里開始自己犯了一個低級錯誤,寫成了dir.mkdir(),需要注意;
}
// 獲取當前時間
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);
if (!file.exists()){
file.createNewFile();
}
try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
pw.println(time);
savePhoneInfo(pw);
pw.println();
ex.printStackTrace(pw); //輸出異常信息
pw.close();
}catch (PackageManager.NameNotFoundException e){
Log.e(TAG,"save crash info failed");
}
}
// 保存手機的信息
private void savePhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
// APP的版本信息
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("CUP ABI:");
pw.println(Build.CPU_ABI);
}
// 將異常信息上傳到服務器
private void uploadExceptionToServer(Throwable ex){
//Error error = new Error(ex.getMessage());
// 上傳服務器的操作還不清楚,希望有會的指教下
}
}
項目中怎樣使用自己的CrashHandler類呢?只需要在應用application初始化的時候為線程設置CrashHandler即可:
public class MyApplication extends Application {
private static MyApplication sMyApplication;
@Override
public void onCreate() {
super.onCreate();
// 在這里為應用設置異常處理,然后程序才能獲取未處理的異常
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
public static MyApplication getInstance(){
return sMyApplication;
}
}
最后我們模擬一個異常,看下結果:
public class MainActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button)findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
throw new RuntimeException("自己拋出一個異常……");
}
});
}
}
程序運行后,我們點擊button后,程序就因為異常退出。這是我們可以在手機上下載一個ES文件瀏覽器,搜索我們的文件夾名CrashTest——>log:
Paste_Image.png
Paste_Image.png