美文网首页android架构Android开发Android开发经验谈
优雅的使用ActivityLifecycleCallbacks管

优雅的使用ActivityLifecycleCallbacks管

作者: 生椰拿铁锤 | 来源:发表于2017-09-14 19:28 被阅读269次

    一、ActivityLifecycleCallbacks接口介绍

    API 14之后,在Application类中,提供了一个应用生命周期回调的注册方法,用来对应用的生命周期进行集中管理,这个接口叫registerActivityLifecycleCallbacks,可以通过它注册自己的ActivityLifeCycleCallback,每一个Activity的生命周期都会回调到这里的对应方法。之前我们想做类似限制制定Activity个数的时候都要自己去添加和计数,有了ActivityLifeCycleCallback接口,所有Activity的生命周期都会在这里回调,我们可以根据条件随心处理。

    Activity生命周期图:

    这里写图片描述

    ActivityLifecycleCallbacks接口代码:

    这里写图片描述

    两者几乎是一一对应的,不管是做Activity的限制还是Activity的状态统计都是非常方便的,里面还有一个void onActivitySaveInstanceState(Activity activity, Bundle outState) 方法,非常方便我们来保存Activity状态数据,是不是很周到美滋滋!

    二、限制指定Activity的数量

    我们需要建立一个集合存储指定打开的Activity,使用java中的Stack集合最好,Stack堆栈 is a last-in-first-out (LIFO) stack of objects,先进后出很天然的复合Activity堆栈的容器集合:

    //这里我们为了限制商品详情页Activity的最多打开个数
        public static Stack<ActivityDetail> store = = new Stack<>();
    

    在只需要在Application中处理就可以了,请看代码:

    /**
     * Created by dawish on 2017/2/16.
     */
    public class App extends Application {
        private static App mApp;
        public static Stack<ActivityDetail> store;
        //商品详情页最多个数,这里为了测试只写了2,大家根据自己的情况设值
        private static final int MAX_ACTIVITY_DETAIL_NUM = 2;
    
        @Override
        public void onCreate() {
            super.onCreate();
            mApp = this;
            store = new Stack<>();
            //注册监听器
            registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());
        }
    
        public static App getAppContext() {
            return mApp;
        }
    
        private class SwitchBackgroundCallbacks implements ActivityLifecycleCallbacks {
    
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {
                if(activity instanceof ActivityDetail) {
                    if(store.size() >= MAX_ACTIVITY_DETAIL_NUM){
                        store.peek().finish(); //移除栈底的详情页并finish,保证商品详情页个数最大不超过指定
                    }
                    store.add((ActivityDetail) activity);
                }
            }
    
            @Override
            public void onActivityStarted(Activity activity) {
    
            }
    
            @Override
            public void onActivityResumed(Activity activity) {
    
            }
    
            @Override
            public void onActivityPaused(Activity activity) {
    
            }
    
            @Override
            public void onActivityStopped(Activity activity) {
    
            }
    
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
    
            }
    
            @Override
            public void onActivityDestroyed(Activity activity) {
                store.remove(activity);
            }
        }
    
        /**
         * 获取当前的Activity
         *
         * @return
         */
        public Activity getCurActivity() {
            return store.lastElement(); //返回栈顶Activity
        }
    }
    
    

    写的App不要忘记在manifest清单文件中注册一下。

    三、控制同一个商品只会有一个ActivitDetail被打开

    这个稍微麻烦点,我们需在在打开的商品ActivityDetail之前判断该商品的详情页是否已经在Activity栈中,这样的情况在电商APP中应该很常见吧,商品之间会互相推荐,我们点击推荐商品去打开详情页,因为会互相推荐,所以存在一个商品会被重复打开详情页。
    ActivityDetail的代码,关键的是要保持当前商品的ID,方便区分不同的商品详情页:

        private String ID;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_detail);
            AnnotateUtils.inject(ActivityDetail.this);
    
            ID = getIntent().getStringExtra("ID");
            currentGoodId.setText("当前详情页展示商品ID: "+ID);
    
        }
    
        public String getID() {
            return ID;
        }
    

    如果当前点击打开的商品详情页已经被打开了,我们直接把之前打开的ActivityDetail调到前台不就行了么?

        /**
         *
         * @param id
         * @return
         */
        public static boolean toGoodsDetail(String id){
    
            if(store == null || store.empty()){
                return false;
            }
            for(ActivityDetail activityDetail : store){
                if(id.equalsIgnoreCase(activityDetail.getID())){ //当前商品的详情页已经打开就直接调到前台
                    ActivityManager am = (ActivityManager) getAppContext().getSystemService(Activity.ACTIVITY_SERVICE);
                    am.moveTaskToFront(activityDetail.getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION);
                    return true;
                }
            }
            return false;
        }
    

    注意: ActivityManager 的moveTaskToFront是你需要在AndroidManifest.xml中添加"Android.permission.STOP_APP_SWITCHES"用户权限,前提是必须是系统应用才可以。

    扎心不?蛋疼不?
    想着实现除非你是手机厂商预装的App,或者是root掉手机,把你的app放在system/app目录下面,但是这是不可能的。

    最终的解决办法就是,要打开的商品详情页如果已经打开,我们就把之前的关闭掉,然后重新打开,这样就在 Activity的栈顶重新创建了一个。有点类似把之前打开的Activity调到了栈顶,但是之前的ActivityDetail的状态你可以自己保存下来传到到新的ActivityDetail中来恢复现场,做到隐藏式无缝。
    在每次打开商品详情页之前都调用一下App中的
    toGoodsDetail(String id)方法来检查:

        /**
         *
         * @param id
         * @return
         */
        public static boolean toGoodsDetail(String id){
    
            if(store == null || store.empty()){
                return false;
            }
            for(ActivityDetail activityDetail : store){
                if(id.equalsIgnoreCase(activityDetail.getID())){ //当前商品的详情页已经打开
                    activityDetail.finish();
    //                这是你需要在AndroidManifest.xml中添加"Android.permission.STOP_APP_SWITCHES"用户权限,前提是必须是系统应用才可以。
    //                ActivityManager am = (ActivityManager) getAppContext().getSystemService(Activity.ACTIVITY_SERVICE);
    //                am.moveTaskToFront(activityDetail.getTaskId(), 0);
                    return true;
                }
            }
            return false;
        }
    

    ActivityDetail中的实现:

    /**
     * Created by dawish on 2017/8/10.
     */
    
    public class ActivityDetail extends AppCompatActivity {
    
        @ViewInject(R.id.recGoods1)
        private Button recGoods1;
    
        @ViewInject(R.id.recGoods2)
        private Button recGoods2;
    
        @ViewInject(R.id.recGoods3)
        private Button recGoods3;
    
        @ViewInject(R.id.currentGoodsId)
        private TextView currentGoodsId;
    
        private String ID;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_detail);
            AnnotateUtils.inject(ActivityDetail.this);
    
            ID = getIntent().getStringExtra("ID");
            currentGoodsId.setText("当前详情页展示商品ID: "+ID);
    
        }
    
        public String getID() {
            return ID;
        }
    
        @OnClick({R.id.recGoods1, R.id.recGoods2, R.id.recGoods3})
        public void recGoodClick(View v){
    
            int id = v.getId();
    
            switch (id){
                case R.id.recGoods1:
                    String goodId1 = "101";
                    toGoodDetail(goodId1);
                    break;
                case R.id.recGoods2:
                    String goodId2 = "102";
                    toGoodDetail(goodId2);
                    break;
                case R.id.recGoods3:
                    String goodId3 = "103";
                    toGoodDetail(goodId3);
                    break;
            }
    
        }
    
        /**
         * 根据推荐商品的点击打开对应的详情页
         * @param id
         */
        public void toGoodDetail(String id){
            App.toGoodsDetail(id); //调用App中的方法去检测点击的商品详情页是否被打开,被打开就将其关闭
            Intent intent = new Intent(ActivityDetail.this, ActivityDetail.class);
            intent.putExtra("ID", id);
            startActivity(intent);
        }
    
    }
    

    效果图:


    这里写图片描述

    这样的话不管我们怎么点击下面的推荐商品,同一个商品不会存在多个详情页,我们详情页的总个数也可以控制。完美!

    四、判断App前后台状态

    App 前后台的切换一般情况下都是按Home来进行,当然也有别的方式,但是此时Activity的生命周期是一样的:

    HOME键前后台切换Activity的执行顺序:onPause->onStop->onRestart->onStart->onResume

    BACK键前后台切换Activity键的顺序: onPause->onStop->onDestroy->onCreate->onStart->onResume

    其实按BACK按键就是退出app了,不算是前台后切换。

    现在我们知道App的由前台切换到后台所有打开的Activity会走:

    onPause->onStop

    后台切换到前台所有打开的Activity会走:

    ->onRestart->onStart->onResume

    前后台切换App所有打开的Activity的生命周期都是一样的,这样我就可以在ActivityLifecycleCallbacks回调接口中记录生命周期:

    App类最终完整代码:

    
    /**
     * Created by dawish on 2017/2/16.
     */
    public class App extends Application {
        //记录Activity的总个数
        public int count = 0;
        private static App mApp;
        public static Stack<ActivityDetail> store;
        //商品详情页最多个数,这里为了测试只写了2,大家根据自己的情况设值
        private static final int MAX_ACTIVITY_DETAIL_NUM = 2;
    
        @Override
        public void onCreate() {
            super.onCreate();
            mApp = this;
            store = new Stack<>();
            registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());
        }
    
        public static App getAppContext() {
            return mApp;
        }
    
        /**
         *
         * @param id
         * @return
         */
        public static boolean toGoodsDetail(String id){
    
            if(store == null || store.empty()){
                return false;
            }
            for(ActivityDetail activityDetail : store){
                if(id.equalsIgnoreCase(activityDetail.getID())){ //当前商品的详情页已经打开
                    activityDetail.finish();
    //                这是你需要在AndroidManifest.xml中添加"Android.permission.STOP_APP_SWITCHES"用户权限,前提是必须是系统应用才可以。
    //                ActivityManager am = (ActivityManager) getAppContext().getSystemService(Activity.ACTIVITY_SERVICE);
    //                am.moveTaskToFront(activityDetail.getTaskId(), 0);
                    return true;
                }
            }
            return false;
        }
    
        private class SwitchBackgroundCallbacks implements ActivityLifecycleCallbacks {
    
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {
                if(activity instanceof ActivityDetail) {
                    if(store.size() >= MAX_ACTIVITY_DETAIL_NUM){
                        store.peek().finish(); //移除栈底的详情页并finish,保证商品详情页个数最大为10
                    }
                    store.add((ActivityDetail) activity);
                }
            }
    
            @Override
            public void onActivityStarted(Activity activity) {
                if (count == 0) { //后台切换到前台
                    Log.v("danxx", ">>>>>>>>>>>>>>>>>>>App切到前台");
                }
                count++;
            }
    
            @Override
            public void onActivityResumed(Activity activity) {
    
            }
    
            @Override
            public void onActivityPaused(Activity activity) {
    
            }
    
            @Override
            public void onActivityStopped(Activity activity) {
                count--;
                if (count == 0) { //前台切换到后台
                    Log.v("danxx", ">>>>>>>>>>>>>>>>>>>App切到后台");
                }
            }
    
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
    
            }
    
            @Override
            public void onActivityDestroyed(Activity activity) {
                store.remove(activity);
            }
        }
    
        /**
         * 获取当前的Activity
         *
         * @return
         */
        public Activity getCurActivity() {
            return store.lastElement();
        }
    }
    

    Github代码请点击

    相关文章

      网友评论

        本文标题:优雅的使用ActivityLifecycleCallbacks管

        本文链接:https://www.haomeiwen.com/subject/iyxjsxtx.html