1. 程式人生 > >獲取Android崩潰crash資訊並寫入日誌

獲取Android崩潰crash資訊並寫入日誌

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資訊(重啟應用後友盟才會提交崩潰資料)後友盟依然記錄到了崩潰