在日常開發的過程中應該不可避免的會發生 crash,無論你的程序寫的多么完美,都不可能完全避免 crash 的發生,可能是由于 Android 底層的 bug,也可能是由于不充分的機型適配或者是糟糕的網絡狀況。當 crash 發生時,系統就會kill掉正在執行的程序,現象就是閃退,或者提醒用戶程序已經停止運行,這對用戶來說是很不友好的,也是我們不愿意看到的,更早的是當用戶發生 crash,我們開發者卻無法得知程序為何 crash,即便我們想去解決這個 bug,但是由于無法知道用戶當時的 crash 信息,所以往往也無能為力,幸運的是,Andorid 提供了處理這類問題的方法,接下來我們就來一起看看到底 Android 給我們提供了什么方法來解決這個棘手的問題
一、Thread 類中的 setDefaultUncaughtExceptionHandler
/**  * Sets the default uncaught exception handler. This handler is invoked in  * case any Thread dies due to an unhandled exception.  *  * @param handler  *      The handler to set or null.  */  public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {    Thread.defaultUncaughtHandler = handler;  }  這個方法其實就可以解決我們應用程序的 crash 問題,設置系統默認異常處理器,當系統發生crash 時,系統就會回調 UncaughtExceptionHandler 的 uncaughtException 方法,在 uncaughtException 方法中就可以獲取到異常信息,可以選擇把異常信息存儲下來,存儲方式大家可以自己選擇,然后在合適的時候通過網絡將 crash 信息上傳到服務器上,這樣我們開發人員就可以分析用戶 crash 的場景從而在后面的版本中進行修復,我們還可以在發生 crash 發生時彈出一個對話框,告訴用戶程序 crash 了,然后再退出
二、實現自己的異常捕獲類
1)建立異常 Handler,命名為 CrashHandler,代碼如下
/**  * 異常捕獲類  * Created by qiudengjiao on 2017/9/29.  */  public class CrashHandler implements Thread.UncaughtExceptionHandler {    private static final String TAG = "CrashHandler";   private static final boolean DEBUG = true;    private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/ryg_test/log/";   private static final String FILE_NAME = "crash";    //log文件的后綴名   private static final String FILE_NAME_SUFFIX = ".trace";    private static CrashHandler sInstance = new CrashHandler();    //系統默認的異常處理(默認情況下,系統會終止當前的異常程序)   private Thread.UncaughtExceptionHandler mDefaultCrashHandler;    private Context mContext;    //構造方法私有,防止外部構造多個實例   private CrashHandler() {   }    public static CrashHandler getInstance() {     return sInstance;   }    /**    * 初始化    *    * @param context    */    public void init(Context context) {     //獲取系統默認的異常處理器     mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();     //將當前實例設為系統默認的異常處理器     Thread.setDefaultUncaughtExceptionHandler(this);     //獲取Context,方便內部使用     mContext = context.getApplicationContext();   }    /**    * 這個是最關鍵的函數,當程序中有未被捕獲的異常,系統將會自動調用#uncaughtException方法    * thread為出現未捕獲異常的線程,ex為未捕獲的異常,有了這個throwable,我們就可以得到異常信息    *    * @param thread    * @param throwable    */   @Override   public void uncaughtException(Thread thread, Throwable throwable) {     try {       //導出異常信息到SD卡中       dumpExceptionToSDCard(throwable);       //這里可以通過網絡上傳異常信息到服務器,便于開發人員分析日志從而解決bug       uploadExceptionToServer();     } catch (IOException e) {       e.printStackTrace();     }      //打印出當前調用棧信息     throwable.printStackTrace();      //如果系統提供了默認的異常處理器,則交給系統去結束我們的程序,否則就由我們自己結束自己     if (mDefaultCrashHandler != null) {       mDefaultCrashHandler.uncaughtException(thread, throwable);     } else {       android.os.Process.killProcess(android.os.Process.myPid());     }   }    /**    * 保存到內存卡    * 這里我們也可以根據項目需要選擇其他的保存方式    *    * @param throwable    * @throws IOException    */   private void dumpExceptionToSDCard(Throwable throwable) throws IOException {     //如果SD卡不存在或無法使用,則無法把異常信息寫入SD卡     if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {       if (DEBUG) {         Log.w(TAG, "sdcard unmounted,skip dump exception");         return;       }     }      File dir = new File(PATH);     if (!dir.exists()) {       dir.mkdirs();     }     long current = System.currentTimeMillis();     String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));     //以當前時間創建log文件     File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);      try {       PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));       //導出發生異常的時間       pw.println(time);        //導出手機信息       dumpPhoneInfo(pw);        pw.println();       //導出異常的調用棧信息       throwable.printStackTrace(pw);        pw.close();     } catch (Exception e) {       Log.e(TAG, "dump crash info failed");     }   }    /**    * 收集設備參數信息    *    * @param pw    * @throws PackageManager.NameNotFoundException    */   private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {     //應用的版本名稱和版本號     PackageManager pm = mContext.getPackageManager();     PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);     pw.print("App Version: ");     pw.print(pi.versionName);     pw.print('_');     pw.println(pi.versionCode);      //android版本號     pw.print("OS Version: ");     pw.print(Build.VERSION.RELEASE);     pw.print("_");     pw.println(Build.VERSION.SDK_INT);      //手機制造商     pw.print("Vendor: ");     pw.println(Build.MANUFACTURER);      //手機型號     pw.print("Model: ");     pw.println(Build.MODEL);      //cpu架構     pw.print("CPU ABI: ");     pw.println(Build.CPU_ABI);   }    /**    * 將異常信息上傳到服務器    */   private void uploadExceptionToServer() {     //在這里寫上傳到服務器的邏輯   } } 從上面的代碼可以看出,當應用程序崩潰時,CrashHandler 類會將異常信息以及設備信息寫入 SD 卡,這里大家也可以根據自己項目需要進行處理,例如也可以存儲在數據庫中,接著將異常交給系統處理,系統會幫我們中止程序,如果系統沒有默認的異常處理機制,那么就自行中止,當然而又可以選擇將異常信息上傳到服務器,這里我們沒有實現這個邏輯,實際開發中都需要將異常信息上傳到服務器
三、如何使用 CrashHandler
其實使用 CrashHandler 也非常簡單,我們可以在 Application 初始化的時候來設置 CrashHandler,如下所示:
/**  * 自定義 Application 類  * Created by qiudengjiao on 2017/9/29.  */  public class App extends Application {   @Override   public void onCreate() {     super.onCreate();     init();   }    private void init() {     //初始化異常捕獲類 CrashHandler     CrashHandler.getInstance().init(this);   } } 通過上面的操作,我們的程序就能捕獲到 crash 了,同時還能從服務器上查看用戶的 crash 信息,今天就寫到這里,給大家推薦一本不錯的書:Android 開發藝術,作者是任玉剛,相信大家也都知道,這本書的內容還是非常不錯的,值得大家一看,比較適合有一定 Android 基礎的同學,馬上就是國慶小長假了,祝大家國慶節愉快
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答