爲了解決這樣的問題,咱們須要可以及時的捕獲異常,但要捕獲的地方是在太多,所以,咱們須要進行全局性的異常捕獲,那麼如何捕獲全局異常呢?java
答案是UncaughtExceptionHandler+Thread.setDefaultUncaughtExceptionHandlerandroid
1.UncaughtExceptionHandler是未捕獲異常的處理接口,該類率先捕獲異常網絡
UncaughtExceptionHandler: 線程未捕獲異常控制器是用來處理未捕獲異常的。 若是程序出現了未捕獲異常默認狀況下則會出現強行關閉對話框 實現該接口並註冊爲程序中的默認未捕獲異常處理 這樣當未捕獲異常發生時,就能夠作些異常處理操做 例如:收集異常信息,發送錯誤報告 等。
對於這個接口,咱們須要進行實現多線程
public class AppCrashHandler implements UncaughtExceptionHandler { private Context mContext; private Thread.UncaughtExceptionHandler mDefaultHandler; /**防止多線程中的異常致使讀寫不一樣步問題的lock**/ private Lock lock = null; /**本地保存文件日誌**/ private final String CRASH_REPORTER_EXTENSION = ".crash"; /**日誌tag**/ private final String STACK_TRACE = "logStackTrance"; /**保存文件名**/ private final String crash_pref_path ="app_crash_pref.xml"; private static final String OOM = "java.lang.OutOfMemoryError"; private static final String HPROF_FILE_PATH = Environment.getExternalStorageDirectory().getPath() + "/data_crash.hprof" private AppCrashHandler() { lock = new ReentrantLock(true); } /** * 得到單例對象 * @param context * @return AppCrashHandler */ public static AppCrashHandler shareInstance(Context context){ AppCrashHandler crashhandler = AppCrashHandler.InstanceHolder.crashHandler; crashhandler.initCrashHandler(context); return crashhandler; } /** * 使用初始化方法初始化,防止提早初始化或者重複初始化 * @param cxt */ private void initCrashHandler(Context cxt) { if(!hasInitilized()){ mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); mContext = cxt; } } public interface InstanceHolder { public static AppCrashHandler crashHandler = new AppCrashHandler(); } public static boolean isOOM(Throwable throwable){ Log.d(TAG, "getName:" + throwable.getClass().getName()); if(OOM.equals(throwable.getClass().getName())){ return true; }else{ Throwable cause = throwable.getCause(); if(cause != null){ return isOOM(cause); } return false; } } @Override public void uncaughtException(Thread thread, Throwable ex) { if(isOOM(throwable)){ try { Debug.dumpHprofData(HPROF_FILE_PATH); } catch (Exception e) { Log.e(TAG, "couldn’t dump hprof", e); } } if (!handleExceptionMessage(ex) && mDefaultHandler != null) { // 若是用戶沒有處理則讓系統默認的異常處理器來處理 mDefaultHandler.uncaughtException(thread, ex); } else { try { Thread.sleep(3000); } catch (InterruptedException e) { Log.e(STACK_TRACE, "Error : ", e); } android.os.Process.killProcess(android.os.Process.myPid()); System.exit(10); } } /** * 自定義錯誤處理,收集錯誤信息 發送錯誤報告等操做均在此完成. 開發者能夠根據本身的狀況來自定義異常處理邏輯 * @param ex * @return true:若是處理了該異常信息;不然返回false */ private boolean handleExceptionMessage(Throwable ex) { if (ex == null) { return false; } // 使用Toast來顯示異常信息 new Thread() { @Override public void run() { // Toast 顯示須要出如今一個線程的消息隊列中 Looper.prepare(); Toast.makeText(mContext, "程序出錯啦,即將退出", Toast.LENGTH_LONG).show(); Looper.loop(); } }.start(); String fileName = mContext.getPackageName()+"-"+"appCrash-Exception"+ CRASH_REPORTER_EXTENSION; String crashFileName = saveExceptionToFile(ex,fileName); SharedPreferences.Editor editor = mContext.getSharedPreferences(crash_pref_path , Context.MODE_PRIVATE).edit(); editor.putString(STACK_TRACE, crashFileName); editor.commit(); Log.d(STACK_TRACE, "errorLogPath="+crashFileName); return true; } /** * 是否已初始化 * @return */ public boolean hasInitilized() { return mContext!=null; } /** * 保存錯誤信息到文件中 * @param ex * @return * @throws IOException */ private String saveExceptionToFile(Throwable ex,String fileName) { File saveFile = null; PrintWriter printWriter = null; try { lock.tryLock(); if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { File sdCardDir = Environment.getExternalStorageDirectory();//獲取SDCard目錄 saveFile = new File(sdCardDir, fileName); }else{ saveFile =new File(mContext.getFilesDir(),fileName); } if(!saveFile.exists()) { saveFile.createNewFile(); } printWriter = new PrintWriter(saveFile); String result = formatException(ex); printWriter.write(result); printWriter.flush(); Log.e("CrashException", result); }catch(Exception e){ e.printStackTrace(); } finally{ if(printWriter!=null) { printWriter.close(); } lock.unlock(); } return saveFile!=null?saveFile.getAbsolutePath():null; } /** * 格式化異常信息 * @param e * @return */ @SuppressLint("SimpleDateFormat") private String formatException(Throwable e) { StringBuilder sb = new StringBuilder(); StackTraceElement[] stackTrace = e.getStackTrace(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); if (stackTrace!=null) { String timeStramp = sdf.format(new Date(System.currentTimeMillis())); String format = String.format("DateTime:%s\nExceptionName:%s\n\n",timeStramp,e.getLocalizedMessage()); sb.append(format); for (int i = 0; i < stackTrace.length; i++) { StackTraceElement traceElement = stackTrace[i]; String fileName = traceElement.getFileName(); int lineNumber = traceElement.getLineNumber(); String methodName = traceElement.getMethodName(); String className = traceElement.getClassName(); sb.append(String.format("%s\t%s[%d].%s \n",className,fileName,lineNumber,methodName)); } sb.append(String.format("\n%s",e.getMessage())); Writer stringWriter = new StringWriter(); PrintWriter pw = new PrintWriter(stringWriter); e.printStackTrace(pw); pw.flush(); pw.close(); sb.append("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); sb.append(stringWriter.toString()); } return sb.toString(); } }
這裏只保存了文件,通常來講,當app第二次啓動時咱們須要將該文件上傳到網絡,時間不是很充裕,這裏上傳暫時不貼代碼了,時間充裕的話會及時補充,請保持關注吧app
2.初始化,監聽全局異常信息,這裏須要繼承Application,並替換系統默認的Applicationide
public class BaseApplication extends Application { private static BaseApplication instance = null; private AppCrashHandler appCrashHandler = null; @Override public void onCreate() { synchronized (this) { if(instance==null) { instance = this; } appCrashHandler = AppCrashHandler.shareInstance(instance); } super.onCreate(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } }
修改清單文件oop
<application android:name="com.hali.luya.unitest.BaseApplication " android:hardwareAccelerated="true" android:icon="@drawable/app_logo" android:logo="@drawable/app_logo" android:label="@string/app_name" android:configChanges="locale|keyboard|screenSize" android:theme="@style/Theme.AppBaseTheme" > <!--- ..這裏省略一大堆代碼.. ----> </Application>
騰訊有一個bugly產品能夠實現crash收集和處理,固然也能夠同時使用UncaughtExceptionHandler,由於騰訊bugly雖然也實現了UncaughtExceptionHandler該回調,但騰訊bugly在捕獲異常的同時也會調用你本身的UncaughtExceptionHandler。ui
目前騰訊的bugly不支持回調,但我申請到了騰訊的內測版支持回調。this
public class BaseApplication extends Application { private static Application instance = null; private AppCrashHandler appCrashHandler = null; private final String APP_CONTEXT_TAG = "appContext"; @Override public void onCreate() { synchronized (this) { if(instance==null) { instance = this; } appCrashHandler = AppCrashHandler.shareInstance(instance); UserStrategy strategy = new UserStrategy(instance); //App的策略Bean strategy.setAppChannel(getPackageName()); //設置渠道 strategy.setAppVersion(getVersion()); //App的版本 strategy.setAppReportDelay(1000); //設置SDK處理延時,毫秒 strategy.setDeviceID(GlobalUtil.getInstance().getDeviceID(instance)); strategy.setCrashHandleCallback(new AppCrashHandleCallback()); CrashReport.initCrashReport(instance, "900001335", true, strategy); //自定義策略生效,必須在初始化SDK前調用 CrashReport. setUserId("BBDTEK"); } //shutDownLog(); super.onCreate(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } /** * 獲取版本號 * @return 當前應用的版本號 */ public String getVersion() { try { PackageManager manager = this.getPackageManager(); PackageInfo info = manager.getPackageInfo(this.getPackageName(), 0); String version = info.versionName; return this.getString(R.string.app_version) + version; } catch (Exception e) { e.printStackTrace(); return this.getString(R.string.app_version); } } private class AppCrashHandleCallback extends CrashHandleCallback //bugly回調 { @Override public synchronized Map<String, String> onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) { String crashTypeName = null; switch (crashType) { case CrashHandleCallback.CRASHTYPE_JAVA_CATCH: crashTypeName = "JAVA_CATCH"; break; case CrashHandleCallback.CRASHTYPE_JAVA_CRASH: crashTypeName = "JAVA_CRASH"; break; case CrashHandleCallback.CRASHTYPE_NATIVE: crashTypeName = "JAVA_NATIVE"; break; case CrashHandleCallback.CRASHTYPE_U3D: crashTypeName = "JAVA_U3D"; break; default: { crashTypeName = "unknown"; } } Log.e(APP_CONTEXT_TAG, "Crash Happen Type:" + crashType + " TypeName:" + crashTypeName); Log.e(APP_CONTEXT_TAG, "errorType:" + errorType); Log.e(APP_CONTEXT_TAG, "errorMessage:" + errorMessage); Log.e(APP_CONTEXT_TAG, "errorStack:" + errorStack); Map<String, String> userDatas = super.onCrashHandleStart(crashType, errorType, errorMessage, errorStack); if (userDatas == null) { userDatas = new HashMap<String, String>(); } userDatas.put("DEBUG", "TRUE"); return userDatas; } } /** * 關閉重要信息的日誌 */ private void shutDownLog() { LogUtils.allowE = false; LogUtils.allowI = false; LogUtils.allowV = false; LogUtils.allowW = false; LogUtils.allowWtf = false; LogUtils.allowD = false; } }
try doing itspa