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(分离关注点)
在很多时候,我们需要在Activity
的onCreated
和onDestroy
方法中注册和释放资源。我们可能还需要防止手机屏幕旋转,分屏之后数据销毁重新拉取。
就拿我们常用的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
}
}
步骤:
- 实现
LifecycleObserver
,用@OnLifecycleEvent
注解生命周期回调方法 - 注册到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
的地方了,而Fragment
的ViewModelStore
则是保存在了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
的情况下,日志才会打印。也就是说LiveData
在Activity 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
的数据源通过setValue
或postValue
方法设置,当Lifecycle
状态为STARTED
或RESUMED
时才会发射数据。如果在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
,切换到活动状态时会分发事件,至于发射不发射就需要看Observer
的version
了。
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的行为总结为以下两点:
-
LiveData只有在Activity在前台的时候才会回调setValue或postValue,如果在Activity在后台时多次调用setValue,只会响应最后一次setValue设置的值。
-
如果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行为是一样的。
网友评论