Android APP一鍵退出的方法總結分析

本文出處: 炎之鎧csdn博客:http://blog.csdn.net/totond 炎之鎧郵箱:yanzhikai_yjk@qq.com 本文demo地址:https://github.com/totond/TestAppExit 本文原創,轉載請註明本出處! 前言——到底APP需不要退出功能java

  Google是推薦APP不須要退出功能的,由於只要把APP切到後臺,系統的GC機制會自動根據內存狀況來對後臺進程進行回收,若是APP進程沒被回收的話,用戶還能快速切回APP。而後在用戶的習慣和一些開發者對這種設計的不重視(例如我上一篇寫的Application裏面的onTrimMemory()方法就沒什麼人用),還加上之前的手機內存不是很大(GC有時來不及清理後臺,致使手機有時會變得很卡),就致使了這種退出APP的需求比較多(如不少APP的連點兩下後退鍵退出功能),到了如今這個問題仍是有不少人爭論,能夠看看這個。   對於這個問題,我保持中立(要是我說不須要的話,這篇博客就不用寫了^_^),當有需求來的時候就作吧。網上也不少這方面的文章,有很多實現,可是每種方法都有它的優缺點,下面就把這些方法都測試,而後分析總結一下吧。git

本文測試使用的API版本爲23,Android6.0,minSDK爲API16. 準備工做github

  要測試確定要先寫demo,在這裏先準備demo,裏面的Activity跳轉關係是這樣的:安全

  全部Activity的啓動模式是默認模式standard,後面測試須要再改。   寫了一個Application子類BaseApplication,用registerActivityLifecycleCallbacks()來監聽每一個Activity的生命週期改變,和輸出當前進程ID(這個後面有用):app

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { Log.d(TAG, "onActivityCreated: " + activity.getLocalClassName()); Log.d(TAG, "Pid: " + + Process.myPid()); }ide

[@Override](https://my.oschina.net/u/1162528)
        public void onActivityStarted(Activity activity) {
            Log.d(TAG, "onActivityStarted: " + activity.getLocalClassName());
        }

        [@Override](https://my.oschina.net/u/1162528)
        public void onActivityResumed(Activity activity) {
        }

        [@Override](https://my.oschina.net/u/1162528)
        public void onActivityPaused(Activity activity) {
        }

        @Override
        public void onActivityStopped(Activity activity) {
            Log.d(TAG, "onActivityStopped: " + activity.getLocalClassName());
        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {
            Log.d(TAG, "onActivityDestroyed: " + activity.getLocalClassName());
        }
    });

  每一個Activity都有兩個Button,一個用來調用退出APP的方法(CloseButton):post

public void onClick(View v) {
    Log.d(BaseApplication.getTAG(), "按下Close———————————————————————————— ");
    exitAPP1();

// exitAPP2(); // exitAPP3(); // exitAPP4(); // exitAPP5();測試

  一個用來調用下面那幾種行不通的方法,用來測試研究一下(TestButton):ui

public void onClick(View v) {
    Log.d(BaseApplication.getTAG(), "按下Test———————————————————————————— ")
    System.exit(0);

// Runtime.getRuntime().exit(0);this

// Process.killProcess(Process.myPid());

// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); // activityManager.killBackgroundProcesses(context.getPackageName());

// activityManager.restartPackage(context.getPackageName()); }

  到了這裏,準備工做就作好了,因爲代碼較多,這裏就不全貼了,具體的實現代碼你們能夠到demo地址去下。

網上流傳的幾種不行的方法

  網上一些比較舊的資料,有一些通過我測試後,發現行不通的方法(不知道是否是當時他們用的時候行得通而到如今行不通)。

System.exit(0)方法

  這個方法和Runtime.getRuntime().exit(0)等價,按道理來講是結束當前的虛擬機,通過測試以後發現,執行後是關閉了當前的虛擬機,可是它還從新啓動了一個新的虛擬機,把除了當前Activity以外的其餘未關閉的Activity按照順序從新啓動過一遍。簡單來講,就是finish了當前Activity,而後刷新了一遍APP。從下面的log能夠看到,APP的進程ID都變了(這裏只開兩個Activity是爲了輸出簡潔一點,其實已作過各類各樣花式的Activity啓動順序和啓動模式的組合測試了,結論仍是同樣):

com.yanzhikai.testappexit D/TestAppExit: onActivityCreated: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: Pid: 29579 com.yanzhikai.testappexit D/TestAppExit: onActivityStarted: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: onActivityCreated: Activities.A0Activity com.yanzhikai.testappexit D/TestAppExit: Pid: 29579 com.yanzhikai.testappexit D/TestAppExit: onActivityStarted: Activities.A0Activity com.yanzhikai.testappexit D/TestAppExit: onActivityStopped: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: 按下Test———————————————————————————— com.yanzhikai.testappexit D/TestAppExit: onActivityCreated: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: Pid: 29985 com.yanzhikai.testappexit D/TestAppExit: onActivityStarted: Activities.MainActivity

Process.killProcess(Process.myPid())方法

  這個方法按道理是殺死當前線程,測試結果卻發現實際效果和上面的System.exit(0)方法同樣:

com.yanzhikai.testappexit D/TestAppExit: onActivityCreated: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: Pid: 30082 com.yanzhikai.testappexit D/TestAppExit: onActivityStarted: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: onActivityCreated: Activities.A0Activity com.yanzhikai.testappexit D/TestAppExit: Pid: 30082 com.yanzhikai.testappexit D/TestAppExit: onActivityStarted: Activities.A0Activity com.yanzhikai.testappexit D/TestAppExit: onActivityStopped: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: 按下Test———————————————————————————— com.yanzhikai.testappexit D/TestAppExit: onActivityCreated: Activities.MainActivity com.yanzhikai.testappexit D/TestAppExit: Pid: 30603 com.yanzhikai.testappexit D/TestAppExit: onActivityStarted: Activities.MainActivity

因此,當前Activity是當前任務棧最後一個Activity,而APP又沒有其餘任務棧的時候,調用這兩個方法是能夠正常結束Activity的,就是和在第一個Activity調用finish差很少,不過finish不會結束進程,這兩個方法會。 至於上面兩個方法爲何會這樣,和API的描述不符,有人說是系統廠商的修改,有人說是Google的安全機制,本人才疏學淺,找不到緣由,還請知道真相的各位告知一下,很是感謝。 ActivityManager.killBackgroundProcesses()方法

  網上資料的寫這個方法,看名字都以爲它不行了,結果固然是沒有效果,這個應該是用於結束後臺進程的方法,可是不知道爲何被人拿來講成結束當前APP。

ActivityManager.restartPackage()方法

  這個是很舊的方法了,如今都棄用了(聽說之前是能夠的),如今在個人API24版本,它只是一個包着killBackgroundProcesses()方法的馬甲:

@Deprecated
public void restartPackage(String packageName) {
    killBackgroundProcesses(packageName);
}

1 2 3 4 一鍵退出APP的方法

  講了這麼久終於進入正題,所謂一鍵,就是一調用這個方法,就能夠關閉這個APP的意思,下面的幾個方法是一鍵關閉當前APP裏面全部的Activity,而且結束APP進程(要是不想結束能夠不使用System.exit(0))。

第一種方法——簡單粗暴法

  網上的有一些方法是模擬Activity棧來管理Activity,這個方法不用模擬,直接調用系統API獲取當前的任務棧,把裏面的Activity所有finish掉,再結束進程(若是不想結束進程,能夠不調用System.exit(0))。

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void exitAPP1() {
    ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.AppTask> appTaskList = activityManager.getAppTasks();
    for (ActivityManager.AppTask appTask : appTaskList) {
        appTask.finishAndRemoveTask();
    }

// appTaskList.get(0).finishAndRemoveTask(); System.exit(0); 1 2 3 4 5 6 7 8 9   根據API源碼的註釋描述,這個ActivityManager.getAppTasks()方法的做用應該是:

Get the list of tasks associated with the calling application.

1   可是通過測試,不少狀況(一個APP裏有多個任務棧,一個APP了開了多個進程)獲取的appTaskList裏面只有一個AppTask,只是獲取到了當前的任務棧,註釋上不是說會給出當前運行的APP的全部Task麼,有知道的真相的請告知一下。

優缺點

優勢:

簡單粗暴,直接用Context獲取ActivityManager來獲取APP當前任務棧就好了,不須要其餘操做。 缺點:

這個方法只能結束當前任務棧,對於APP有多個任務棧的狀況(有Activity的啓動模式是singleInstance),不會結束其餘的後臺任務棧,這就須要本身作邏輯判斷了; 須要API21或以上,也就是Android5.0以上,根據從AndroidStudio2.3.2得到的數據,目前Android5.0以上的手機和平板佔比是40.5%(不知道準不許,有沒有考慮中國國情)。    第二種方法——保存管理法

  這種方法在網上最流行了,就是本身創建一個容器,來保存正在運行的Activity的實例,在onCreate方法寫入,在onDestroy方法寫出,當須要結束APP的時候把全部Activity實例拿出來finish掉。這裏我採用LinkedList,增刪速度快。   在BaseApplication存放activityLinkedList,經過registerActivityLifecycleCallbacks()方法來控制全部Activity實例的增刪。

@Override
public void onCreate() {
    super.onCreate();

    activityLinkedList = new LinkedList<>();

    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            Log.d(TAG, "onActivityCreated: " + activity.getLocalClassName());
            Log.d(TAG, "Pid: " +  + Process.myPid());
            activityLinkedList.add(activity);
        }

        @Override
        public void onActivityStarted(Activity activity) {
            Log.d(TAG, "onActivityStarted: " + activity.getLocalClassName());
        }

        @Override
        public void onActivityResumed(Activity activity) {
        }

        @Override
        public void onActivityPaused(Activity activity) {
        }

        @Override
        public void onActivityStopped(Activity activity) {
            Log.d(TAG, "onActivityStopped: " + activity.getLocalClassName());
        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {
            Log.d(TAG, "onActivityDestroyed: " + activity.getLocalClassName());
            activityLinkedList.remove(activity);
        }
    });
}


public static void showList() {
    for (Activity activity : activityLinkedList) {
        Log.d(TAG, "showList: " + activity.getLocalClassName());
    }
}

public static void exitAppList() {
    for (Activity activity : activityLinkedList) {
        activity.finish();
    }
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57   結束APP的時候,調用exitAppList()方法並結束進程就能夠了,還能夠用showList()來log一下當前運行的Activity名單:

private void exitAPP2() {
    BaseApplication.showList();
    BaseApplication.exitAppList();
    System.exit(0);
}

1 2 3 4 5 優缺點

優勢:

這種方法不須要考慮到Activity有多個任務棧的狀況,不管啓動模式是什麼,只要Activity的建立和結束時經歷正常的生命週期——即建立時通過onCreate方法,結束時通過onDestroy方法,都不能逃離ActivityList的「魔爪」。 缺點:

前面說了Activity是要正常的經歷生命週期,這種方法纔不會出現問題。   下面咱們來測試一下,首先是按順序進入到B0Activity,而後按下Test,這裏的Test是執行一次System.exit(0),而後就會發現Close方法並不能正常結束全部Activity,只結束了當前Activity:

  上面這個問題並不大,由於應該沒有人會無故端地調用一下System.exit(0),可是下面這個問題比較大,我在B0Activity人爲的加一個空指針,讓進入的時候會拋出空指針異常,讓APP Crash,而後發現APP也是重啓了進程,而且Activity棧回退到A0Activity,這時候能夠看到activityLinkedList裏面只有一個A0Activity了,並不能實現一鍵退出的效果。

  也就是說,採用這種方法,遇到Activity不是通過正常生命週期建立和結束的狀況,是達不到退出的效果的(要是Activity非正常結束並且APP進程沒有結束的話,activityLinkedList持有這個Activity的實例可能會致使內存泄漏,不過目前我還沒看到過這種狀況,上面兩種狀況都是進程重啓了)。 第三種方法——釜底抽薪法

  這種方法讓APP的入口Activity採用SingleTask啓動模式,那樣若是在後面的Activity啓動這個處於任務棧底部的Activity的時候,就會調用它的onNewIntent()方法,在這個方法里根據Intent判斷是否調用finish,是的話那麼整個任務棧的Activity就都結束了:

  這裏在MainActivity裏重寫onNewIntent()方法:

//exitApp3()方法使用
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (intent != null) {
        boolean isExitApp = intent.getBooleanExtra("exit", false);
        if (isExitApp) {
            this.finish();
        }
    }
}

1 2 3 4 5 6 7 8 9 10 11   退出時調用:

private void exitAPP3() {
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra("exit", true);
    context.startActivity(intent);
    System.exit(0);
}

1 2 3 4 5 6 優缺點

優勢

不怕第二種方法那樣的Activity不走正常生命週期的狀況,實現比較簡潔。 缺點

要讓MainActivity的啓動模式限定爲singleTask; 這種方法也是隻能結束當前任務棧的Activity,若是有啓動模式爲SingleInstance的Activity的話就要本身寫邏輯判斷了。 第四種方法——RxBus退出法

  使用RxBus看成事件總線,當Activity在onCreate()的時候註冊訂閱:

//exitApp4()方法使用
private Disposable disposable;

//exitApp4()方法使用註冊訂閱
private void initRxBusExit(){
    disposable = RxBus.getInstance().toObservable(String.class)
            .subscribe(new Consumer<String>() {
                @Override
                public void accept(String s) throws Exception {
                    if (s.equals("exit")){
                        finish();
                    }
                }
            });
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15   在Activity的onDestroy()取消訂閱:

//exitApp4()方法使用取消訂閱
    if (!disposable.isDisposed()){
        disposable.dispose();;
    }

1 2 3 4   當須要退出的時候,發送事件:

private void exitAPP4() {
    RxBus.getInstance().post("exit");
    System.exit(0);
}

1 2 3 4 這裏RxBus的實現是基於RxJava2.0.1,參考了這篇文章,防止文章太長這裏就不貼了,具體的實現能夠去個人demo裏看。 優缺點

優勢

能夠與RxJava和RxBus結合,對於使用RxBus和RxJava的項目能夠很容易地使用。 缺點

須要RxJava和RxBus,對於不使用的RxJava來實現的APP不推薦使用,可使用其餘的方法。 須要在每一個Activity的onCreate()方法和onDestroy()方法來註冊和取消訂閱,這樣比較麻煩,不過能夠採用一個Activity統一的基類解決,可是這樣也有了要繼承統一基類的麻煩。 和第二種同樣,要是出現Crash而後重啓進程的話仍是會失效。 第五種方法——廣播監聽法

  這種方法和上一種比較像,經過讓每個Activity在onCreate()和onDestroy()的時候註冊和註銷一個廣播接收器:

public class CloseReceiver extends BroadcastReceiver { private Activity activity;

public CloseReceiver(Activity activity){
    this.activity = activity;
}

@Override
public void onReceive(Context context, Intent intent) {
    activity.finish();
}

} 1 2 3 4 5 6 7 8 9 10 11 12 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //exitApp5()方法使用 closeReceiver = new CloseReceiver(this); registerReceiver(closeReceiver,new IntentFilter(BaseApplication.EXIT)); }

protected void onDestroy() {
    super.onDestroy();
    //exitApp5()方法使用
    unregisterReceiver(closeReceiver);
}

1 2 3 4 5 6 7 8 9 10 11 12 13   當須要退出的時候調用:

private void exitAPP5() {
    context.sendBroadcast(new Intent(BaseApplication.EXIT));
}

1 2 3 優缺點

優勢

和第二種方法同樣,不須要考慮到Activity有多個任務棧的狀況。 缺點

須要爲每一個打開的Activity註冊廣播接收器,這樣比較麻煩,不過能夠採用一個Activity統一的基類解決,可是這樣也有了要繼承統一基類的麻煩。 和第二種同樣,要是出現Crash而後重啓進程的話仍是會失效。 這種方法不能在後面加System.exit(0)來結束進程,由於執行了發送廣播這個方法以後,不會等到廣播接收器收到廣播,程序就開始執行下一句System.exit(0),而後就直接變成執行System.exit(0)的效果了。 總結

  總的來講,第二種方法——保存管理法比較實用,不過當Android5.0普及以後,第一種方法應該會用的比較多,由於用到SingleInstance啓動模式Activity的APP應該比較少。當APPActivity數量很少,並且對啓動模式沒有特別的需求的時候(也就是懶的時候),能夠選擇第三種方法。而第四種方法則是比較適合用到RxJava的時候使用。

參考文章

http://www.jianshu.com/p/8cd954b43eed http://johnnyshieh.me/posts/rxbus-rxjava2/ http://www.imooc.com/article/3300

相關文章
相關標籤/搜索