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行为是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值