1. 程式人生 > >Android Crash處理 崩潰後禁止預設重啟與崩潰後手動重啟

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以上系統),會遵循以下原則重啟:

  1. 包含service, 如果程式crash的時候,執行著service,那麼系統會重新啟動service 。
  2. 不包含service,只有一個Activity,那麼系統不會重新啟動該Activity 。
  3. 不包含service,但是當前棧中包含兩個Activity, A–>B, 如果B crash,那麼系統會重啟A。
  4. 不包含service,但是當前棧中包含三個Activity, A–>B–>C, 如果C crash,那麼系統會重啟B,並且A仍然存在,即可以從重啟的Back到A。

我們來看下沒有進過任何處理的3種crash情況,3張圖分別對應第2,第3,第4種情況

crash at activity1.gif

 

crash at activity2.gif

 

 

 

crash at activity3.gif

 

 

所以我們根據專案需求以及實際情況有兩種解決方案,都可以避免無限crash或者是丟失必要的傳遞資訊引起其他的crash,從而造成非常差的使用者體驗的情況。

  1. crash後不重啟APP,讓使用者手動重啟。
  2. 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());即可。

經過我們處理後的情況如下圖所示:

 

demo after handle.gif

 

 

這樣問題就解決了(已封裝好,直接依賴即可使用)。