Android Crash處理 崩潰後禁止預設重啟與崩潰後手動重啟
轉載自:https://juejin.im/post/5a321db5f265da431b6d38ff
伸手黨福利:compile 'com.tuzhenlei:crashhandler:1.0.1'
詳情參見文件和demo:github地址
/**簡單初始化*/ //CrashHandler.getInstance().init(this, BuildConfig.DEBUG); /**個性初始化*/ CrashHandler.getInstance().init(this, BuildConfig.DEBUG, true, 0, MainActivity.class); /** * 引數1:this * 引數2:是否儲存日誌到SD卡crash目錄,建議設定為BuildConfig.DEBUG,在debug時候儲存,方便除錯 * 引數3:是否crash後重啟APP * 引數4:多少秒後重啟app,建議設為0,因為重啟採用鬧鐘定時任務模式,app會反應3秒鐘,所以最快也是3-4秒後重啟 * 引數5:重啟後開啟的第一個activity,建議是splashActivity */ /** * 更多的設定方法 */ /* //自定義Toast Toast toast = Toast.makeText(this, "自定義提示資訊", Toast.LENGTH_LONG); toast.setGravity(Gravity.BOTTOM, 0, 0); CrashHandler.setCustomToast(toast); //自定義提示資訊 CrashHandler.setCrashTip("自定義提示資訊"); //自定義APP關閉動畫 CrashHandler.setCloseAnimation(android.R.anim.fade_out); */ 複製程式碼
Crash相信是很多朋友開發過程經常遇到的問題。經過本人測試,Android在API21以下(也就是Android5.0以下),crash後會直接退出應用;但是在API21以上(5.0以上系統),會遵循以下原則重啟:
- 包含service, 如果程式crash的時候,執行著service,那麼系統會重新啟動service 。
- 不包含service,只有一個Activity,那麼系統不會重新啟動該Activity 。
- 不包含service,但是當前棧中包含兩個Activity, A–>B, 如果B crash,那麼系統會重啟A。
- 不包含service,但是當前棧中包含三個Activity, A–>B–>C, 如果C crash,那麼系統會重啟B,並且A仍然存在,即可以從重啟的Back到A。
我們來看下沒有進過任何處理的3種crash情況,3張圖分別對應第2,第3,第4種情況
所以我們根據專案需求以及實際情況有兩種解決方案,都可以避免無限crash或者是丟失必要的傳遞資訊引起其他的crash,從而造成非常差的使用者體驗的情況。
- crash後不重啟APP,讓使用者手動重啟。
- crash後1秒重啟APP。
首先,我們寫一個crashHandler類,繼承UncaughtExceptionHandler
CrashHandler implements Thread.UncaughtExceptionHandler 複製程式碼
然後初始化crashhandler的時候設定CrashHandler為程式的預設處理器,同時獲取系統預設的UncaughtException處理器
Thread.setDefaultUncaughtExceptionHandler(this);
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
複製程式碼
在需要複寫的uncaughtException方法中進行我們需要的處理,這裡關鍵就是在必須要先關閉棧內所有的activity,再退出APP:
/**
* 當UncaughtException發生時會轉入該函式來處理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
boolean isHandle = handleException(ex);
if (!isHandle && mDefaultHandler != null) {
// 如果我們沒有處理則讓系統預設的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
//給Toast留出時間
Thread.sleep(2800);
} catch (InterruptedException e) {
Log.e(TAG, "uncaughtException() InterruptedException:" + e);
}
if (mIsRestartApp) {
//利用系統時鐘進行重啟任務
AlarmManager mgr = (AlarmManager) mApplication.getSystemService(Context.ALARM_SERVICE);
try {
Intent intent = new Intent(mApplication, mClassOfFirstActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent restartIntent = PendingIntent.getActivity(mApplication, 0, intent, PendingIntent.FLAG_ONE_SHOT);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + mRestartTime, restartIntent); // x秒鐘後重啟應用
} catch (Exception e) {
Log.e(TAG, "first class error:" + e);
}
}
mMyActivityLifecycleCallbacks.removeAllActivities();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
System.gc();
}
}
複製程式碼
handleException的方法主要是為了彈出Toast和收集crash資訊
/**
* 自定義錯誤處理,收集錯誤資訊 傳送錯誤報告等操作均在此完成.
*
* @param ex
* @return true:如果處理了該異常資訊;否則返回false.
*/
private boolean handleException(Throwable ex) {
if (!hasToast) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Looper.prepare();
Toast toast;
if (mCustomToast == null) {
toast = Toast.makeText(mApplication, mCrashTip, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
} else {
toast = mCustomToast;
}
toast.show();
Looper.loop();
hasToast = true;
} catch (Exception e) {
Log.e(TAG, "handleException Toast error" + e);
}
}
}).start();
}
if (ex == null) {
return false;
}
if (mIsDebug) {
// 收集裝置引數資訊
collectDeviceInfo();
// 儲存日誌檔案
saveCatchInfo2File(ex);
}
return true;
}
複製程式碼
由於我們要關閉棧內所有activity,所以要監聽每個activity的生命週期,建議直接在application裡面註冊一個ActivityLifecycleCallbacks,實現如下:
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
private List<Activity> activities = new LinkedList<>();
public static int sAnimationId = 0;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
addActivity(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
removeActivity(activity);
}
/**
* 新增Activity
*/
public void addActivity(Activity activity) {
if (activities == null) {
activities = new LinkedList<>();
}
if (!activities.contains(activity)) {
activities.add(activity);//把當前Activity新增到集合中
}
}
/**
* 移除Activity
*/
public void removeActivity(Activity activity) {
if (activities.contains(activity)) {
activities.remove(activity);
}
if (activities.size() == 0) {
activities = null;
}
}
/**
* 銷燬所有activity
*/
public void removeAllActivities() {
for (Activity activity : activities) {
if (null != activity) {
activity.finish();
activity.overridePendingTransition(0, sAnimationId);
}
}
}
}
複製程式碼
然後用appliction.registerActivityLifecycleCallbacks(new MyActivityLifecycleCallbacks());即可。
經過我們處理後的情況如下圖所示:
這樣問題就解決了(已封裝好,直接依賴即可使用)。