前言
承接上篇的学习顺序,本文主要是对 ViewModel
的学习。ViewModel
是用来保存 UI 数据的类,并且会在配置变更后(如屏幕旋转)继续存在。先总结下 ViewModel
的特点:
-
提供 UI 界面的数据
-
负责和数据层通讯
-
配置变更后继续存在
官方文档建议,我们应将应用的 UI 数据保存在 ViewModel
中,而不是 Activity
中,确保数据不会受到 Configuration Change
的影响。 尽量弱化 Activity
的职责, Activity
仅负责如何在屏幕上显示数据和接受用户互动。
如果系统销毁或重新创建 UI 控制器,存储在其中的临时数据会造成丢失。例如:在某个 Activity
中展示用户列表,因配置变更导致 Activity
重新创建。新创建的 Activity
必须重新获取用户列表。对于简单的数据,可使用 onSaveInstanceState()
存储,并在 onCreate()
中通过 Bundle
进行数据恢复。但是这种方法仅适用于少量数据,不适用存储大量数据(如:用户列表和 bitmaps
)。
另一个问题是,UI 控制器经常需要接受一些异步回调。UI 控制器需要管理这些异步回调,确保在界面销毁时,不会发生潜在的内存泄漏问题。这种处理方式需要耗费大量的精力,并且在配置变更重新创建对象时,重新联网获取数据也会造成资源的浪费。
Activiy
和 Fragment
主要是用来显示 UI 数据,接受用户的交互请求或者处理系统通讯(权限请求)。如果把从数据库或网络获取的数据,都一窝蜂的堆积在 Activity
获 Fragment
中。会造成该类代码膨胀,为日后的维护埋下了隐患。为 UI 控制器分配过多的任务,违背了单一职责原则,也会使单元测试变得困难。
将数据和 UI 分离,将会让开发和维护变得更加高效和容易。啰嗦了这么多,下面正式进入本文的主题 ViewModel
。
实现 ViewModel
Android Jetpack Components 提供了 ViewModel
类,用来给 UI 控制器提供数据。ViewModel
在配置变更时自动保留,以便保存的数据用于下一个 Activity
或 Fragment
的实例。下面是一个计数器的例子,来展示 ViewModel
的数据在配置变更后继续存在的特性。
class MainViewModel : ViewModel() {
var count = 0
}
我们把按钮点击次数的 count
属性保存在 ViewModel
中,接下来在 Activity
中使用。
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
btn.setOnClickListener {
mainViewModel.count++
tvCount.text = "点击次数:${mainViewModel.count}"
}
}
通过一个 TextView
显示按钮点击的次数,当旋转屏幕,Activity
重新创建,但 count
被没有被销毁。
如果 Activity
重新创建,它将接受到由第一个 Activity
创建的相同 MainViewModel
实例。当 Activity
关闭,framework
层会调用 ViewModel
的 onCleared()
释放资源。但需要注意的是,开发者应该自己实现 onCleared()
,而不用关心 onCleared()
的调用时机。
如果你的 ViewModel
需要通过构造函数传递参数,可以使用 ViewModelFactory
来创建自定义构造函数。如:
class LoginViewModelFactory(
private val repo: LoginDataSourceRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
LoginViewModel(repo) as T
}
注意: 不要将
Context
传入ViewModel
中,也就是说ViewModel
中不应当持有Activity
,Fragment
,View
的引用。
如果 ViewModel
需要 Application
的 context
对象(如:使用系统服务),可以继承自 AndroidViewModel
。
ViewModel 的生命周期
当获取 ViewModel
时,ViewModel
对象的范围限定为传递给 ViewModelProvider
的 LifecycleOwner
对象。当 Activity
finsih 或者 Fragment
detach 时,ViewModel
将会一直保留在内存中。
下图展示了 Activity
和 ViewModel
的生命周期
我们应当在系统第一次调用 Activity
的 onCreate()
时,去获取 ViewModel
对象。当系统因配置变更时,重新创建 Activity
时,ViewModel
还是第一次获取到的实例。
Fragment 共享数据
在Activity
中内嵌一个或者多个 Fragment
是常见的做法。一般情况下,Fragment
之间通信,都是采用接口回调或者 EventBus
。现在又多了一个新的选择 ViewModel
,以下是官方的示例代码:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
由于 MasterFragment
和 DetailFragment
拥有相同的宿主 Activity
,因此获取到的 ViewModel
示例也是一样的。
源码解析
在分析源码前,我们先思考下面两个问题:
-
ViewModel
是通过什么存储的? -
系统在因配置变更,是如何保留
ViewModel
的实例?
1. ViewModelProviders
还是以 MainActivity
中的代码作为切入点。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("onCreate")
setContentView(R.layout.activity_main)
// 步骤 1
val viewModelProvider = ViewModelProviders.of(this)
// 步骤 2
val mainViewModel = viewModelProvider.get(MainViewModel::class.java)
}
}
通过 of()
方法,可以获取一个 ViewModelProvider
示例。
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(ViewModelStores.of(activity), factory);
}
2. ViewModelStores
ViewModelStores
主要是用来提供 ViewModelStore
的实例。通过 of()
返回一个 ViewModelStore
。
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if (activity instanceof ViewModelStoreOwner) {
return ((ViewModelStoreOwner) activity).getViewModelStore();
}
return holderFragmentFor(activity).getViewModelStore();
}
3. ViewModelStoreOwner
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
在 SDK
27 及以上版本,supprot
包下的 FragmentActivity
和 Fragment
实现了 ViewModelStoreOwner
。该接口主要是在配置变更时,保留原有的 ViewModelStore
。
4. ViewModelStore
ViewModelStore
在内部通过 HashMap
来存放 ViewModel
。当 ViewModelStoreOwner
因配置发生变更时,该类会被系统保留,确保新创建的 Activity
能获取到和之前一样的 ViewModelStore
。
当 ViewModelStoreOwner
被销毁,并且不会新建时。ViewModelStore
的 clear()
将会调用,继而调用 ViewModel
的 onCleared()
释放资源。
到目前为止,对于提出的第一个问题。我们已经清楚了。现在让我们进入 FragmentActivity
中查看系统是如何保存 ViewModelStore
实例的。
FragmentActivity
- 从
onCreate()
出发
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
...
}
现在可以肯定的是,ViewModelStore
对象是保存在 NonConfigurationInstances
中。getLastNonConfigurationInstance()
是定义在 Activity
中的,该方法用来获取之前 onRetainNonConfigurationInstance()
返会的 Object
对象。
* @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
*/
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
- 现在我们看下
Activity
中的onRetainNonConfigurationInstance()
。
public Object onRetainNonConfigurationInstance() {
return null;
}
通过注释可以发现,该方法在 Activity
因配置变更并销毁的时候由系统调用,具体的调用时机是在 onStop()
和 onDestory()
之间。
- 接下来我们查看
FragmentActivity
中onRetainNonConfigurationInstance
的具体实现。
public final Object onRetainNonConfigurationInstance() {
if (mStopped) {
doReallyStop(true);
}
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
高兴的是,我们在该方法中发现了 mViewModelStore
的身影。
现在让我们梳理下,系统对于 ViewModel
保存的逻辑。当 Activity
因配置变更销毁时,系统会调用 onRetainNonConfigurationInstance()
保存 ViewModel
。在新建 Activity
中的 onCreate()
方法通过 getLastNonConfigurationInstance()
获取 NonConfigurationInstances
。继而获取先前的 ViewModel
实例。
到此为止 步骤 1 中代码的执行流程就分析完了,步骤 2 的代码,我们简单看下。
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
当 Activity
因配置变更重建时,mViewModelStore
还是一开始创建的示例,因此返回的 ViewModel
对象和最初的一样。
总结
笔者是基于 SDK
版本 27 ,Lifecycle
版本 1.1.1 分析的。需要注意的是系统在 SDK
27 之前是通过一个不可见的 Fragment
,将 setRetainInstance()
设置为 true
进行处理的。笔者不再做过多分析,感兴趣的可自行研究。如分析有误,还多请指正。
参考资料:
官方文档
B 站视频讲解
网友评论