1.為什麼需要回報Crash報告?
Crash最通俗直覺的感受就是App軟體出現崩潰導緻的閃退等現象,在Android原生态下會出現一個App Force Close的Dialog,但是對于使用者體驗相當不好。Crash的産生是不可避免的,它産生的原因可能來自于Android底層的Bug,或是因為網絡不暢,又或者是手機适配性問題,更嚴重的是代碼品質不過關。Crash的産生是我們最不願意看到的,即使我們花費大量時間進行測試,測試,再測試,bug還是會産生,為了能夠擷取到Crash資訊,正式的軟體中都會有Crash的回報機制,開發人員會根據這些Crash資訊對軟體進行改進。
2.如何擷取這些Crash資訊?
1)首先,先看這樣一個方法
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) { Thread.defaultUncaughtHandler = handler; }
這個方法可以設定系統的預設異常處理器,我們可以利用這個方法解決應用中常見的crash問題。當crash發生的時候,我們可以捕獲到異常資訊,把異常資訊存儲到SD卡中,然後在合适的時機通過網絡将crash資訊上傳到伺服器上,這樣開發人員就可以分析使用者crash的場景進而在後面的版本中修複此類crash。我們還可以在crash發生時,彈出一個通知告訴使用者程式crash了,然後再退出,這樣做比閃退要溫和一點。
2)再看一個Java中的接口——UncaughtExceptionHandler
static interface
Thread.UncaughtExceptionHandler
當 Thread 因未捕獲的異常而突然終止時,調用處理程式的接口。
我們可以實作這個接口,然後在這個接口中的uncaughtException(Thread thread, Throwable ex)方法中實作對Crash資訊的捕獲。
3.思路描述
4.具體實作
1)實作異常處理CrashHandler
package com.example.crash.overall; 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 com.example.crash.utils.AppUtil; import android.content.Context;import android.os.Environment;import android.os.Process;import android.util.Log; public class CrashHandler implements UncaughtExceptionHandler { private static final String TAG = "CrashHandler"; private Context context; private UncaughtExceptionHandler defaultExceptionHandler; private static CrashHandler crashHandler; private CrashHandler() { ; } // 單例模式 public static CrashHandler getInstance() { if (null == crashHandler) { crashHandler = new CrashHandler(); } return crashHandler; } public void init(Context context) { // 系統預設的異常處理器 defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); // 将目前執行個體設為系統預設的異常處理器 Thread.setDefaultUncaughtExceptionHandler(this); // 擷取App資訊時使用者此參數 context = context.getApplicationContext(); } @Override public void uncaughtException(Thread thread, Throwable ex) { // 導出崩潰資訊到日志檔案 dumpExceptionToSDCard(ex); // 列印崩潰資訊到Log日志 ex.printStackTrace(); // 如果系統提供了預設的異常處理器,則交給系統去結束我們的程式,否則就由我們自己結束自己 if (null != defaultExceptionHandler) { defaultExceptionHandler.uncaughtException(thread, ex); } else { Process.killProcess(Process.myPid()); } } // 導出崩潰資訊到日志檔案 private void dumpExceptionToSDCard(Throwable ex) { // 檢測SDCard是否可用--如果不可以在log中給予提醒 if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { Log.w(TAG, "SDCard unmounted!"); return; } // 判斷Crash檔案夾是否存在,如果不存在則建立 File crashDir = new File(Constants.CRASH_FILE_PATH); if (!crashDir.exists()) { crashDir.mkdir(); } // 建立目前崩潰日志檔案 String currentTime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss") .format(new Date(System.currentTimeMillis())); String logFileName = currentTime + ".txt"; File logFile = new File(crashDir, logFileName); BufferedWriter bufferedWriter = null; PrintWriter printWriter = null; try { bufferedWriter = new BufferedWriter(new FileWriter(logFile)); printWriter = new PrintWriter(bufferedWriter); // 崩潰發生時間 printWriter.write(currentTime); // 崩潰手機的系統資訊及其使用者所用的軟體資訊 String phoneInfo = new AppUtil(context).getPhoneInfoToCrash(); printWriter.write(phoneInfo); // 崩潰資訊 ex.printStackTrace(printWriter); printWriter.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (null != bufferedWriter) { try { bufferedWriter.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != printWriter) { printWriter.close(); } } } }
2)擷取手機及App軟體等相關資訊
package com.example.crash.utils; import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException; public class AppUtil { private Context context; public AppUtil(Context context) { this.context = context; } // 擷取App的VersionCode public int getAppVersionCode() { PackageManager packageManager = context.getPackageManager(); PackageInfo packageInfo = null; try { packageInfo = packageManager.getPackageInfo( context.getPackageName(), PackageManager.GET_ACTIVITIES); } catch (NameNotFoundException e) { e.printStackTrace(); } int versionCode = 0; if (null != packageInfo) { versionCode = packageInfo.versionCode; } return versionCode; } // 擷取App的VersionName public String getAppVersionName() { PackageManager packageManager = context.getPackageManager(); PackageInfo packageInfo = null; try { packageInfo = packageManager.getPackageInfo( context.getPackageName(), PackageManager.GET_ACTIVITIES); } catch (NameNotFoundException e) { e.printStackTrace(); } return packageInfo.versionName; } public String getPhoneInfoToCrash() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("PackageName:").append(context.getPackageName()) .append('\n'); stringBuilder.append("VesionCode:").append(getAppVersionCode()) .append('\n'); stringBuilder.append("VersionName:").append(getAppVersionName()) .append('\n'); stringBuilder.append("OS Version:") .append(android.os.Build.VERSION.RELEASE).append('_') .append(android.os.Build.VERSION.SDK_INT).append('\n'); stringBuilder.append("Model:").append(android.os.Build.MODEL) .append('\n'); stringBuilder.append("Manufacturer:") .append(android.os.Build.MANUFACTURER).append('\n'); stringBuilder.append("CPU:").append(android.os.Build.CPU_ABI) .append('\n'); return stringBuilder.toString(); }}
3)為UI線程添加預設異常事件Handler
//Thread類中辨別預設異常事件Handler的成員
private static UncaughtExceptionHandler defaultUncaughtHandler;
這裡涉及到在哪裡添加的問題,從源碼中注意到,這個defaultUncaughtHandler是Thread類中一個靜态的成員,是以,按道理,我們為任意一個線程設定異常處理,所有的線程都應該能共用這個異常處理器,這個是我的猜測,沒有經過驗證,不過沒關系,有一個觀點是大家都認可的:就是為主線程也就是ui線程添加異常程式器。為了在ui線程中添加異常處理Handler,我們推薦大家在Application中添加而不是在Activity中添加。Application辨別着整個應用,在Android聲明周期中是第一個啟動的,早于任何的Activity、Service等。
package com.example.crash.overall; import android.app.Application; public class App extends Application { @Override public void onCreate() { super.onCreate(); // 開啟崩潰日志,這樣我們的程式就能捕獲未處理的異常 CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(getApplicationContext()); } }
4.測試代碼
package com.example.crash.activity; import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.View; import com.example.crash.R; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } public void testCrash(View view) { if (1 > 0) { throw new RuntimeException("Test Exception!!!"); } } }
5.備注
日志的上傳屬于網絡相關的内容,暫且不做專門介紹
參考:
1.http://blog.csdn.net/singwhatiwanna/article/details/17289479
2.http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html
3.http://blog.csdn.net/wangjia55/article/details/14000833
---------------------
作者:小屁孩2013
來源:CSDN
原文:https://blog.csdn.net/u010119170/article/details/38236991
版權聲明:本文為部落客原創文章,轉載請附上博文連結!