Android抓取崩潰日誌
阿新 • • 發佈:2019-02-12
在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>