1. 程式人生 > >android 應用程式異常崩潰捕捉

android 應用程式異常崩潰捕捉

  1. import java.io.BufferedReader;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStreamReader;  
  8. import java.io.PrintWriter;  
  9. import java.io.StringWriter;  
  10. import java.io.Writer;  
  11. import java.lang.Thread.UncaughtExceptionHandler;  
  12. import java.lang.reflect.Field;  
  13. import java.text.DateFormat;  
  14. import java.text.SimpleDateFormat;  
  15. import java.util.Date;  
  16. import java.util.HashMap;  
  17. import java.util.Map;  
  18. import android.content.Context;  
  19. import android.content.pm.PackageInfo;  
  20. import
     android.content.pm.PackageManager;  
  21. import android.content.pm.PackageManager.NameNotFoundException;  
  22. import android.os.Build;  
  23. import android.os.Environment;  
  24. import android.os.Looper;  
  25. import android.util.Log;  
  26. import android.widget.Toast;  
  27. /**   
  28.  * UncaughtException處理類,當程式發生Uncaught異常的時候,有該類來接管程式,並記錄傳送錯誤報告. 
     
  29.  *  
  30.  *  需要在Application中註冊,為了要在程式啟動器就監控整個程式。 
  31.  */
  32. publicclass CrashHandler implements UncaughtExceptionHandler {      
  33.     publicstaticfinal String TAG = "CrashHandler";      
  34.     //系統預設的UncaughtException處理類     
  35.     private Thread.UncaughtExceptionHandler mDefaultHandler;      
  36.     //CrashHandler例項    
  37.     privatestatic CrashHandler instance;  
  38.    //程式的Context物件    
  39.     private Context mContext;      
  40.     //用來儲存裝置資訊和異常資訊    
  41.     private Map<String, String> infos = new HashMap<String, String>();      
  42.     //用於格式化日期,作為日誌檔名的一部分    
  43.     private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");      
  44.     /** 保證只有一個CrashHandler例項 */
  45.     private CrashHandler() {}      
  46.     /** 獲取CrashHandler例項 ,單例模式 */
  47.     publicstatic CrashHandler getInstance() {      
  48.         if(instance == null)  
  49.             instance = new CrashHandler();     
  50.         return instance;      
  51.     }      
  52.     /**   
  53.      * 初始化   
  54.      */
  55.     publicvoid init(Context context) {      
  56.         mContext = context;      
  57.         //獲取系統預設的UncaughtException處理器    
  58.         mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();      
  59.         //設定該CrashHandler為程式的預設處理器    
  60.         Thread.setDefaultUncaughtExceptionHandler(this);      
  61.     }      
  62.     /**   
  63.      * 當UncaughtException發生時會轉入該函式來處理   
  64.      */
  65.     @Override
  66.     publicvoid uncaughtException(Thread thread, Throwable ex) {      
  67.         if (!handleException(ex) && mDefaultHandler != null) {      
  68.             //如果使用者沒有處理則讓系統預設的異常處理器來處理    
  69.             mDefaultHandler.uncaughtException(thread, ex);      
  70.         } else {      
  71.             try {      
  72.                 Thread.sleep(3000);      
  73.             } catch (InterruptedException e) {      
  74.                 Log.e(TAG, "error : ", e);      
  75.             }      
  76.             //退出程式    
  77.             android.os.Process.killProcess(android.os.Process.myPid());      
  78.             System.exit(1);      
  79.         }      
  80.     }      
  81.     /**   
  82.      * 自定義錯誤處理,收集錯誤資訊 傳送錯誤報告等操作均在此完成.   
  83.      *    
  84.      * @param ex   
  85.      * @return true:如果處理了該異常資訊;否則返回false.   
  86.      */
  87.     privateboolean handleException(Throwable ex) {      
  88.         if (ex == null) {      
  89.             returnfalse;      
  90.         }      
  91.         //收集裝置引數資訊     
  92.         collectDeviceInfo(mContext);      
  93.         //使用Toast來顯示異常資訊    
  94.         new Thread() {      
  95.             @Override
  96.             publicvoid run() {      
  97.                 Looper.prepare();      
  98.                 Toast.makeText(mContext, "很抱歉,程式出現異常,即將退出.", Toast.LENGTH_SHORT).show();      
  99.                 Looper.loop();      
  100.             }      
  101.         }.start();      
  102.         //儲存日誌檔案     
  103.         saveCatchInfo2File(ex);    
  104.         returntrue;      
  105.     }      
  106.     /**   
  107.      * 收集裝置引數資訊   
  108.      * @param ctx   
  109.      */
  110.     publicvoid collectDeviceInfo(Context ctx) {      
  111.         try {      
  112.             PackageManager pm = ctx.getPackageManager();      
  113.             PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);      
  114.             if (pi != null) {      
  115.                 String versionName = pi.versionName == null ? "null" : pi.versionName;      
  116.                 String versionCode = pi.versionCode + "";      
  117.                 infos.put("versionName", versionName);      
  118.                 infos.put("versionCode", versionCode);      
  119.             }      
  120.         } catch (NameNotFoundException e) {      
  121.             Log.e(TAG, "an error occured when collect package info", e);      
  122.         }      
  123.         Field[] fields = Build.class.getDeclaredFields();      
  124.         for (Field field : fields) {      
  125.             try {      
  126.                 field.setAccessible(true);      
  127.                 infos.put(field.getName(), field.get(null).toString());      
  128.                 Log.d(TAG, field.getName() + " : " + field.get(null));      
  129.             } catch (Exception e) {      
  130.                 Log.e(TAG, "an error occured when collect crash info", e);      
  131.             }      
  132.         }      
  133.     }      
  134.     /**   
  135.      * 儲存錯誤資訊到檔案中   
  136.      *    
  137.      * @param ex   
  138.      * @return  返回檔名稱,便於將檔案傳送到伺服器   
  139.      */
  140.     private String saveCatchInfo2File(Throwable ex) {      
  141.         StringBuffer sb = new StringBuffer();      
  142.         for (Map.Entry<String, String> entry : infos.entrySet()) {      
  143.             String key = entry.getKey();      
  144.             String value = entry.getValue();      
  145.             sb.append(key + "=" + value + "\n");      
  146.         }      
  147.         Writer writer = new StringWriter();      
  148.         PrintWriter printWriter = new PrintWriter(writer);      
  149.         ex.printStackTrace(printWriter);      
  150.         Throwable cause = ex.getCause();      
  151.         while (cause != null) {      
  152.             cause.printStackTrace(printWriter);      
  153.             cause = cause.getCause();      
  154.         }      
  155.         printWriter.close();      
  156.         String result = writer.toString();      
  157.         sb.append(result);      
  158.         try {      
  159.             long timestamp = System.currentTimeMillis();      
  160.             String time = formatter.format(new Date());      
  161.             String fileName = "crash-" + time + "-" + timestamp + ".log";      
  162.             if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {      
  163.                 String path = "/mnt/sdcard/crash/";      
  164.                 File dir = new File(path);      
  165.                 if (!dir.exists()) {      
  166.                     dir.mkdirs();      
  167.                 }      
  168.                 FileOutputStream fos = new FileOutputStream(path + fileName);      
  169.                 fos.write(sb.toString().getBytes());    
  170.                 //傳送給開發人員
  171.                 sendCrashLog2PM(path+fileName);  
  172.                 fos.close();      
  173.             }      
  174.             return fileName;      
  175.         } catch (Exception e) {      
  176.             Log.e(TAG, "an error occured while writing file...", e);      
  177.         }      
  178.         returnnull;      
  179.     }      
  180.     /** 
  181.      * 將捕獲的導致崩潰的錯誤資訊傳送給開發人員 
  182.      *  
  183.      * 目前只將log日誌儲存在sdcard 和輸出到LogCat中,並未傳送給後臺。 
  184.      */
  185.     privatevoid sendCrashLog2PM(String fileName){  
  186.         if(!new File(fileName).exists()){  
  187.             Toast.makeText(mContext, "日誌檔案不存在!", Toast.LENGTH_SHORT).show();  
  188.             return;  
  189.         }  
  190.         FileInputStream fis = null;  
  191.         BufferedReader reader = null;  
  192.         String s = null;  
  193.         try {  
  194.             fis = new FileInputStream(fileName);  
  195.             reader = new BufferedReader(new InputStreamReader(fis, "GBK"));  
  196.             while(true){  
  197.                 s = reader.readLine();  
  198.                 if(s == nullbreak;  
  199.                 //由於目前尚未確定以何種方式傳送,所以先打出log日誌。
  200.                 Log.i("info", s.toString());  
  201.             }  
  202.         } catch (FileNotFoundException e) {  
  203.             e.printStackTrace();  
  204.         } catch (IOException e) {  
  205.             e.printStackTrace();  
  206.         }finally{   // 關閉流
  207.             try {  
  208.                 reader.close();  
  209.                 fis.close();  
  210.             } catch (IOException e) {  
  211.                 e.printStackTrace();  
  212.             }  
  213.         }  
  214.     }  
  215. }      

針對異常的捕捉要進行全域性監控整個專案,所以要將其在Application中註冊(也就是初始化):

[java] view plaincopyprint?
  1. import android.app.Application;  
  2. publicclass CrashApplication extends Application {  
  3.     @Override
  4.     publicvoid onCreate() {  
  5.         super.onCreate();  
  6.         CrashHandler catchHandler = CrashHandler.getInstance();  
  7.         catchHandler.init(getApplicationContext());  
  8.     }  
  9. }  

現在模擬一個空指標異常: [java] view plaincopyprint?
  1. import android.app.Activity;  
  2. import android.os.Bundle;  
  3. publicclass CatchExceptionLogActivity extends Activity {  
  4.     /** Called when the activity is first created. */
  5.     private String s;  
  6.     @Override
  7.     publicvoid onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.         System.out.println(s.equals("hello"));  // s沒有進行賦值,所以會出現NullPointException異常
  11.     }  
  12. }  

別忘了在配置檔案中對Application進行註冊: [html] view plaincopyprint?
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <manifestxmlns:android="http://schemas.android.com/apk/res/android"
  3.     package="com.forms.catchlog"
  4.     android:versionCode="1"
  5.     android:versionName="1.0">
  6.     <uses-sdkandroid:minSdkVersion="8"/>
  7.     <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  8.     <application
  9.         android:icon="@drawable/ic_launcher"
  10.         android:label="@string/app_name"
  11.         <spanstyle="color:#ff0000;">android:name=".CrashApplication"></span>
  12.         <activity
  13.             android:label="@string/app_name"
  14.             android:name=".CatchExceptionLogActivity">
  15.             <intent-filter>
  16.                 <actionandroid:name="android.intent.action.MAIN"/>
  17.                 <categoryandroid:name="android.intent.category.LAUNCHER"/>
  18.             </intent-filter>
  19.         </activity>
  20.     </application>
  21. </manifest>