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的状态和事件

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

/*** 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/FusedLocationProviderApilocationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManagerlocationManager?.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/FusedLocationProviderApilocationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManagerlocationManager?.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 = 30fval lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)lp.gravity = Gravity.CENTERsetContentView(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 = 30fval lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)lp.gravity = Gravity.CENTERsetContentView(chronometer, lp)val viewModel = ViewModelProviders.of(this).get(ChronometerViewModel::class.java)val startTime = viewModel.startTime ?: SystemClock.elapsedRealtime().also { viewModel.startTime = it }chronometer.base = startTimechronometer.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: Timerprivate val _liveData = MutableLiveData<Long>()val liveData: LiveData<Long>get() = _liveDatainit {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 = 30fval lp = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)lp.gravity = Gravity.CENTERsetContentView(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 uncheckedobserver.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// ownermActive = 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(生命周期感知组件用法和原理)相关推荐

  1. Lifecycle Activity和Fragment生命周期感知组件 LifecycleObserver MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. 打造一个生命周期感知的MVP架构

    很久没写Blog了,这两年也积累了比较多的知识和总结.也实现了不少的业务,以及针对部分业务的优化,发现呢有些知识还是可以分享出去的,只是之前一直在纠结会不会被人看不起什么的,之后才发现多虑了.毕竟放出 ...

  3. 用生命周期规范组件化流程

    写在前面 1. 组件划分 架构 宿主壳.调试壳 组件层 基础层 MVC.MVP.MVVM 如何下沉 Utils 规范:使用 Kotlin 静态方法 单例模式 res 规范:命名清晰 string.xm ...

  4. 应用生命周期、页面生命周期、组件生命周期

    一 应用生命周期 函数名 说明 应用场景 onLaunch 当 uni-app 应用初始化完成时触发,全局只触发一次 一般用于查看用户是否授权.获取用户的设备信息等 onShow 当应用启动,或从后台 ...

  5. 微信小程序自定义标签组件component封装、组件生命周期,组件通信

    微信小程序自定义标签组件component封装.组件生命周期,组件通信 本文来说下小程序的自定义标签组件封装. 相比于vue,react的非路由组件,微信小程序的component组件要麻烦些,而且生 ...

  6. [react] react的性能优化在哪个生命周期?它优化的原理是什么?

    [react] react的性能优化在哪个生命周期?它优化的原理是什么? shouldComponentUpdate 减少不必要的重新渲染 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易 ...

  7. react组件生命周期_React组件生命周期-挂钩/方法介绍

    react组件生命周期 React components have several lifecycle methods that you can override to run your code a ...

  8. vue笔记(三)生命周期、组件(嵌套)、数据传递

    生命周期文档 一.生命周期 1.参考一 2.参考二 二.自定义组件 1. 使用:<组件名></组件名> 2. 定义组件: (1)方法一:官网 let 组件变量名 = Vue.e ...

  9. Vue 学习笔记(2)Vue 生命周期、组件

    Vue Vue 生命周期 Vue 中组件(Component) 全局组件的开发 局部组件的开发 组件中 props 的使用 在组件上声明静态数据传递给组件内部 在组件上声明动态数据传递给组件内部 pr ...

  10. vue生命周期,组件,slot替换,tab切换,简易留言板

    data规范: data:(){ return{ arr:[{ a: "wan1", b: "在线", c: 5000},{ a: "wan2&quo ...

最新文章

  1. 黑客基础知识与防护(二)
  2. 在.NET环境下发送邮件
  3. PaaS下半场,任重且道远
  4. python3.82版本安装_CentOS7下安装Python3和Python2并存
  5. Windows下MySql安装【图文】
  6. Codis集群的搭建与使用
  7. css列表格式属性,css list-style-type属性笔记
  8. appium java 点击坐标_appium定位元素java篇【转】
  9. winform能连MySQL吗_c# winform中怎么连接mysql
  10. CSS之创建等高列布局之二
  11. oracle关联字段和序列,oracle(9) 序列和约束
  12. Node.js抓取网页信息并展示(cheerio网络爬虫)
  13. HTML5 dataset遍历,H5中data-xxxx属性
  14. 【HANA系列】SAP HANA SQL获取当前日期最后一天
  15. CISC 332*/CMPE 332* –Database Management Systems
  16. [安全攻防进阶篇] 十.熊猫烧香病毒机理IDA和OD逆向分析--病毒释放过程(中)
  17. 极客书的编程教程合集
  18. 关于icon小图标的实现
  19. rt-thread驱动篇(02)---STM32F429板卡外设驱动添加
  20. 人脸检测——基于face_recognition库

热门文章

  1. 书籍推荐:国内第一本ASP.NET 3.5 MVC技术专著
  2. 企信下载的文件在哪里_iTunes下载的固件在哪 iTunes固件下载地址【介绍】
  3. 51心形流水灯原理图PCB及程序简介
  4. 《高绩效成果教练》课程笔记及思考
  5. [SHOI2017]期末考试
  6. 中国IT风险投资机构
  7. 北京休闲好去处 适合春游的地方
  8. script for kettle send mail contect
  9. JS数组常用的方法shift,unshift,splice,split,slice
  10. Java实现新浪微博第三方登录