lifecycle-aware components(生命周期感知组件用法和原理)

Android and Architecture
Android lifecycle-aware components codelab
https://github.com/googlecodelabs/android-lifecycles
savedInstanceState和 fragment.setRetainInstance以及 viewmodel的区别

Demo使用方式:

https://github.com/AItsuki/LifecycleAwareCodable

文章配合Demo更加容易理解:
下载好之后执行以下命令
MacOS / Linux:

mkdir -p .idea/runConfigurations
cp runConfigurations/* .idea/runConfigurations/

windows

MKDIR .idea\runConfigurations
COPY runConfigurations\* .idea\runConfigurations\

然后可以选择运行对应的章节


1. Separation of concerns(分离关注点)

在很多时候,我们需要在ActivityonCreatedonDestroy方法中注册和释放资源。我们可能还需要防止手机屏幕旋转,分屏之后数据销毁重新拉取。

就拿我们常用的MVP + RxJava来说,在Presenter中,我们通常需要在外部手动调用presenter.destroy方法,以便于取消RxJava的订阅,防止内存溢出。而为了避免重复请求数据,在Activity因为配置发生变化(如旋转屏幕)销毁重建时想要保存数据更加麻烦。

为此使用lifecycle-aware components可以轻松做到这些事情。

2. Lifecycle、LifecycleOwner、LifecycleObserver

Demo选择Step1和Step1Think运行

生命周期感知的能力主要由以上三个对象赋予。

  • Lifecycle: 封装的生命周期对象,保存了当前Activity,Fragment当前生命周期状态。同时也是一个事件源,可以被其他已注册的观察者监听。
  • LifecycleOwner: 生命周期提供者,或者说生命周期本身,提供Lifecycle对象。它的实现为Fragment和Activity。
  • LifecycleObserver: 生命周期观察者,可以将它注册到Lifecycle监听生命周期时间。

下图为Lifecycle的状态和事件


Lifecycle States and Events

现在已经可以利用这三个对象创建一个自定义的生命周期感知组件。

/**
 * Create by AItsuki on 2019/2/26.
 */
class BoundLocationManager {

    companion object {
        fun bindLocationListenerIn(
            context: Context,
            lifecycleOwner: LifecycleOwner,
            listener: LocationListener
        ) = BoundLocationListener(context, lifecycleOwner, listener)
    }
}

class BoundLocationListener(
    private val context: Context,
    lifecycleOwner: LifecycleOwner,
    private val listener: LocationListener
) : LifecycleObserver {

    init {
        lifecycleOwner.lifecycle.addObserver(this)
    }

    private var locationManager: LocationManager? = null

    @SuppressLint("MissingPermission")
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun addLocationListener() {
        // Note: Use the Fused Location Provider from Google Play Services instead.
        // https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi
        locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
        locationManager?.run {
            requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, listener)
            Log.d("BoundLocationMgr", "Listener added")

            // Force an update with the last location, if available.
            val location = getLastKnownLocation(LocationManager.GPS_PROVIDER)
            listener.onLocationChanged(location)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun removeLocationListener() {
        locationManager?.run {
            removeUpdates(listener)
            Log.d("BoundLocationMgr", "Listener removed");
        }
        locationManager = null
    }
}

步骤:

  1. 实现LifecycleObserver,用@OnLifecycleEvent注解生命周期回调方法
  2. 注册到LifecycleObserver到Lifecycle

内存泄漏的原因主要是因为Activity引用被长期持有无法回收,上面代码在onPause中释放引用,并在onResume中重新引用,理论上不会出现内存泄漏。
但实际上上面代码还是泄露了, 这不是我们的做法有误,而是LocationManager本身的Bug,locationManager.removeUpdates()无法正确的移除监听器, 而监听器为内部类,持有Activity引用,引发泄漏。

可以用弱引用解决这个问题,并且context要使用application的。

/**
 * Create by AItsuki on 2019/2/26.
 * https://stackoverflow.com/questions/43135948/memory-leak-when-removing-location-update-from-a-fragment-in-onpause
 */
class BoundLocationManager {

    companion object {
        fun bindLocationListenerIn(
            context: Context,
            lifecycleOwner: LifecycleOwner,
            callback: (Location?) -> Unit
        ) {
            val locationCallback = object : LocationCallback {
                override fun onLocationChanged(location: Location?) {
                    callback.invoke(location)
                }
            }
            BoundLocationListener(context.applicationContext, lifecycleOwner, WeakLocationListener(locationCallback))
        }
    }
}

interface LocationCallback {
    fun onLocationChanged(location: Location?)
}


private class BoundLocationListener(
    private val context: Context,
    lifecycleOwner: LifecycleOwner,
    private val listener: LocationListener
) : LifecycleObserver {

    init {
        lifecycleOwner.lifecycle.addObserver(this)
    }

    private var locationManager: LocationManager? = null

    @SuppressLint("MissingPermission")
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun addLocationListener() {
        // Note: Use the Fused Location Provider from Google Play Services instead.
        // https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi
        locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
        locationManager?.run {
            requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, listener)
            Log.d("BoundLocationMgr", "Listener added")

            // Force an update with the last location, if available.
            val location = getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
            listener.onLocationChanged(location)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun removeLocationListener() {
        locationManager?.run {
            removeUpdates(listener)
            Log.d("BoundLocationMgr", "Listener removed");
        }
        locationManager = null
    }
}

/**
 * LocationManager本身有内存泄漏问题,它无法正常的释放Listener
 */
private class WeakLocationListener(callback: LocationCallback) : LocationListener {

    private val callback = WeakReference<LocationCallback>(callback)

    override fun onLocationChanged(location: Location?) {
        callback.get()?.onLocationChanged(location)
    }

    override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
    }

    override fun onProviderEnabled(provider: String?) {
    }

    override fun onProviderDisabled(provider: String?) {
    }
}

Lifecycle的原理思考:

Fragment中持有一个LifecycleRegistry对象,它是Lifecycle的实现。在Fragment的各个生命周期中调用LifecycleRegistry对象的handleLifecycleEvent()方法记录状态和分发事件。
而在Activity中,则是绑定一个用户不可见的ReportFragment,通过这个ReportFragment记录状态和分发事件。
使用Fragment记录生命周期是有好处的,在Fragment添加到Activity的时候,Fragment的生命周期会从onAttach开始逐一回调,也就是说即使当前Activity处于onResume状态,Fragment依然会发射onCreated事件,类似Rxjava中的BehaviorSubject

典型的例子就是在Activity的onResume之后才注册观察者,但是观察者依然能收到onCreated事件。
例如:

/**
 * Create by AItsuki on 2019/2/26.
 */
class LifecycleThinkActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 延迟5秒注册观察者
        window.decorView.postDelayed({
            Log.d("lifecycle", "addObserver")
            lifecycle.addObserver(ThinkObserver())
        }, 5000)

        Log.d("lifecycle", "onInitialize")
    }
}

class ThinkObserver : LifecycleObserver {
    // ON_ANY表示接收所有生命周期事件
    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
    fun onLifecycleChange(owner: LifecycleOwner, event: Lifecycle.Event) {
        Log.d("lifecycle", "observeEvent = ${event.name}, currentState = ${owner.lifecycle.currentState}")
    }
}
2019-02-26 19:25:17.871 D/lifecycle: onInitialize
2019-02-26 19:25:22.954 D/lifecycle: addObserver
2019-02-26 19:25:22.960 D/lifecycle: observeEvent = ON_CREATE, currentState = RESUMED
2019-02-26 19:25:22.960 D/lifecycle: observeEvent = ON_START, currentState = RESUMED
2019-02-26 19:25:22.961 D/lifecycle: observeEvent = ON_RESUME, currentState = RESUMED
2019-02-26 19:26:04.360 D/lifecycle: observeEvent = ON_PAUSE, currentState = STARTED
2019-02-26 19:26:04.747 D/lifecycle: observeEvent = ON_STOP, currentState = CREATED
2019-02-26 19:26:04.751 D/lifecycle: observeEvent = ON_DESTROY, currentState = DESTROYED

注意看第三和第四条日志的事件和状态不是对应的,证明了上面所说的话,即使当前Activity处于onResume状态,Fragment依然会发射onCreated事件。这是由Fragment特性带来的结果,恰好而又完美。

3. ViewModel(持有数据越过Activity配置变化)

Demo选择Step2Activity运行可以看效果

旋转屏幕等原因导致Activity生命周期重走,想要在这过程保存数据有点困难。
而ViewModel提供了这一个功能,只有当Activity真正销毁的时候ViewModel才会被销毁,它主要负责保存视图状态和业务逻辑处理。


来看这么个计时页面,当屏幕旋转时,因为onCreate重新执行的原因,所以计时也从头开始了。

/**
 * Create by AItsuki on 2019/2/26.
 */
class ChronometerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val chronometer = Chronometer(this)
        chronometer.textSize = 30f
        val lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
        lp.gravity = Gravity.CENTER
        setContentView(chronometer, lp)
        chronometer.start()
    }
}

可以在onSaveInstanceState()方法中保存,在onCreate()中取出重新设置。
也可以通过ViewModel持有,实现一个ViewModel非常简单,只需要继承ViewModel即可,然后使用ViewModelProvides类提供该ViewModel实例,切勿自己new一个出来,直接new出来的ViewModel和普通对象一样,没有绑定生命周期的特性。

/**
 * Create by AItsuki on 2019/2/26.
 */
class ChronometerViewModel : ViewModel() {
    var startTime: Long? = null

    // onCleared方法是在Activity真正结束时才调用
    override fun onCleared() {
        super.onCleared()
        Log.d("ChronometerVM", "onCleared, startTime = $startTime")
    }
}
/**
 * Create by AItsuki on 2019/2/26.
 */
class ChronometerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val chronometer = Chronometer(this)
        chronometer.textSize = 30f
        val lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
        lp.gravity = Gravity.CENTER
        setContentView(chronometer, lp)

        val viewModel = ViewModelProviders.of(this).get(ChronometerViewModel::class.java)
        val startTime = viewModel.startTime ?: SystemClock.elapsedRealtime().also { viewModel.startTime = it }
        chronometer.base = startTime
        chronometer.start()
    }
}

分析ViewModel对象是怎么越过onConfigurationChange存活的

  • 在support包下的,Activity内部持有一个setRetainInstance(true)HolderFragment,而ViewModel就是保存在HolderFragment中,而setRetianInstance的原理是将Fragment保存到NonConfigurationInstances中。
  • 在AndroidX包下,直接将ViewModel保存到NonConfigurationInstances中。

NonConfigurationInstances是FragmentActivity的一个静态内部类,作用就和它的命名一样,当Activity因为配置修改而重新生成实例时,这个NonConfigurationInstances的实例会在原Activity销毁之前传递到新的Activity上,ViewModelStore就是用来保存ViewModel的地方了,而FragmentViewModelStore则是保存在了FragmentManagerNonConfig中。

注意:以下源码都来自于AndroidX - 1.0.2

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

关于NonConfigurationInstances我找到了一篇更详细的文章说明,感谢:savedInstanceState和 fragment.setRetainInstance以及 viewmodel的区别

另外,ViewModel在真正销毁时会回调onCleared方法,而它的逻辑很简单,在FragmentActivity执行onDestory的时候判断。

/**
 * Destroy all fragments.
 */
@Override
protected void onDestroy() {
    super.onDestroy();

    if (mViewModelStore != null && !isChangingConfigurations()) {
        mViewModelStore.clear();
    }
    mFragments.dispatchDestroy();
}

ViewModel不建议引用View,Lifecycle或任何可能包含对Activity context的引用的类,可能会导致内存泄漏。如果需要Application,可以继承ApplicationViewModel。

4. LiveData

Demo Step3,Step3 - MediaLiveData

LiveData是一个生命周期感知的数据持有类。用以下代码体验下

/**
 * Create by AItsuki on 2019/2/26.
 */
class LiveDataChronometerViewModel : ViewModel() {

    private var timer: Timer
    private val _liveData = MutableLiveData<Long>()
    val liveData: LiveData<Long>
        get() = _liveData

    init {
        val startTime = SystemClock.elapsedRealtime()
        timer = timer(period = 1000, action = {
            val newValue = (SystemClock.elapsedRealtime() - startTime) / 1000
            _liveData.postValue(newValue)
        })
    }

    override fun onCleared() {
        timer.cancel()
    }
}
/**
 * Create by AItsuki on 2019/2/26.
 */
class ChronometerActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val textView = TextView(this)
        textView.textSize = 30f
        val lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
        lp.gravity = Gravity.CENTER
        setContentView(textView, lp)

        val viewModel = ViewModelProviders.of(this).get(LiveDataChronometerViewModel::class.java)
        viewModel.liveData.observe(this, Observer {
            Log.d("Chronometer2", "$it")
            textView.text = "$it"
        })
    }
}

只有在Lifecycle状态为Resumed的情况下,日志才会打印。也就是说LiveDataActivity onResume()的时候才响应事件。并且在Activity onDestroy()时,会自动取消订阅。

如果需要LiveData在任何时候都响应事件,使用observeForever(Observer)的订阅方式。
如下,不需要传LifecycleOwner进去了,但是这种方式不会绑定生命周期,需要我们在合适的时候手动取消订阅。

private val observer = Observer<Long> { }
viewModel.liveData.observeForever(observer)
viewModel.liveData.removeObserver(observer) // 手动取消订阅

LiveData的行为

LiveData.observe(LifecycleOwner,  Observer)

从上面这行代码可以看出,LiveData的生命周期感应是因为传递了LifecycleOwner。事实上也如此,就和step1那样,LiveData内部实现了一个生命周期感应组件,然后注册到Lifecycle

LiveData的数据源通过setValuepostValue方法设置,当Lifecycle状态为STARTEDRESUMED时才会发射数据。如果在Lifecycle STARTED之前多次设置数据源,会将数据缓存起来,但是只会缓存最新那个。

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++; // Observer用version判断该Data是否已经响应过
    mData = value;
    dispatchingValue(null);
}

dispatchingVaule(observer)是负责分发事件到observer,如果参数传null,则分发给所有订阅的observer。而它的内部调用considerNotify发射事件,只有在Lifecycle活动时才发射。

private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // 再次判断生命周期,mActive可能不是最新记录。
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

ObserverWrapper中的方法,当Lifecycle状态变化时会回调activeStateChanged,切换到活动状态时会分发事件,至于发射不发射就需要看Observerversion了。

void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    // immediately set active state, so we'd never dispatch anything to inactive
    // owner
    mActive = newActive;
    boolean wasInactive = LiveData.this.mActiveCount == 0;
    LiveData.this.mActiveCount += mActive ? 1 : -1;
    if (wasInactive && mActive) {
        onActive();
    }
    if (LiveData.this.mActiveCount == 0 && !mActive) {
        onInactive();
    }
    if (mActive) {
        dispatchingValue(this);
    }
}

在这里还有一个比较重要的特性,如果LiveData在活动状态时调用了setValue发射了一项数据,此时再去注册一个Observer,发射数据在前,观察者注册在后,请问后面注册的这个Observer能接收到事件吗。

这就要看Observer在注册时会不会回调activeStateChanged了。

其实结果已经很明确,在step1 - think中已经测试过。一个Observer在注册到Lifecycle时,会回调一次所有的之前的生命周期,所以activeStateChanged会调用,新添加进去的Observer会立即收到事件。

LiveData的行为总结为以下两点:

  1. LiveData只有在Activity在前台的时候才会回调setValue或postValue,如果在Activity在后台时多次调用setValue,只会响应最后一次setValue设置的值。

  2. 如果LiveData已经提前调用过setValue,后面再注册Observer时,该Observer还是会响应前面的setValue这个事件。

MediatorLiveData

MediatorLiveData是LiveData的子类,它可以观察多个LiveData并对他们的onChanged做出响应。

 LiveData liveData1 = ...;
 LiveData liveData2 = ...;

 MediatorLiveData liveDataMerger = new MediatorLiveData<>();
 liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
 liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));

现在,liveData1或者liveData2触发onChanged时,都会回调到liveDataMerger的的onChanged上。

再看以下这个例子,liveData1发射10个值之后就取消对他的监听。

liveDataMerger.addSource(liveData1, new Observer() {
      private int count = 1;

      @Override public void onChanged(@Nullable Integer s) {
          count++;
          liveDataMerger.setValue(s);
          if (count > 10) {
              liveDataMerger.removeSource(liveData1);
          }
      }
 });

另外,在addSource一个liveData的时候,如果liveData中已经有值(已经调用过setValue),那么liveDataMerger在Activity在前台时就会立即收到onChanged事件,这和上面的LiveData行为是一样的。

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

推荐阅读更多精彩内容