獲取Android崩潰crash資訊並寫入日誌
阿新 • • 發佈:2019-01-06
Android崩潰是開發中不可避免的一件事,考慮不夠周全的程式碼、糟糕的網路環境、讓人頭疼的碎片化問題都可能導致crash,線上版本crash嚴重影響使用者體驗,所以crash的捕獲和收集對我們開發人員很重要。
〇、Exception的分類及捕獲
Java的異常可以分為兩類:
- UnChecked Exception
- Checked Exception
所有RuntimeException類及其子類的例項被稱為Runtime異常,即UnChecked Exception,
不是RuntimeException類及其子類的異常例項則被稱為Checked Exception。Checked異常又稱為編譯時異常,即在編譯階段被處理的異常。編譯器會強制程式處理所有的Checked異常,也就是用try…catch顯式的捕獲並處理,因為Java認為這類異常都是可以被處理(修復)的。在Java API文件中,方法說明時,都會新增是否throw某個exception,這個exception就是Checked異常。如果沒有try…catch這個異常,則編譯出錯,錯誤提示類似於“Unhandled exception type xxxxx ...”and so on .
一、實現Thread.UncaughtExceptionHandler
UnChecked異常發生時,由於沒有相應的try…catch處理該異常物件,所以Java執行環境將會終止,程式將退出,也就是我們所說的Crash。Java API提供了一個全域性異常捕獲處理器,Android應用在Java層捕獲Crash依賴的就是Thread.UncaughtExceptionHandler處理器介面,通常我們只需實現這個介面,並重寫其中的uncaughtException方法,在該方法中可以讀取Crash的堆疊資訊。
public class CrashManager implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler mDefaultHandler;
private Map<String, String> infos;
private MyApplication application;
public CrashManager(MyApplication application){
//獲取系統預設的UncaughtExceptionHandler
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
this .application = application;
}
private boolean handleException(final Throwable exc){
if (exc == null) {
return false;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Log.i("Urmytch","崩潰正在寫入日誌");
flushBufferedUrlsAndReturn();
//處理崩潰
collectDeviceAndUserInfo(application);
writeCrash(exc);
Looper.loop();
}
}).start();
return true;
}
/**
* 把未存檔的url和返回資料寫入日誌檔案
*/
private void flushBufferedUrlsAndReturn(){
//TODO 可以在請求網路時把url和返回xml或json資料快取在佇列中,崩潰時先寫入以便查明原因
}
/**
* 採集裝置和使用者資訊
* @param context 上下文
*/
private void collectDeviceAndUserInfo(Context context){
PackageManager pm = context.getPackageManager();
infos = new HashMap<String, String>();
try {
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionName = pi.versionName == null?"null":pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("versionName",versionName);
infos.put("versionCode",versionCode);
infos.put("crashTime",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Urmytch",e.getMessage());
}
Field[] fields = Build.class.getDeclaredFields();
try {
for (Field field : fields) {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
}
} catch (IllegalAccessException e) {
Log.e("Urmytch",e.getMessage());
}
}
/**
* 採集崩潰原因
* @param exc 異常
*/
private void writeCrash(Throwable exc){
StringBuffer sb = new StringBuffer();
sb.append("------------------crash----------------------");
sb.append("\r\n");
for (Map.Entry<String,String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key+"="+value+"\r\n");
}
Writer writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
exc.printStackTrace(pw);
Throwable excCause = exc.getCause();
while (excCause != null) {
excCause.printStackTrace(pw);
excCause = excCause.getCause();
}
pw.close();
String result = writer.toString();
sb.append(result);
sb.append("\r\n");
sb.append("-------------------end-----------------------");
sb.append("\r\n");
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
String sdcardPath = Environment.getExternalStorageDirectory().getPath();
String filePath = sdcardPath + "//Urmytch/crash/";
writeLog(sb.toString(), filePath);
}
}
/**
*
* @param log 檔案內容
* @param name 檔案路徑
* @return 返回寫入的檔案路徑
* 寫入Log資訊的方法,寫入到SD卡里面
*/
private String writeLog(String log, String name)
{
String filename = name + "mycrash"+ ".log";
File file =new File(filename);
if(!file.getParentFile().exists()){
Log.i("Urmytch","新建檔案");
file.getParentFile().mkdirs();
}
if (file != null && file.exists() && file.length() + log.length() >= 64 * 1024) {
//控制日誌檔案大小
file.delete();
}
try
{
file.createNewFile();
FileWriter fw=new FileWriter(file,true);
BufferedWriter bw = new BufferedWriter(fw);
//寫入相關Log到檔案
bw.write(log);
bw.newLine();
bw.close();
fw.close();
return filename;
}
catch(IOException e)
{
Log.w("Urmytch",e.getMessage());
return null;
}
}
@Override
public void uncaughtException(Thread thread, Throwable exc) {
if(!handleException(exc) && mDefaultHandler != null){
//如果使用者沒有處理則讓系統預設的異常處理器來處理
mDefaultHandler.uncaughtException(thread, exc);
}else{
try{
Thread.sleep(2000);
}catch (InterruptedException e){
Log.w("Urmytch",e.getMessage());
}
Intent intent = new Intent(application.getApplicationContext(), MainActivity.class);
PendingIntent restartIntent = PendingIntent.getActivity(
application.getApplicationContext(), 0, intent,
0);
//退出程式
AlarmManager mgr = (AlarmManager)application.getSystemService(Context.ALARM_SERVICE);
//1秒後重啟應用
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,
restartIntent);
android.os.Process.killProcess(android.os.Process.myPid());
}
}
}
二、在Application中註冊
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashManager crashHandler = new CrashManager(this);
Thread.setDefaultUncaughtExceptionHandler(crashHandler);
}
}
三、看下效果
點選button會直接crash掉
四、與友盟錯誤統計是否會存在衝突
- 友盟是這樣說的:
開發者自己使用UncaughtExceptionHandler在程式中添加了全域性的異常捕捉時,如果是開發者的先註冊友盟的後註冊,友盟不會覆蓋開發者的,但是如果友盟先註冊,開發者註冊的可能會覆蓋友盟的。如果您不需要錯誤統計,可以通過MobclickAgent.setCatchUncaughtExceptions(false);關閉,如果開發者需要自己上傳錯誤,友盟也提供相應的方法:public static void reportError(Context context, String error) //或public static void reportError(Context context, Throwable e)。
- 經驗證本demo中並未衝突,本地捕獲並記錄crash資訊(重啟應用後友盟才會提交崩潰資料)後友盟依然記錄到了崩潰