寫(xiě)在前面

這是最近一些朋友問(wèn)我的問(wèn)題,我把它整理成了一個(gè)庫(kù),供大家享用,GitHub 地址:https://github.com/nanchen2251/AppManager

從四個(gè)應(yīng)用場(chǎng)景說(shuō)起

  • 退出應(yīng)用
    相信各位朋友或多或少都會(huì)有遇到過(guò)需要在某個(gè)特定的地方退出應(yīng)用的需求,這個(gè)場(chǎng)景一定非常普遍。

  • 崩潰后重啟
    程序總是無(wú)法做到盡善盡美,有時(shí)候你也不知道因?yàn)槭裁丛驅(qū)е铝?APP 的崩潰,這無(wú)疑是非常糟糕的用戶體驗(yàn)。這時(shí)候我們可以采用重啟機(jī)制來(lái)增強(qiáng)用戶舒適體驗(yàn)感。

  • 莫名其妙重啟
    然而心細(xì)的小伙伴肯定會(huì)發(fā)現(xiàn),在部分手機(jī)上會(huì)出現(xiàn)莫名其妙的崩潰后重啟(后面會(huì)講原因),而且最要命的是,假設(shè)你有三個(gè) Activity,他們分別是 Act1, Act2, Act3,它們的啟動(dòng)順序是 Act1 -> Act2 -> Act3,而如果在 Act3 發(fā)生了崩潰,這時(shí)候極有可能應(yīng)用重啟后進(jìn)入的是 Act2,而 Act2 中需要某個(gè)來(lái)源于 Act1 (或者在 Act1 中通過(guò)接口獲取) 的參數(shù),當(dāng)沒(méi)有這個(gè)參數(shù)的時(shí)候會(huì)引發(fā)崩潰(或者數(shù)據(jù)不全)。這時(shí)候你可能最直觀的想法就是禁止應(yīng)用重啟,但或許這并不是最佳的方式。

  • 崩潰時(shí)彈出一個(gè)對(duì)話框
    在部分手機(jī)上,當(dāng)崩潰的時(shí)候,會(huì)彈出一個(gè)提示對(duì)話框。在這種情況下,用戶只有點(diǎn)擊 “強(qiáng)行關(guān)閉” 來(lái)結(jié)束程序。當(dāng)該對(duì)話框出現(xiàn),對(duì)用戶來(lái)說(shuō)是相當(dāng)不友好的。或許我們可以通過(guò)某種方式攔截掉系統(tǒng)的處理,讓?xiě)?yīng)用出錯(cuò)時(shí)不再顯示它。

退出應(yīng)用的幾種方式

Andorid 退出應(yīng)用的方式很多,常見(jiàn)的也就下面四種。

  • System.exit(0) 使用系統(tǒng)的方法,強(qiáng)制退出
    System.exit(0) 表示的是終止程序,終止當(dāng)前正在運(yùn)行的 Java 虛擬機(jī),在 Java 中我們也使用這種方式來(lái)關(guān)閉整個(gè)應(yīng)用,在前期很多開(kāi)發(fā)人員都是使用這種方式,我自己在開(kāi)發(fā)項(xiàng)目過(guò)程中也用過(guò)這種方式來(lái)退出,但是有時(shí)候會(huì)在部分機(jī)型中,當(dāng)退出應(yīng)用后彈出應(yīng)用程序崩潰的對(duì)話框,有時(shí)退出后還會(huì)再次啟動(dòng),少部分的用戶體驗(yàn)不太好。但現(xiàn)在也依舊還會(huì)有少部分的開(kāi)發(fā)人員會(huì)使用這種方式,因?yàn)槭褂梅绞胶芎?jiǎn)單,只需要在需要退出的地方加上這句代碼就行。

  • 拋出異常,強(qiáng)制退出
    這種方式現(xiàn)在基本上已經(jīng)看不到了,用戶體驗(yàn)比第一種方式更差,就是讓拋出異常、是系統(tǒng)崩潰、從而達(dá)到退出應(yīng)用的效果

  • 使用 Application 退出
    目前比較常用方法之一,我們都知道 Application 是 Android 的系統(tǒng)組件,當(dāng)應(yīng)用程序啟動(dòng)時(shí),會(huì)自動(dòng)幫我們創(chuàng)建一個(gè) Application,而且一個(gè)應(yīng)用程序只能存在一個(gè) Application ,它的生命周期也是最長(zhǎng)的,如果需要使用自己創(chuàng)建的 Application 時(shí),這個(gè)時(shí)候我們只需要在 Androidmanifest.xml 中的 標(biāo)簽中添加 name 屬性:把創(chuàng)建的 Application完整的包名 + 類名放進(jìn)了就行了。

  • 使用廣播退出
    使用廣播來(lái)實(shí)現(xiàn)退出應(yīng)用程序,其實(shí)實(shí)現(xiàn)的思路相對(duì)于第三種更簡(jiǎn)單,我們編寫(xiě)一個(gè) BaseActivity,讓其他的 Activity 都繼承于它,當(dāng)我需要退出時(shí),我們就銷毀 BaseActivity,那么其他繼承與它的 Activity 都會(huì)銷毀。

四種方式的代碼也就不多提,需要的自己去Android:銷毀所有的Activity退出應(yīng)用程序幾種方式

莫名其妙重啟?

上面的場(chǎng)景中說(shuō)到了,再部分手機(jī)上會(huì)出現(xiàn)崩潰后自動(dòng)重啟的情況,這讓我們很不好控制。經(jīng)本人測(cè)試,在 Android 的 API 21 ( Android 5.0 ) 以下,Crash 會(huì)直接退出應(yīng)用,但是在 API 21 ( Android 5.0 ) 以上,系統(tǒng)會(huì)遵循以下原則進(jìn)行重啟:

  • 包含 Service,如果應(yīng)用 Crash 的時(shí)候,運(yùn)行著 Service,那么系統(tǒng)會(huì)重新啟動(dòng) Service。

  • 不包含 Service,只有一個(gè) Activity,那么系統(tǒng)不會(huì)重新啟動(dòng)該 Activity。

  • 不包含 Service,但當(dāng)前堆棧中存在兩個(gè) Activity:Act1 -> Act2,如果 Act2 發(fā)生了 Crash ,那么系統(tǒng)會(huì)重啟 Act1。

  • 不包含 Service,但是當(dāng)前堆棧中存在三個(gè) Activity:Act1 -> Act2 -> Act3,如果 Act3 崩潰,那么系統(tǒng)會(huì)重啟 Act2,并且 Act1 依然存在,即可以從重啟的 Act2 回到 Act1。

在這樣的情況下,我們或許會(huì)有兩種需求:

  • 崩潰后不允許重啟

  • 崩潰后需要重啟

怎么辦

翻看 API 我們發(fā)現(xiàn),Java 中存在一個(gè) UncaughtExceotionHandler 的接口,而在 Android 中我們沿用了它,我們可以采用這個(gè)接口實(shí)現(xiàn)我們想要的功能。
(為了方便,我把它做成了庫(kù),傳送門(mén):https://github.com/nanchen2251/AppManager

講一些核心

CrashApplication

首先是我們的 CrashApplication 類,因?yàn)槲覀儽罎⒌臅r(shí)候需要結(jié)束程序后再重啟,所以我們需要退出應(yīng)用,這里我們采用上面的第三種方式。

public class CrashApplication extends Application {    private List<Activity> mActivityList;    @Override
    public void onCreate() {        super.onCreate();
        mActivityList = new ArrayList<>();
    }    /**
     * 添加單個(gè)Activity
     */
    public void addActivity(Activity activity) {        // 為了避免重復(fù)添加,需要判斷當(dāng)前集合是否滿足不存在該Activity
        if (!mActivityList.contains(activity)) {
            mActivityList.add(activity); // 把當(dāng)前Activity添加到集合中
        }
    }    /**
     * 銷毀單個(gè)Activity
     */
    public void removeActivity(Activity activity) {        // 判斷當(dāng)前集合是否存在該Activity
        if (mActivityList.contains(activity)) {
            mActivityList.remove(activity); // 從集合中移除
            if (activity != null){
                activity.finish(); // 銷毀當(dāng)前Activity
            }
        }
    }    /**
     * 銷毀所有的Activity
     */
    public void removeAllActivity() {        // 通過(guò)循環(huán),把集合中的所有Activity銷毀
        for (Activity activity : mActivityList) {            if (activity != null){
                activity.finish();
            }
        }        //殺死該應(yīng)用進(jìn)程
        android.os.Process.killProcess(android.os.Process.myPid());
    }

}

UncaughtExceptionHandlerImpl

我們當(dāng)然少不了新建一個(gè) UncaughtExceptionHandlerImpl 類去實(shí)現(xiàn)我們的 UncaughtExceptionHandler 接口,它必須實(shí)現(xiàn)我們的 uncaughtException(thread, throwable) 方法,我們接下來(lái)可以在這中間作文章。需要特別注意的是:重啟必須清除堆棧內(nèi)的 Activity。

/**
     * 當(dāng) UncaughtException 發(fā)生時(shí)會(huì)轉(zhuǎn)入該函數(shù)來(lái)處理
     */
    @SuppressWarnings("WrongConstant")
    @Override    public void uncaughtException(Thread thread, Throwable ex) {        if (!handleException(ex) && mDefaultHandler != null) {            // 如果用戶沒(méi)有處理則讓系統(tǒng)默認(rèn)的異常處理器來(lái)處理
            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);                //重啟應(yīng)用,得使用PendingIntent
                PendingIntent restartIntent = PendingIntent.getActivity(
                        mContext.getApplicationContext(), 0, intent,
                        Intent.FLAG_ACTIVITY_NEW_TASK);
                mAlarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + mRestartTime,
                        restartIntent); // 重啟應(yīng)用
            }            // 結(jié)束應(yīng)用
            ((CrashApplication) mContext.getApplicationContext()).removeAllActivity();
        }
    }

我們的 handleException(throwable) 方法用于彈出 Toast 和收集 Crash 信息。

  /**
     * 自定義錯(cuò)誤處理,收集錯(cuò)誤信息,發(fā)送錯(cuò)誤報(bào)告等操作均在此完成
     *
     * @param ex
     * @return true:如果處理了該異常信息;否則返回 false
     */
    private boolean handleException(final Throwable ex) {        if (ex == null) {            return false;
        }        // 使用 Toast 來(lái)顯示異常信息
        new Thread() {            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, getTips(ex), Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();        //  如果用戶不賦予外部存儲(chǔ)卡的寫(xiě)權(quán)限導(dǎo)致的崩潰,會(huì)造成循環(huán)崩潰//        if (mIsDebug) {//            // 收集設(shè)備參數(shù)信息//            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、在需要使用的地方使用

// 設(shè)置崩潰后自動(dòng)重啟 APPUncaughtExceptionHandlerImpl.getInstance().init(this, BuildConfig.DEBUG, true, 0, MainActivity.class);

3、你也可以禁止重啟

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

歡迎關(guān)注我的技術(shù)公眾號(hào)(公眾號(hào)搜索nanchen),每天一篇 Android 資源分享。

效果圖


作  者: 南 塵   平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn) 
出  處: http://www.cnblogs.com/liushilin/ 
關(guān)于作者:專注于移動(dòng)前端的項(xiàng)目開(kāi)發(fā)。如有問(wèn)題或建議,請(qǐng)多多賜教!歡迎加入Android交流群:118116509 
版權(quán)聲明:本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文鏈接。 
特此聲明:所有評(píng)論和私信都會(huì)在第一時(shí)間回復(fù)。也歡迎園子的大大們指正錯(cuò)誤,共同進(jìn)步?;蛘?a style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); text-decoration-line: none;">直接私信我 
聲援博主:如果您覺(jué)得文章對(duì)您有幫助,可以點(diǎn)擊文章下部推薦或側(cè)邊關(guān)注。您的鼓勵(lì)是作者堅(jiān)持原創(chuàng)和持續(xù)寫(xiě)作的最大動(dòng)力! 

http://www.cnblogs.com/liushilin/p/7219206.html