本文出處:
炎之鎧csdn博客:http://blog.csdn.net/totond
炎之鎧郵箱:yanzhikai_yjk@qq.com
本文demo地址:https://github.com/totond/TestAppExit
本文原創(chuàng)辨图,轉(zhuǎn)載請(qǐng)注明本出處盒揉!
前言——到底APP需不要退出功能
Google是推薦APP不需要退出功能的跛璧,因?yàn)橹灰袮PP切到后臺(tái)芬沉,系統(tǒng)的GC機(jī)制會(huì)自動(dòng)根據(jù)內(nèi)存情況來(lái)對(duì)后臺(tái)進(jìn)程進(jìn)行回收祸穷,如果APP進(jìn)程沒(méi)被回收的話,用戶還能快速切回APP蛇耀。然后在用戶的習(xí)慣和一些開(kāi)發(fā)者對(duì)這種設(shè)計(jì)的不重視(例如我上一篇寫(xiě)的Application里面的onTrimMemory()
方法就沒(méi)什么人用)嫁蛇,還加上以前的手機(jī)內(nèi)存不是很大(GC有時(shí)來(lái)不及清理后臺(tái),導(dǎo)致手機(jī)有時(shí)會(huì)變得很卡)厦坛,就導(dǎo)致了這種退出APP的需求比較多(如很多APP的連點(diǎn)兩下后退鍵退出功能)五垮,到了現(xiàn)在這個(gè)問(wèn)題還是有很多人爭(zhēng)論,可以看看這個(gè)。
對(duì)于這個(gè)問(wèn)題杜秸,我保持中立(要是我說(shuō)不需要的話放仗,這篇博客就不用寫(xiě)了_),當(dāng)有需求來(lái)的時(shí)候就做吧撬碟。網(wǎng)上也很多這方面的文章诞挨,有不少實(shí)現(xiàn),但是每種方法都有它的優(yōu)缺點(diǎn)呢蛤,下面就把這些方法都測(cè)試惶傻,然后分析總結(jié)一下吧。
本文測(cè)試使用的API版本為23其障,Android6.0,minSDK為API16.
準(zhǔn)備工作
要測(cè)試肯定要先寫(xiě)demo银室,在這里先準(zhǔn)備demo,里面的Activity跳轉(zhuǎn)關(guān)系是這樣的:
所有Activity的啟動(dòng)模式是默認(rèn)模式standard静秆,后面測(cè)試需要再改粮揉。
寫(xiě)了一個(gè)Application子類(lèi)BaseApplication,用
registerActivityLifecycleCallbacks()
來(lái)監(jiān)聽(tīng)每個(gè)Activity的生命周期改變抚笔,和輸出當(dāng)前進(jìn)程ID(這個(gè)后面有用):
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated: " + activity.getLocalClassName());
Log.d(TAG, "Pid: " + + Process.myPid());
}
@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());
}
});
每個(gè)Activity都有兩個(gè)Button,一個(gè)用來(lái)調(diào)用退出APP的方法(CloseButton):
public void onClick(View v) {
Log.d(BaseApplication.getTAG(), "按下Close———————————————————————————— ");
exitAPP1();
// exitAPP2();
// exitAPP3();
// exitAPP4();
// exitAPP5();
}
一個(gè)用來(lái)調(diào)用下面那幾種行不通的方法侨拦,用來(lái)測(cè)試研究一下(TestButton):
public void onClick(View v) {
Log.d(BaseApplication.getTAG(), "按下Test———————————————————————————— ")
System.exit(0);
// Runtime.getRuntime().exit(0);
// Process.killProcess(Process.myPid());
// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// activityManager.killBackgroundProcesses(context.getPackageName());
// activityManager.restartPackage(context.getPackageName());
}
到了這里殊橙,準(zhǔn)備工作就做好了,由于代碼較多狱从,這里就不全貼了膨蛮,具體的實(shí)現(xiàn)代碼大家可以到demo地址去下。
網(wǎng)上流傳的幾種不行的方法
網(wǎng)上一些比較舊的資料季研,有一些經(jīng)過(guò)我測(cè)試后敞葛,發(fā)現(xiàn)行不通的方法(不知道是不是當(dāng)時(shí)他們用的時(shí)候行得通而到現(xiàn)在行不通)。
System.exit(0)方法
這個(gè)方法和Runtime.getRuntime().exit(0)
等價(jià)与涡,按道理來(lái)說(shuō)是結(jié)束當(dāng)前的虛擬機(jī)惹谐,經(jīng)過(guò)測(cè)試之后發(fā)現(xiàn)持偏,執(zhí)行后是關(guān)閉了當(dāng)前的虛擬機(jī),但是它還重新啟動(dòng)了一個(gè)新的虛擬機(jī)氨肌,把除了當(dāng)前Activity之外的其他未關(guān)閉的Activity按照順序重新啟動(dòng)過(guò)一遍鸿秆。簡(jiǎn)單來(lái)說(shuō),就是finish了當(dāng)前Activity怎囚,然后刷新了一遍APP卿叽。從下面的log可以看到,APP的進(jìn)程ID都變了(這里只開(kāi)兩個(gè)Activity是為了輸出簡(jiǎn)潔一點(diǎn)恳守,其實(shí)已做過(guò)各種各樣花式的Activity啟動(dòng)順序和啟動(dòng)模式的組合測(cè)試了考婴,結(jié)論還是一樣):
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())方法
這個(gè)方法按道理是殺死當(dāng)前線程,測(cè)試結(jié)果卻發(fā)現(xiàn)實(shí)際效果和上面的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
所以催烘,當(dāng)前Activity是當(dāng)前任務(wù)棧最后一個(gè)Activity沥阱,而APP又沒(méi)有其他任務(wù)棧的時(shí)候,調(diào)用這兩個(gè)方法是可以正常結(jié)束Activity的颗圣,就是和在第一個(gè)Activity調(diào)用finish差不多喳钟,不過(guò)finish不會(huì)結(jié)束進(jìn)程,這兩個(gè)方法會(huì)在岂。
至于上面兩個(gè)方法為什么會(huì)這樣奔则,和API的描述不符,有人說(shuō)是系統(tǒng)廠商的修改蔽午,有人說(shuō)是Google的安全機(jī)制易茬,本人才疏學(xué)淺,找不到原因及老,還請(qǐng)知道真相的各位告知一下抽莱,非常感謝。
ActivityManager.killBackgroundProcesses()方法
網(wǎng)上資料的寫(xiě)這個(gè)方法骄恶,看名字都覺(jué)得它不行了食铐,結(jié)果當(dāng)然是沒(méi)有效果,這個(gè)應(yīng)該是用于結(jié)束后臺(tái)進(jìn)程的方法僧鲁,但是不知道為什么被人拿來(lái)說(shuō)成結(jié)束當(dāng)前APP虐呻。
ActivityManager.restartPackage()方法
這個(gè)是很舊的方法了,現(xiàn)在都棄用了(據(jù)說(shuō)以前是可以的)寞秃,現(xiàn)在在我的API24版本斟叼,它只是一個(gè)包著killBackgroundProcesses()
方法的馬甲:
@Deprecated
public void restartPackage(String packageName) {
killBackgroundProcesses(packageName);
}
一鍵退出APP的方法
講了這么久終于進(jìn)入正題,所謂一鍵春寿,就是一調(diào)用這個(gè)方法朗涩,就可以關(guān)閉這個(gè)APP的意思,下面的幾個(gè)方法是一鍵關(guān)閉當(dāng)前APP里面所有的Activity绑改,并且結(jié)束APP進(jìn)程(要是不想結(jié)束可以不使用System.exit(0)
)谢床。
第一種方法——簡(jiǎn)單粗暴法
網(wǎng)上的有一些方法是模擬Activity棧來(lái)管理Activity兄一,這個(gè)方法不用模擬,直接調(diào)用系統(tǒng)API獲取當(dāng)前的任務(wù)棧萤悴,把里面的Activity全部finish掉瘾腰,再結(jié)束進(jìn)程(如果不想結(jié)束進(jìn)程,可以不調(diào)用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);
根據(jù)API源碼的注釋描述,這個(gè)ActivityManager.getAppTasks()方法的作用應(yīng)該是:
Get the list of tasks associated with the calling application.
但是經(jīng)過(guò)測(cè)試蹋盆,很多情況(一個(gè)APP里有多個(gè)任務(wù)棧,一個(gè)APP了開(kāi)了多個(gè)進(jìn)程)獲取的appTaskList里面只有一個(gè)AppTask硝全,只是獲取到了當(dāng)前的任務(wù)棧栖雾,注釋上不是說(shuō)會(huì)給出當(dāng)前運(yùn)行的APP的所有Task么,有知道的真相的請(qǐng)告知一下伟众。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 簡(jiǎn)單粗暴析藕,直接用Context獲取ActivityManager來(lái)獲取APP當(dāng)前任務(wù)棧就行了,不需要其他操作凳厢。
缺點(diǎn):
- 這個(gè)方法只能結(jié)束當(dāng)前任務(wù)棧账胧,對(duì)于APP有多個(gè)任務(wù)棧的情況(有Activity的啟動(dòng)模式是singleInstance),不會(huì)結(jié)束其他的后臺(tái)任務(wù)棧先紫,這就需要自己做邏輯判斷了治泥;
-
需要API21或以上,也就是Android5.0以上遮精,根據(jù)從AndroidStudio2.3.2獲得的數(shù)據(jù)居夹,目前Android5.0以上的手機(jī)和平板占比是40.5%(不知道準(zhǔn)不準(zhǔn),有沒(méi)有考慮中國(guó)國(guó)情)本冲。
第二種方法——保存管理法
這種方法在網(wǎng)上最流行了准脂,就是自己建立一個(gè)容器,來(lái)保存正在運(yùn)行的Activity的實(shí)例檬洞,在onCreate方法寫(xiě)入狸膏,在onDestroy方法寫(xiě)出,當(dāng)需要結(jié)束APP的時(shí)候把所有Activity實(shí)例拿出來(lái)finish掉添怔。這里我采用LinkedList环戈,增刪速度快。
在BaseApplication存放activityLinkedList,通過(guò)registerActivityLifecycleCallbacks()
方法來(lái)控制所有Activity實(shí)例的增刪澎灸。
@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();
}
}
結(jié)束APP的時(shí)候,調(diào)用exitAppList()
方法并結(jié)束進(jìn)程就可以了遮晚,還可以用showList()
來(lái)log一下當(dāng)前運(yùn)行的Activity名單:
private void exitAPP2() {
BaseApplication.showList();
BaseApplication.exitAppList();
System.exit(0);
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 這種方法不需要考慮到Activity有多個(gè)任務(wù)棧的情況性昭,無(wú)論啟動(dòng)模式是什么,只要Activity的創(chuàng)建和結(jié)束時(shí)經(jīng)歷正常的生命周期——即創(chuàng)建時(shí)經(jīng)過(guò)onCreate方法县遣,結(jié)束時(shí)經(jīng)過(guò)onDestroy方法糜颠,都不能逃離ActivityList的“魔爪”汹族。
缺點(diǎn):
- 前面說(shuō)了Activity是要正常的經(jīng)歷生命周期,這種方法才不會(huì)出現(xiàn)問(wèn)題其兴。
下面我們來(lái)測(cè)試一下顶瞒,首先是按順序進(jìn)入到B0Activity,然后按下Test元旬,這里的Test是執(zhí)行一次System.exit(0)
榴徐,然后就會(huì)發(fā)現(xiàn)Close方法并不能正常結(jié)束所有Activity,只結(jié)束了當(dāng)前Activity:
上面這個(gè)問(wèn)題并不大匀归,因?yàn)閼?yīng)該沒(méi)有人會(huì)無(wú)端端地調(diào)用一下System.exit(0)
坑资,但是下面這個(gè)問(wèn)題比較大,我在B0Activity人為的加一個(gè)空指針穆端,讓進(jìn)入的時(shí)候會(huì)拋出空指針異常袱贮,讓APP Crash,然后發(fā)現(xiàn)APP也是重啟了進(jìn)程体啰,并且Activity椩芪。回退到A0Activity,這時(shí)候可以看到activityLinkedList里面只有一個(gè)A0Activity了荒勇,并不能實(shí)現(xiàn)一鍵退出的效果柒莉。
也就是說(shuō),采用這種方法枕屉,遇到Activity不是經(jīng)過(guò)正常生命周期創(chuàng)建和結(jié)束的情況常柄,是達(dá)不到退出的效果的(要是Activity非正常結(jié)束而且APP進(jìn)程沒(méi)有結(jié)束的話,activityLinkedList持有這個(gè)Activity的實(shí)例可能會(huì)導(dǎo)致內(nèi)存泄漏搀擂,不過(guò)目前我還沒(méi)看到過(guò)這種情況西潘,上面兩種情況都是進(jìn)程重啟了)。
第三種方法——釜底抽薪法
這種方法讓APP的入口Activity采用SingleTask啟動(dòng)模式哨颂,那樣如果在后面的Activity啟動(dòng)這個(gè)處于任務(wù)棧底部的Activity的時(shí)候喷市,就會(huì)調(diào)用它的onNewIntent()
方法,在這個(gè)方法里根據(jù)Intent判斷是否調(diào)用finish威恼,是的話那么整個(gè)任務(wù)棧的Activity就都結(jié)束了:
這里在MainActivity里重寫(xiě)onNewIntent()
方法:
//exitApp3()方法使用
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null) {
boolean isExitApp = intent.getBooleanExtra("exit", false);
if (isExitApp) {
this.finish();
}
}
}
退出時(shí)調(diào)用:
private void exitAPP3() {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("exit", true);
context.startActivity(intent);
System.exit(0);
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 不怕第二種方法那樣的Activity不走正常生命周期的情況品姓,實(shí)現(xiàn)比較簡(jiǎn)潔。
缺點(diǎn)
- 要讓MainActivity的啟動(dòng)模式限定為singleTask箫措;
- 這種方法也是只能結(jié)束當(dāng)前任務(wù)棧的Activity腹备,如果有啟動(dòng)模式為SingleInstance的Activity的話就要自己寫(xiě)邏輯判斷了。
第四種方法——RxBus退出法
使用RxBus當(dāng)作事件總線斤蔓,當(dāng)Activity在onCreate()的時(shí)候注冊(cè)訂閱:
//exitApp4()方法使用
private Disposable disposable;
//exitApp4()方法使用注冊(cè)訂閱
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();
}
}
});
}
在Activity的onDestroy()取消訂閱:
//exitApp4()方法使用取消訂閱
if (!disposable.isDisposed()){
disposable.dispose();;
}
當(dāng)需要退出的時(shí)候植酥,發(fā)送事件:
private void exitAPP4() {
RxBus.getInstance().post("exit");
System.exit(0);
}
這里RxBus的實(shí)現(xiàn)是基于RxJava2.0.1,參考了這篇文章,防止文章太長(zhǎng)這里就不貼了友驮,具體的實(shí)現(xiàn)可以去我的demo里看漂羊。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 可以與RxJava和RxBus結(jié)合,對(duì)于使用RxBus和RxJava的項(xiàng)目可以很容易地使用卸留。
缺點(diǎn)
- 需要RxJava和RxBus走越,對(duì)于不使用的RxJava來(lái)實(shí)現(xiàn)的APP不推薦使用,可以使用其他的方法耻瑟。
- 需要在每個(gè)Activity的
onCreate()
方法和onDestroy()
方法來(lái)注冊(cè)和取消訂閱旨指,這樣比較麻煩,不過(guò)可以采用一個(gè)Activity統(tǒng)一的基類(lèi)解決匆赃,但是這樣也有了要繼承統(tǒng)一基類(lèi)的麻煩淤毛。 - 和第二種一樣,要是出現(xiàn)Crash然后重啟進(jìn)程的話還是會(huì)失效算柳。
第五種方法——廣播監(jiān)聽(tīng)法
這種方法和上一種比較像低淡,通過(guò)讓每一個(gè)Activity在onCreate()和onDestroy()的時(shí)候注冊(cè)和注銷(xiāo)一個(gè)廣播接收器:
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();
}
}
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);
}
當(dāng)需要退出的時(shí)候調(diào)用:
private void exitAPP5() {
context.sendBroadcast(new Intent(BaseApplication.EXIT));
}
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 和第二種方法一樣,不需要考慮到Activity有多個(gè)任務(wù)棧的情況瞬项。
缺點(diǎn)
- 需要為每個(gè)打開(kāi)的Activity注冊(cè)廣播接收器蔗蹋,這樣比較麻煩,不過(guò)可以采用一個(gè)Activity統(tǒng)一的基類(lèi)解決囱淋,但是這樣也有了要繼承統(tǒng)一基類(lèi)的麻煩猪杭。
- 和第二種一樣,要是出現(xiàn)Crash然后重啟進(jìn)程的話還是會(huì)失效妥衣。
-
這種方法不能在后面加
System.exit(0)
來(lái)結(jié)束進(jìn)程皂吮,因?yàn)閳?zhí)行了發(fā)送廣播這個(gè)方法之后,不會(huì)等到廣播接收器收到廣播税手,程序就開(kāi)始執(zhí)行下一句System.exit(0)
蜂筹,然后就直接變成執(zhí)行System.exit(0)
的效果了。
總結(jié)
總的來(lái)說(shuō)芦倒,第二種方法——保存管理法比較實(shí)用艺挪,不過(guò)當(dāng)Android5.0普及之后,第一種方法應(yīng)該會(huì)用的比較多兵扬,因?yàn)橛玫絊ingleInstance啟動(dòng)模式Activity的APP應(yīng)該比較少麻裳。當(dāng)APPActivity數(shù)量不多,而且對(duì)啟動(dòng)模式?jīng)]有特別的需求的時(shí)候(也就是懶的時(shí)候)器钟,可以選擇第三種方法津坑。而第四種方法則是比較適合用到RxJava的時(shí)候使用。
參考文章
http://www.reibang.com/p/8cd954b43eed
http://johnnyshieh.me/posts/rxbus-rxjava2/
http://www.imooc.com/article/3300