原理解析Android开发Android开发

Android APP一键退出的方法总结分析

2017-06-09  本文已影响229人  炎之铠

本文出处
炎之铠csdn博客:http://blog.csdn.net/totond
炎之铠邮箱:yanzhikai_yjk@qq.com
本文demo地址:https://github.com/totond/TestAppExit
本文原创,转载请注明本出处!

前言——到底APP需不要退出功能

Google是推荐APP不需要退出功能的,因为只要把APP切到后台,系统的GC机制会自动根据内存情况来对后台进程进行回收,如果APP进程没被回收的话,用户还能快速切回APP。然后在用户的习惯和一些开发者对这种设计的不重视(例如我上一篇写的Application里面的onTrimMemory()方法就没什么人用),还加上以前的手机内存不是很大(GC有时来不及清理后台,导致手机有时会变得很卡),就导致了这种退出APP的需求比较多(如很多APP的连点两下后退键退出功能),到了现在这个问题还是有很多人争论,可以看看这个
  对于这个问题,我保持中立(要是我说不需要的话,这篇博客就不用写了_),当有需求来的时候就做吧。网上也很多这方面的文章,有不少实现,但是每种方法都有它的优缺点,下面就把这些方法都测试,然后分析总结一下吧。

本文测试使用的API版本为23,Android6.0,minSDK为API16.

准备工作

要测试肯定要先写demo,在这里先准备demo,里面的Activity跳转关系是这样的:


  所有Activity的启动模式是默认模式standard,后面测试需要再改。
  写了一个Application子类BaseApplication,用registerActivityLifecycleCallbacks()来监听每个Activity的生命周期改变,和输出当前进程ID(这个后面有用):
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());
            }
        });

每个Activity都有两个Button,一个用来调用退出APP的方法(CloseButton):

    public void onClick(View v) {
        Log.d(BaseApplication.getTAG(), "按下Close———————————————————————————— ");
        exitAPP1();
//        exitAPP2();
//        exitAPP3();
//        exitAPP4();
//        exitAPP5();
    }

一个用来调用下面那几种行不通的方法,用来测试研究一下(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());
    }

到了这里,准备工作就做好了,由于代码较多,这里就不全贴了,具体的实现代码大家可以到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);
    }

一键退出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);

根据API源码的注释描述,这个ActivityManager.getAppTasks()方法的作用应该是:

    Get the list of tasks associated with the calling application.

但是经过测试,很多情况(一个APP里有多个任务栈,一个APP了开了多个进程)获取的appTaskList里面只有一个AppTask,只是获取到了当前的任务栈,注释上不是说会给出当前运行的APP的所有Task么,有知道的真相的请告知一下。

优缺点

优点:

缺点:

第二种方法——保存管理法

这种方法在网上最流行了,就是自己建立一个容器,来保存正在运行的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();
        }
    }

结束APP的时候,调用exitAppList()方法并结束进程就可以了,还可以用showList()来log一下当前运行的Activity名单:

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

优缺点

优点:

缺点:

第三种方法——釜底抽薪法

这种方法让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();
            }
        }
    }

退出时调用:

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

优缺点

优点

缺点

第四种方法——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();
                        }
                    }
                });
    }

在Activity的onDestroy()取消订阅:

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

当需要退出的时候,发送事件:

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

这里RxBus的实现是基于RxJava2.0.1,参考了这篇文章,防止文章太长这里就不贴了,具体的实现可以去我的demo里看。

优缺点

优点

缺点

第五种方法——广播监听法

这种方法和上一种比较像,通过让每一个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();
    }
}
    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);
    }

当需要退出的时候调用:

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

优缺点

优点

缺点

总结

总的来说,第二种方法——保存管理法比较实用,不过当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

上一篇 下一篇

猜你喜欢

热点阅读