如何優雅的退出應用和處理崩潰異常並重啓

寫在前面

這是最近一些朋友問個人問題,我把它整理成了一個庫,供你們享用,GitHub 地址:https://github.com/nanchen2251/AppManagerjava

從四個應用場景提及

  • 退出應用
    相信各位朋友或多或少都會有遇到過須要在某個特定的地方退出應用的需求,這個場景必定很是廣泛。android

  • 崩潰後重啓
    程序老是沒法作到盡善盡美,有時候你也不知道由於什麼緣由致使了 APP 的崩潰,這無疑是很是糟糕的用戶體驗。這時候咱們能夠採用重啓機制來加強用戶溫馨體驗感。git

  • 莫名其妙重啓
    然而心細的小夥伴確定會發現,在部分手機上會出現莫名其妙的崩潰後重啓(後面會講緣由),並且最要命的是,假設你有三個 Activity,他們分別是 Act1, Act2, Act3,它們的啓動順序是 Act1 -> Act2 -> Act3,而若是在 Act3 發生了崩潰,這時候極有可能應用重啓後進入的是 Act2,而 Act2 中須要某個來源於 Act1 (或者在 Act1 中經過接口獲取) 的參數,當沒有這個參數的時候會引起崩潰(或者數據不全)。這時候你可能最直觀的想法就是禁止應用重啓,但或許這並非最佳的方式。github

  • 崩潰時彈出一個對話框
    在部分手機上,當崩潰的時候,會彈出一個提示對話框。在這種狀況下,用戶只有點擊 「強行關閉」 來結束程序。當該對話框出現,對用戶來講是至關不友好的。或許咱們能夠經過某種方式攔截掉系統的處理,讓應用出錯時再也不顯示它。maven

退出應用的幾種方式

Andorid 退出應用的方式不少,常見的也就下面四種。ide

  • System.exit(0) 使用系統的方法,強制退出
    System.exit(0) 表示的是終止程序,終止當前正在運行的 Java 虛擬機,在 Java 中咱們也使用這種方式來關閉整個應用,在前期不少開發人員都是使用這種方式,我本身在開發項目過程當中也用過這種方式來退出,可是有時候會在部分機型中,當退出應用後彈出應用程序崩潰的對話框,有時退出後還會再次啓動,少部分的用戶體驗不太好。但如今也依舊還會有少部分的開發人員會使用這種方式,由於使用方式很簡單,只須要在須要退出的地方加上這句代碼就行。函數

  • 拋出異常,強制退出
    這種方式如今基本上已經看不到了,用戶體驗比第一種方式更差,就是讓拋出異常、是系統崩潰、從而達到退出應用的效果oop

  • 使用 Application 退出
    目前比較經常使用方法之一,咱們都知道 ApplicationAndroid 的系統組件,當應用程序啓動時,會自動幫咱們建立一個 Application,並且一個應用程序只能存在一個 Application ,它的生命週期也是最長的,若是須要使用本身建立的 Application 時,這個時候咱們只須要在 Androidmanifest.xml 中的 <Application> 標籤中添加 name 屬性:把建立的 Application 完整的包名 + 類名放進了就好了。測試

  • 使用廣播退出
    使用廣播來實現退出應用程序,其實實現的思路相對於第三種更簡單,咱們編寫一個 BaseActivity,讓其餘的 Activity 都繼承於它,當我須要退出時,咱們就銷燬 BaseActivity,那麼其餘繼承與它的 Activity 都會銷燬。gradle

四種方式的代碼也就很少提,須要的本身去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;
    }

封裝好的使用

一、添加依賴

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'
    }

二、在須要使用的地方使用

// 設置崩潰後自動重啓 APP
UncaughtExceptionHandlerImpl.getInstance().init(this, BuildConfig.DEBUG, true, 0, MainActivity.class);

三、你也能夠禁止重啓

// 禁止重啓UncaughtExceptionHandlerImpl.getInstance().init(this,BuildConfig.DEBUG);

歡迎關注個人技術公衆號(公衆號搜索nanchen),天天一篇 Android 資源分享。

效果圖

image

相關文章
相關標籤/搜索