標籤:crash application thread.uncaughtexcep 日誌
轉載請註明出處:
http://blog.csdn.net/allen315410/article/details/41444053
在實際項目開發中,會出現很多的異常直接導致程式crash掉,在開發中我們可以通過logcat查看錯誤記錄檔,Debug出現的異常,讓程式安全的運行,但是在開發中有些異常隱藏的比較深,直到項目發布後,由於各種原因,譬如android裝置不一致等等,android版本不同,實際上我們在測試的時候不可能在市場上所有的Android裝置上都做了測試,當使用者安裝使用時被暴露出來,導致程式直接crash掉,這顯然對於使用者是不OK的!這些在使用者裝置上導致crash的異常我們是不知道的,要想知道這些異常出現的一些資訊,我們還是得自己通過程式捕獲到異常,並且將其記錄下來(本地儲存或者上傳伺服器),方便項目維護。
先來看一下,我自己“故意”定義出來的一個異常,在MainActivity,java中:
package com.example.crash;import android.os.Bundle;import android.app.Activity;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);int i = 1;System.out.println(i/0);}} 以上程式報出一個數學運算的除0異常,顯然程式被“崩潰”,不能繼續執行的。看一下運行效果:
運行結果如所示,這種直接崩潰的效果對於使用者來說是很不OK的,使用者不知道發生了什麼,程式就停止了,會讓使用者對程式有種不想繼續使用的想法。 對於程式中未捕獲的異常,我們可以做哪些操作呢!我們需要的是軟體有一個全域的異常捕獲器,當出現一個我們沒有發現的異常時,捕獲這個異常,並且將異常資訊記錄下來,上傳到伺服器公開發這分析出現異常的具體原因,這是一種最佳實務,那麼我們接下來就必須要熟悉兩個類別,一個是android提供的Application,另一個是Java提供的Thread.UncaughtExceptionHandler。
Application:這是android程式管理全域狀態的類,Application在程式啟動的時候首先被建立出來,它被用來統一管理activity、service、broadcastreceiver、contentprovider四大組件以及其他android元素,這裡可以開啟android工程下的Mainifest.xml檔案查看一下。我們除了使用android預設的Application來處理常式,也可以自訂一個Application處理一些需要在全域狀態下控製程序的操作,例如本文講到的處理常式未知異常時,這是一種最佳實務。
Thread.UncaughtExceptionHandler:關於這個概念的解釋,我在JDK1.6的文檔中找到一些科學的解釋。
當 Thread 因未捕獲的異常而突然終止時,調用處理常式的介面。
當某一線程因未捕獲的異常而即將終止時,JAVA 虛擬機器將使用 Thread.getUncaughtExceptionHandler() 查詢該線程以獲得其 UncaughtExceptionHandler 的線程,並調用處理常式的 uncaughtException 方法,將線程和異常作為參數傳遞。如果某一線程沒有明確設定其 UncaughtExceptionHandler,則將它的 ThreadGroup 對象作為其 UncaughtExceptionHandler。如果 ThreadGroup 對象對處理異常沒有什麼特殊要求,那麼它可以將調用轉寄給預設的未捕獲例外處理常式。
Thread.UncaughtExceptionHandler是一個介面,它提供如下的方法,讓我們自訂處理常式。
void uncaughtException(Thread t,Throwable e)
當給定線程因給定的未捕獲異常而終止時,調用該方法。JAVA 虛擬機器將忽略該方法拋出的任何異常。參數:t - 線程 e - 異常
一句話,線程未捕獲異常處理器,用來處理未捕獲異常。如果程式出現了未捕獲異常,預設會彈出系統中強制關閉對話方塊。我們需要實現此介面,並註冊為程式中預設未捕獲異常處理。這樣當未捕獲異常發生時,就可以做一些個人化的異常處理操作。所以接下來,我們要做的就是自訂一個CrashHandler類去實現Thread.UncaughtExceptionHandler,並且在實現的方法中做一些相關的操作。
package com.example.crash;import java.io.File;import java.io.FileOutputStream;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.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;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;public class CrashHandler implements UncaughtExceptionHandler {public static final String TAG = "CrashHandler";// 系統預設的UncaughtException處理類private Thread.UncaughtExceptionHandler mDefaultHandler;// CrashHandler執行個體private static CrashHandler INSTANCE = new CrashHandler();// 程式的Context對象private Context mContext;// 用來存放裝置資訊和異常資訊private Map<String, String> infos = new HashMap<String, String>();// 用于格式化日期,作為記錄檔名的一部分private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");/** 保證只有一個CrashHandler執行個體 */private CrashHandler() {}/** 擷取CrashHandler執行個體 ,單例模式 */public static CrashHandler getInstance() {return INSTANCE;}/** * 初始化 * * @param context */public void init(Context context) {mContext = context;// 擷取系統預設的UncaughtException處理器mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 設定該CrashHandler為程式的預設處理器Thread.setDefaultUncaughtExceptionHandler(this);}/** * 當UncaughtException發生時會轉入該函數來處理 */@Overridepublic void uncaughtException(Thread thread, Throwable ex) {if (!handleException(ex) && mDefaultHandler != null) {// 如果使用者沒有處理則讓系統預設的異常處理器來處理mDefaultHandler.uncaughtException(thread, ex);} else {try {Thread.sleep(3000);} catch (InterruptedException e) {Log.e(TAG, "error : ", e);}// 退出程式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() {@Overridepublic void run() {Looper.prepare();Toast.makeText(mContext, "很抱歉,程式出現異常,即將退出.", Toast.LENGTH_LONG).show();Looper.loop();}}.start();// 收集裝置參數資訊collectDeviceInfo(mContext);// 儲存記錄檔saveCrashInfo2File(ex);return true;}/** * 收集裝置參數資訊 * * @param ctx */public void collectDeviceInfo(Context ctx) {try {PackageManager pm = ctx.getPackageManager();PackageInfo pi = pm.getPackageInfo(ctx.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);}} catch (NameNotFoundException e) {Log.e(TAG, "an error occured when collect package info", e);}Field[] fields = Build.class.getDeclaredFields();for (Field field : fields) {try {field.setAccessible(true);infos.put(field.getName(), field.get(null).toString());Log.d(TAG, field.getName() + " : " + field.get(null));} catch (Exception e) {Log.e(TAG, "an error occured when collect crash info", e);}}}/** * 儲存錯誤資訊到檔案中 * * @param ex * @return 返迴文件名稱,便於將檔案傳送到伺服器 */private String saveCrashInfo2File(Throwable ex) {StringBuffer sb = new StringBuffer();for (Map.Entry<String, String> entry : infos.entrySet()) {String key = entry.getKey();String value = entry.getValue();sb.append(key + "=" + value + "\n");}Writer writer = new StringWriter();PrintWriter printWriter = new PrintWriter(writer);ex.printStackTrace(printWriter);Throwable cause = ex.getCause();while (cause != null) {cause.printStackTrace(printWriter);cause = cause.getCause();}printWriter.close();String result = writer.toString();sb.append(result);try {long timestamp = System.currentTimeMillis();String time = formatter.format(new Date());String fileName = "crash-" + time + "-" + timestamp + ".log";if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {String path = "/sdcard/crash/";File dir = new File(path);if (!dir.exists()) {dir.mkdirs();}FileOutputStream fos = new FileOutputStream(path + fileName);fos.write(sb.toString().getBytes());fos.close();}return fileName;} catch (Exception e) {Log.e(TAG, "an error occured while writing file...", e);}return null;}}
完成了這個CrashHandler類之後,還需要自訂一個全域Application來啟動管理異常收集,以下是自訂的Application類,很簡單:
package com.example.crash;import android.app.Application;public class CrashApplication extends Application {@Overridepublic void onCreate() {// TODO Auto-generated method stubsuper.onCreate();CrashHandler crashHandler = CrashHandler.getInstance();crashHandler.init(getApplicationContext());}}最後,為了讓程式在啟動時使用我們自訂的Application,必須在Mainifest.xml的Application節點上,聲明出我們自訂的Application:
<application android:name=".CrashApplication" ...> ..... </application>
配置SDCard寫檔案的許可權:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
運行以下程式:
在SD卡中找到crash檔案夾,開啟檔案夾:
到處這個log日誌,用notepad開啟,查看內容如下:
TIME=1385535270000......java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.crash/com.example.crash.MainActivity}: java.lang.ArithmeticException: divide by zero......Caused by: java.lang.ArithmeticException: divide by zeroat com.example.crash.MainActivity.onCreate(MainActivity.java:13)at android.app.Activity.performCreate(Activity.java:5243)at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)... 11 morejava.lang.ArithmeticException: divide by zeroat com.example.crash.MainActivity.onCreate(MainActivity.java:13)......好了,程式中未捕獲的異常被及時捕捉到,儲存在SD卡中,並且給使用者良好的提示資訊,被沒有一下子crash掉,通過SD卡中的錯誤記錄檔,我們可以很快定義到錯誤的根源,方便我們及時對程式進行修正。當然了,這裡我由於做的是個Demo,所以相關錯誤記錄檔僅僅儲存在了SD卡上,其實好的做法是將錯誤記錄檔上傳到伺服器中,以便我們收集來自四面八方使用者的日誌,為程式進行更新迭代升級。
註:該文是我學習筆記,裡面會有一些Bug。程式僅作為參考執行個體,不能直接使用到真實項目中,請諒解!
參考資料:http://www.cjsdn.net/Doc/JDK60/java/lang/Thread.UncaughtExceptionHandler.html
http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html
http://blog.csdn.net/liuhe688/article/details/6584143
Android程式crash處理