本文出處: 炎之鎧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