通过Lifecycle-Aware 组件处理生命周期[翻译]

引入概念

  • Lifecycle解决的问题:

    • 用于响应、管理其他应用组件(如ActivityFragment)的改变状态,相对于我们自己写事件监听回调接口,Lifecycle会更加简洁、易于管理。
    • 大部分应用组件都存在于Android Framework,生命周期绑定在此之上,并且直接由系统或者由应用进程框架管理,因此必须遵循它们的规则,避免内存泄露和应用崩溃。
  • 实际场景: 我们需要在Activity中显示设备的位置,通常会这样实现:

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
} 

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    } 
    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

  • 貌似看起来很不错,但是在实际应用中,最终会存在太多用于管理其他组件生命周期状态的调用,管理多个组件时会在生命周期方法中放置大量代码,例如 onStart()onStop(),这使得它们难以维护。
  • 此外,无法保证组件在ActivityFragment停止之前启动,这在我们需要执行耗时长的操作时尤为真实,比如我们在onStart()中检查某些配置,这就可能在当 onStop()onStart()之间完成的情况下 产生竞争条件,最终导致组件存活的时间比实际需要长。如下示例:
class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}
  • 针对以上问题,android.arch.lifecycle包提供了可弹性、解耦地解决这些问题的类和接口。

Lifecycle的概念

  • lifecycle是一个持有组件生命周期(ActivityFragment)状态的类,并且允许其他对象观察这一状态。

  • lifecycle 使用两个主要枚举类来处理与之绑定的组件的生命周期状态。

    • Event: 这个事件由系统框架和Lifecyle类分发,并且会映射到ActivitFragment的回调事件上。
    • State: 代表当前LIfecycle对象正在处理的组件的状态。
      思考`States`节点以及`Events`事件
  • 通过给方法添加注解的方式可以使这个类具备监听组件生命周期的能力,然后通过Lifecycle#addObserver()添加观察者即可赋予其他对象这个观察能力,如下示例:

    // 作为观察者,我们需要实现 LifecycleObserver 接口
    public class MyObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) // 在onResume时执行
        public void connectListener() {
            ...
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) // 在onPause时执行
        public void disconnectListener() {
            ...
        }
    }
    // 添加一个观察者,使得这个观察者也可以监听组件状态变化
    myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
    

LifeOwner的概念

  • LifeOwner只包含一个getLifecycle()方法,用于获取Lifecycle,使用时必须实现这个方法。如果想要管理整个应用进程的生命周期,可以使用ProcessLifecycleOwner代替

  • 这个接口从ActivityFragment等中抽取了Lifecycle的所有权,并且允许编写组件来与之配合,任何自定义的应用类都可以实现LifecOwner接口

  • 实现了LifecycleOwner的组件与实现了LifecycleObserver的组件运作方式是无缝衔接的的,因为Owner用于提供事件,而Observer用于注册、监听事件

  • 在前面我们定义了一个实现了LifecycleObserver接口的MyLocationLIstener类,我们可以如下面代码这样在onCreate中初始化,这意味响应生命周期变化的逻辑都提取到了MyLocationLIstener,而不是全部挤在Activity中,可见这样可以极大简化ActivityFragment的代码逻辑。

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;
    
        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
                // update UI
            });
            Util.checkUserStatus(result -> {
                if (result) {
                    myLocationListener.enable();
                }
            });
      }
    }
    
    • 为了避免在Lifecycle的不合适状态下执行回调,比如如果这个回调用于在Activity保存状态后执行Fragment的转场切换,就会触发崩溃,因此我们千万不要执行这个回调。为了简单处理这个问题,Lifecycle允许其他对象查看当前状态。
    class MyLocationListener implements LifecycleObserver {
        private boolean enabled = false;
        public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
           ...
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void start() {
            if (enabled) {
               // connect
            }
        }
    
        public void enable() {
            enabled = true;
            // 查看Lifecycle的当前状态
            if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
                // connect if not connected
            }
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void stop() {
            // disconnect if connected
        }
    }
    
    • 通过上面实现,我们的MyLocationListener就完全可以管理生命周期了,如果想要在其他ActivityFragment中使用它,那么只需要初始化一下就行了,其他处理操作都会在它内部处理。
    • 如果一个类库提供需要结合Android生命周期的处理类,那么建议使用Lifecycle-aware组件,这样的话类库客户端就可以轻易地整合这些组件而不需要手动地在客户端处理生命周期管理工作。
  • 实现自定义的 LifecycleOwner
    • Support Library 26.1.0以及上版本中,FragmentActivity已经实现了LifecycleOwner接口。
    • 如果需要自定义实现一个LifecycleOwner,那么可以使用LifecycleRegistry类,但是你需要发送事件到LifecycleRegistry类中,如下示例:
    public class MyActivity extends Activity implements LifecycleOwner {
        private LifecycleRegistry mLifecycleRegistry;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mLifecycleRegistry = new LifecycleRegistry(this);
            mLifecycleRegistry.markState(Lifecycle.State.CREATED);
        }
    
        @Override
        public void onStart() {
            super.onStart();
            mLifecycleRegistry.markState(Lifecycle.State.STARTED);
        }
    
        @NonNull
        @Override
        public Lifecycle getLifecycle() {
            return mLifecycleRegistry;
        }
    }
    

lifecycle-aware 组件最佳实践

  • 尽可能保证UI控制器,如ActivityFragment的简洁性,它们不应该请求它们自身的数据,而应交给ViewModel去做,并观察一个LiveData对象,用来将变化返回给UI视图。

  • 尽量编写数据驱动型(data-drivenUI,这种形式下,UI控制器只需要负责在数据改变时更新视图,或者通知用户动作给ViewModel

  • 将数据逻辑放到ViewModel类,ViewModel应当用作UI控制器和应用其他部分的连接器,但是注意,ViewModel不负责请求数据(比如网络请求等),相反,它只是调用数据请求模块去请求数据,然后将数据结果返回给UI控制器。

  • 使用DataBinding来维持视图与UI控制器间的简洁性。它可以可简化视图的声明和视图更新时所需在UI控制器中编写的代码,如果喜欢使用Java,那么建议使用类似于ButterKnife之类的类库来避免编写无聊的声明代码,并且它可以实现更好的抽象。

  • 如果UI很复杂,可以考虑创建一个Presenter类来处理UI更改操作,这可能很费事,但可以使UI组件更易于测试。

  • 禁止在ViewModel中引用View或者Activity上下文(context,否则如果ViewModel生命周期比Activity长时(比如configuration change的情况),Activity就会内存泄露而不被GC了。


lifecycle-aware 组件使用场景

lifecycle-aware组件可以在各种场景中让生命周期的管理更简单,比如以下场景:

  • 粗略定位(coarse-grained)与高精度定位(fine-grained)之间的更新状态切换。使用lifecycle-aware组件在应用处于前台时开启高精度定位,而在后台时开启粗略定位,可以结合LiveData来实现状态改变时更新UI的操作。
  • 开启和关闭视频缓冲。 比如使用lifecycle-aware组件尽快开启视频缓冲,而延迟到应用完全启动后才真正播放视频,同样也可以在应用关闭时终止缓冲动作。
  • 开启和关闭网络连接。 使用lifecycle-aware组件进行动态更新网络数据,如应用处于前台时自动加载数据,而应用切换至后台时自动暂停加载。
  • 启动和暂停Drawable动画。 前台时播放动画,后台是暂停动画。

处理 onStop 事件

Lifecycle关联到AppCompatActivityFragment时,它的状态会切换到CREATED,而ON_STOP状态则是会在AppCompatActivityFragmentonSaveInstanceState()被调用是触发。

如果AppCompatActivityFragment是通过onSaveInstanceState()中保存状态的,那么在ON_START被调用之前,它们的UI状态都会被认定为不可变的(immutable)。这时如果尝试在UI状态保存后修改UI的话,就会导致应用导航状态不一致,这也就是为什么在状态保存后执行FragmentTransactionFragmentManager会抛异常的原因了,具体看 commit()方法

如果LiveData的已经关联到LifecycleObserver还没到到达STARTED状态的话, LiveData可以通过终止observer的调用来避免上述边角情况的发生,这是因为LiveData会在执行Observer之前先调用isAtLeast()确定状态,然后再决定是否执行。

然而不幸的是,AppCompatActivityonStop()方法实在onSaveInstanceState()之后调用的,这种情况就导致已经保存的UI状态不允许改变,而Lifecycle又还没有到达STARTED状态。

为了避免这个问题的发生,在版本beta2及之前的Lifecycle类都会将这一状态标记为CREATED,而不分发这一事件,这样,任何检查当前状态的代码都能拿到真实状态值,即使这一事件还没有被分发,直到系统调用onStop()方法。

然而又不幸的是,这个解决方案有两大问题:

  • API 23及之前的版本,Android系统确实会保存Activity的状态,即使是由其他AActivity转换的部分,也就是说,系统调用onSaveInstanceState(),但是确实没有调用onStop()必要。这造成了一个潜在的长间隔期,而在这个间隔期之间,observer一直会认为Lifecycle是活动的,即使UI状态已经不能被改变了。
  • 任何想要暴露给LiveData类似行为的类都必须实现Lifecyclebeta2及之前版本所提供的解决方案。

Note: 为了简化流程并兼容老版本,请直接从版本1.0.0-rc1开始使用Lifecycle对象会被标记为CREATED,并且会在onSaveInstanceState()被调用是标记为ON_STOP状态,而无需等待onStop()的调用。虽然这并不会影响我们的代码,但是确是我们需要注意的,因为它没有遵循API 26及以前版本中Activity的生命周期调用次序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容