1. 程式人生 > >Android抓取崩潰日誌

Android抓取崩潰日誌

在Android裡如果執行時出現異常,而開發者又沒有手動去catch的話,程式就會崩潰;
這裡寫圖片描述

在IDE上進行除錯的時候,錯誤資訊會第一時間顯示在logcat裡,可以很方便的檢視崩潰資訊,找出錯誤;
但是如果程式在非除錯階段崩潰的話,logcat就沒法為我們顯示崩潰日誌了。

所以當程式出現未捕獲的異常導致崩潰時,我們可以將崩潰日誌寫到sd卡中,方便排查。

這裡簡單講解一下步驟:

1.自定義類實現UncaughtExceptionHandler介面

2.重寫uncaughtException方法

3.在初始化時使用自定義的CrashCatchHandler替換掉系統預設的UncaughtException處理器

4.在uncaughtException方法中獲得錯誤資訊,並將它儲存到sd卡中

5.自定義Application繼承Application,在onCreate方法中初始化自定義的CrashCatchHandler

6.更改AndroidManifest中Application的name欄位為自定義的Application

注:這裡其實不是必須重寫Application,在任何時候我們都可以設定預設的UncaughtException處理器;但是為了能夠將整個應用程式的崩潰日誌都擷取下來,最好是在Application中就更改好。

預覽

提示資訊:

這裡寫圖片描述

sd卡中的crash檔案:

這裡寫圖片描述

使用NotePad++開啟.log檔案:

這裡寫圖片描述

下面直接貼出程式碼:

許可權:

<!--讀寫sd卡許可權-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

CrashCatchHandler自定義UncaughtException處理器

package com.waka.workspace.crashcatchdemo;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import
java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import android.annotation.SuppressLint; 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.Looper; import android.util.Log; import android.widget.Toast; /** * 崩潰日誌抓取 * <p> * UncaughtException處理類,當程式發生Uncaught異常的時候,有該類來接管程式,並記錄傳送錯誤報告 * <p> * 感謝 liuhe688 大神的無私分享 * <p> * 這裡是部落格地址:http://blog.csdn.net/liuhe688/article/details/6584143# * * @author liuhe688 * @一些改動 waka */ public class CrashCatchHandler implements UncaughtExceptionHandler { public static final String TAG = "CrashHandler"; private static final CrashCatchHandler INSTANCE = new CrashCatchHandler();// 單例模式 private Context context; private UncaughtExceptionHandler defaultHandler;// 系統預設的UncaughtException處理類 private Map<String, String> infosMap = new HashMap<String, String>(); // 用來儲存裝置資訊和異常資訊 /** * 私有構造方法,保證只有一個CrashHandler例項 */ private CrashCatchHandler() { } /** * 獲取CrashHandler,單例模式 * * @return */ public static CrashCatchHandler getInstance() { return INSTANCE; } /** * 初始化 * * @param context */ public void init(Context context) { this.context = context; defaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 獲取系統預設的UncaughtException處理器 Thread.setDefaultUncaughtExceptionHandler(this);// 設定當前CrashHandler為程式的預設處理器 } /** * 當UncaughtException發生時會轉入該函式來處理 */ @Override public void uncaughtException(Thread thread, Throwable ex) { if (!handleException(ex) && defaultHandler != null) { // 如果使用者沒有處理則讓系統預設的異常處理器來處理 defaultHandler.uncaughtException(thread, ex); } else { try { Thread.sleep(3000); } catch (InterruptedException e) { Log.e(TAG, "exception : ", e); e.printStackTrace(); } // 殺死程序 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); } } /** * 自定義錯誤處理,收集錯誤資訊 傳送錯誤報告等操作均在此完成. * * @param ex * @return 如果處理了該異常資訊, 返回true;否則返回false. */ private boolean handleException(Throwable ex) { if (ex == null) { return false; } // 使用Toast顯示異常資訊 new Thread() { public void run() { Looper.prepare(); Toast.makeText(context, "程式出現未捕獲的異常,即將退出!", Toast.LENGTH_SHORT).show(); Looper.loop(); } }.start(); collectDeviceInfo(context);// 收集裝置引數資訊 saveCrashInfoToFile(ex);// 儲存日誌檔案 return true; } /** * 收集裝置資訊 * * @param context */ public void collectDeviceInfo(Context context) { // 使用包管理器獲取資訊 PackageManager pm = context.getPackageManager(); try { PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); if (pi != null) { // TODO 在這裡得到包的資訊 String versionName = pi.versionName == null ? "" : pi.versionName;// 版本名;若versionName==null,則="null";否則=versionName String versionCode = pi.versionCode + "";// 版本號 infosMap.put("versionName", versionName); infosMap.put("versionCode", versionCode); } } catch (NameNotFoundException e) { Log.e(TAG, "an NameNotFoundException occured when collect package info"); e.printStackTrace(); } // 使用反射獲取獲取系統的硬體資訊 Field[] fields = Build.class.getDeclaredFields();// 獲得某個類的所有申明的欄位,即包括public、private和proteced, for (Field field : fields) { field.setAccessible(true);// 暴力反射 ,獲取私有的資訊;類中的成員變數為private,故必須進行此操作 try { infosMap.put(field.getName(), field.get(null).toString()); Log.d(TAG, field.getName() + " : " + field.get(null)); } catch (IllegalArgumentException e) { Log.e(TAG, "an IllegalArgumentException occured when collect reflect field info", e); e.printStackTrace(); } catch (IllegalAccessException e) { Log.e(TAG, "an IllegalAccessException occured when collect reflect field info", e); e.printStackTrace(); } } } /** * 儲存錯誤資訊到檔案中 * * @param ex * @return 返回檔名稱 */ @SuppressLint("CommitPrefEdits") private String saveCrashInfoToFile(Throwable ex) { // 字串流 StringBuffer stringBuffer = new StringBuffer(); // 獲得裝置資訊 for (Map.Entry<String, String> entry : infosMap.entrySet()) {// 遍歷map中的值 String key = entry.getKey(); String value = entry.getValue(); stringBuffer.append(key + "=" + value + "\n"); } // 獲得錯誤資訊 Writer writer = new StringWriter();// 這個writer下面還會用到,所以需要它的例項 PrintWriter printWriter = new PrintWriter(writer);// 輸出錯誤棧資訊需要用到PrintWriter ex.printStackTrace(printWriter); Throwable cause = ex.getCause(); while (cause != null) {// 迴圈,把所有的cause都輸出到printWriter中 cause.printStackTrace(printWriter); cause = ex.getCause(); } printWriter.close(); String result = writer.toString(); stringBuffer.append(result); // 寫入檔案 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US); String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath(); String crashFileName = rootPath + "/crash_" + simpleDateFormat.format(new Date()) + ".log"; /*//因為是sd卡根目錄,所以就需要建立父檔案夾了 File file = new File(rootPath); if (!file.exists()) { file.mkdirs();// 如果不存在,則建立所有的父資料夾 }*/ try { FileOutputStream fos = new FileOutputStream(crashFileName); fos.write(stringBuffer.toString().getBytes()); fos.close(); //TODO 在這裡可以將檔名寫入sharedPreferences中,方便下一次開啟程式時對錯誤日誌進行操作 /*SharedPreferences.Editor editor = mContext .getSharedPreferences("waka").edit(); editor.putString("lastCrashFileName", crashFileName); editor.commit();*/ return crashFileName; } catch (FileNotFoundException e) { Log.e(TAG, "an FileNotFoundException occured when write crashfile to sdcard", e); e.printStackTrace(); } catch (IOException e) { Log.e(TAG, "an IOException occured when write crashfile to sdcard", e); e.printStackTrace(); } return null; } }

自定義Application:

public class CrashCatchApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        CrashCatchHandler crashCatchHandler = CrashCatchHandler.getInstance();//獲得單例
        crashCatchHandler.init(getApplicationContext());//初始化,傳入context
    }
}

更改AndroidManifest.xml

    <!--使用自定義的Application-->
    <application
        android:name=".CrashCatchApplication"
    </application>