美文网首页
lifecycle-aware components(生命周期感

lifecycle-aware components(生命周期感

作者: AItsuki | 来源:发表于2019-02-28 14:30 被阅读0次

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

    相关文章

      网友评论

          本文标题:lifecycle-aware components(生命周期感

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