本文出处: 炎之铠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