寫在前面
這是最近一些朋友問我的問題,我把它整理成了一個庫,供大家享用,GitHub 地址:https://github.com/nanchen2251/AppManager
從四個應用場景說起
-
退出應用
相信各位朋友或多或少都會有遇到過需要在某個特定的地方退出應用的需求,這個場景一定非常普遍。 -
崩潰后重啟
程序總是無法做到盡善盡美,有時候你也不知道因為什么原因導致了 APP 的崩潰,這無疑是非常糟糕的用戶體驗。這時候我們可以采用重啟機制來增強用戶舒適體驗感。 -
莫名其妙重啟
然而心細的小伙伴肯定會發現,在部分手機上會出現莫名其妙的崩潰后重啟(后面會講原因),而且最要命的是,假設你有三個 Activity,他們分別是 Act1, Act2, Act3,它們的啟動順序是 Act1 -> Act2 -> Act3,而如果在 Act3 發生了崩潰,這時候極有可能應用重啟后進入的是 Act2,而 Act2 中需要某個來源於 Act1 (或者在 Act1 中通過接口獲取) 的參數,當沒有這個參數的時候會引發崩潰(或者數據不全)。這時候你可能最直觀的想法就是禁止應用重啟,但或許這並不是最佳的方式。 -
崩潰時彈出一個對話框
在部分手機上,當崩潰的時候,會彈出一個提示對話框。在這種情況下,用戶只有點擊 “強行關閉” 來結束程序。當該對話框出現,對用戶來說是相當不友好的。或許我們可以通過某種方式攔截掉系統的處理,讓應用出錯時不再顯示它。
退出應用的幾種方式
Andorid 退出應用的方式很多,常見的也就下面四種。
-
System.exit(0) 使用系統的方法,強制退出
System.exit(0)
表示的是終止程序,終止當前正在運行的 Java 虛擬機,在 Java 中我們也使用這種方式來關閉整個應用,在前期很多開發人員都是使用這種方式,我自己在開發項目過程中也用過這種方式來退出,但是有時候會在部分機型中,當退出應用后彈出應用程序崩潰的對話框,有時退出后還會再次啟動,少部分的用戶體驗不太好。但現在也依舊還會有少部分的開發人員會使用這種方式,因為使用方式很簡單,只需要在需要退出的地方加上這句代碼就行。 -
拋出異常,強制退出
這種方式現在基本上已經看不到了,用戶體驗比第一種方式更差,就是讓拋出異常、是系統崩潰、從而達到退出應用的效果 -
使用 Application 退出
目前比較常用方法之一,我們都知道Application
是Android
的系統組件,當應用程序啟動時,會自動幫我們創建一個Application
,而且一個應用程序只能存在一個Application
,它的生命周期也是最長的,如果需要使用自己創建的Application
時,這個時候我們只需要在Androidmanifest.xml
中的標簽中添加 name 屬性:把創建的 Application
完整的包名 + 類名放進了就行了。 -
使用廣播退出
使用廣播來實現退出應用程序,其實實現的思路相對於第三種更簡單,我們編寫一個BaseActivity
,讓其他的Activity
都繼承於它,當我需要退出時,我們就銷毀BaseActivity
,那么其他繼承與它的Activity
都會銷毀。
四種方式的代碼也就不多提,需要的自己去Android:銷毀所有的Activity退出應用程序幾種方式
莫名其妙重啟?
上面的場景中說到了,再部分手機上會出現崩潰后自動重啟的情況,這讓我們很不好控制。經本人測試,在 Android 的 API 21 ( Android 5.0 ) 以下,Crash 會直接退出應用,但是在 API 21 ( Android 5.0 ) 以上,系統會遵循以下原則進行重啟:
- 包含 Service,如果應用 Crash 的時候,運行着 Service,那么系統會重新啟動 Service。
- 不包含 Service,只有一個 Activity,那么系統不會重新啟動該 Activity。
- 不包含 Service,但當前堆棧中存在兩個 Activity:Act1 -> Act2,如果 Act2 發生了 Crash ,那么系統會重啟 Act1。
- 不包含 Service,但是當前堆棧中存在三個 Activity:Act1 -> Act2 -> Act3,如果 Act3 崩潰,那么系統會重啟 Act2,並且 Act1 依然存在,即可以從重啟的 Act2 回到 Act1。
在這樣的情況下,我們或許會有兩種需求:
- 崩潰后不允許重啟
- 崩潰后需要重啟
怎么辦
翻看 API 我們發現,Java 中存在一個 UncaughtExceotionHandler
的接口,而在 Android 中我們沿用了它,我們可以采用這個接口實現我們想要的功能。
(為了方便,我把它做成了庫,傳送門:https://github.com/nanchen2251/AppManager)
講一些核心
CrashApplication
首先是我們的 CrashApplication
類,因為我們崩潰的時候需要結束程序后再重啟,所以我們需要退出應用,這里我們采用上面的第三種方式。
public class CrashApplication extends Application {
private List<Activity> mActivityList;
@Override
public void onCreate() {
super.onCreate();
mActivityList = new ArrayList<>();
}
/**
* 添加單個Activity
*/
public void addActivity(Activity activity) {
// 為了避免重復添加,需要判斷當前集合是否滿足不存在該Activity
if (!mActivityList.contains(activity)) {
mActivityList.add(activity); // 把當前Activity添加到集合中
}
}
/**
* 銷毀單個Activity
*/
public void removeActivity(Activity activity) {
// 判斷當前集合是否存在該Activity
if (mActivityList.contains(activity)) {
mActivityList.remove(activity); // 從集合中移除
if (activity != null){
activity.finish(); // 銷毀當前Activity
}
}
}
/**
* 銷毀所有的Activity
*/
public void removeAllActivity() {
// 通過循環,把集合中的所有Activity銷毀
for (Activity activity : mActivityList) {
if (activity != null){
activity.finish();
}
}
//殺死該應用進程
android.os.Process.killProcess(android.os.Process.myPid());
}
}
UncaughtExceptionHandlerImpl
我們當然少不了新建一個 UncaughtExceptionHandlerImpl
類去實現我們的 UncaughtExceptionHandler
接口,它必須實現我們的 uncaughtException(thread, throwable)
方法,我們接下來可以在這中間作文章。需要特別注意的是:重啟必須清除堆棧內的 Activity。
/**
* 當 UncaughtException 發生時會轉入該函數來處理
*/
@SuppressWarnings("WrongConstant")
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用戶沒有處理則讓系統默認的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Log.e(TAG, "error : ", e);
}
if (mIsRestartApp) { // 如果需要重啟
Intent intent = new Intent(mContext.getApplicationContext(), mRestartActivity);
AlarmManager mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
//重啟應用,得使用PendingIntent
PendingIntent restartIntent = PendingIntent.getActivity(
mContext.getApplicationContext(), 0, intent,
Intent.FLAG_ACTIVITY_NEW_TASK);
mAlarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + mRestartTime,
restartIntent); // 重啟應用
}
// 結束應用
((CrashApplication) mContext.getApplicationContext()).removeAllActivity();
}
}
我們的 handleException(throwable)
方法用於彈出 Toast
和收集 Crash 信息。
/**
* 自定義錯誤處理,收集錯誤信息,發送錯誤報告等操作均在此完成
*
* @param ex
* @return true:如果處理了該異常信息;否則返回 false
*/
private boolean handleException(final Throwable ex) {
if (ex == null) {
return false;
}
// 使用 Toast 來顯示異常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, getTips(ex), Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
// 如果用戶不賦予外部存儲卡的寫權限導致的崩潰,會造成循環崩潰
// if (mIsDebug) {
// // 收集設備參數信息
// collectDeviceInfo(mContext);
// // 保存日志文件
// saveCrashInfo2File(ex);
// }
return true;
}
封裝好的使用
1、添加依賴
Step 1. Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
compile 'com.github.nanchen2251:AppManager:1.0.1'
}
2、在需要使用的地方使用
// 設置崩潰后自動重啟 APP
UncaughtExceptionHandlerImpl.getInstance().init(this, BuildConfig.DEBUG, true, 0, MainActivity.class);
3、你也可以禁止重啟
// 禁止重啟UncaughtExceptionHandlerImpl.getInstance().init(this,BuildConfig.DEBUG);
歡迎關注我的技術公眾號(公眾號搜索nanchen),每天一篇 Android 資源分享。