Android Jetpack之ViewModel
ViewModel简介
ViewModel综述
ViewModel是Google Jetpack组件中的一员,注重以生命周期的方式存储和管理UI界面相关的数据,防止内存泄漏。
另外可以让数据在发生配置更改(如屏幕旋转后)仍然存在,优于onSaveInstanceState()+onCreate()方式,因为后者只适合可以序列化的
少量数据,不适合大的数据,如列表等。
ViewModel相关的类
- ViewModel.java类:类,实现数据保持相关功能,并且在Activity销毁(非配置更改(如屏幕旋转))的时候随之销毁。
- AndroidViewModel.java类:类,ViewModel的子类,持有application对象,可以调用系统服务等。
- ViewModelProvider.java类:Provider类,通过get方法获取相关ViewModel对象。
- ViewModelStore.java类:管理页面(Activity、Fragment相关的ViewModel),内部HashMap实现。
- ViewModelStoreOwner.java类:接口,内部只有一个方法getViewModelStore,类似于LifecycleOwner,ComponentActivity实现了该接口。
- ViewModelProviders.java类:类,对外提供的工具类,通过of方法传入Fragment或者FragmentActivity等获取ViewModelProvider对象。
ViewModel使用
依赖引入:
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
不引入第二行则ViewModelProviders类找不到
简单使用
- 新建MyViewModel继承自ViewModel或者AndroidViewModel,新建MutableLiveData保持数据,数据为Student对象,并且启动线程休眠1s模拟网络数据加载,加载完成之后通过MutableLiveData的post方法传输数据,代码如下:
class MyViewModel:ViewModel() {
private val student: MutableLiveData<Student> by lazy {
MutableLiveData<Student>().also{
loadStudent()
}
}
private fun loadStudent(){
//加载数据
Thread(
Runnable {
Thread.sleep(1000)
student.postValue(Student("小美女",5))
}
).start()
}
fun getStudent():LiveData<Student>{
return student
}
}
- 通过ViewModelProviders的of方法以及ViewModelProvider的get方法获取MyViewModel的对象。
- 通过MyViewModel的对象获取LiveData对象并进行observe监听注册。
代码如下:
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_view_model)
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
model.getStudent().observe(this, Observer{ student ->
Log.d(Common.TAG,"name:${student.name}")
Log.d(Common.TAG,"age:${student.age}")
})
}
}
1、运行日志如下,表示在网络加载完成的时候收到数据,在此处更新UI即可:
2、屏幕转成横屏,再转回来,日志如下,表示实现了数据保持:
1)进入页面的时候延迟1s收到数据
2)转成横屏马上收到数据
3)转回到竖屏马上收到数据
3、进入页面,马上退出,则不会收到数据,表示监听到了生命周期,实现了以生命周期的方式存储和管理UI界面相关的数据,防止内存泄漏。
实现同一个FragmentActivity之间的多个Fragment与Activity的数据共享与交互
1、创建MyFragment和MyFragment2如下:
class MyFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_test_view_model, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val model = ViewModelProviders.of(activity!!)[MyViewModel::class.java]
model.getStudent().observe(this, Observer { student ->
Log.d(Common.TAG, "MyFragment->name:${student.name}")
Log.d(Common.TAG, "MyFragment->age:${student.age}")
tv_name.text = student.name
val age = Integer.toString(student.age)
tv_age.text = age
})
tv_action.setOnClickListener {
val liveData: MutableLiveData<Student> = model.getStudent() as MutableLiveData<Student>
liveData.value = Student("小可爱", 6)
}
}
}
class MyFragment2 :Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment2_test_view_model,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val model = ViewModelProviders.of(activity!!)[MyViewModel::class.java]
model.getStudent().observe(this, Observer{ student ->
Log.d(Common.TAG,"MyFragment2->name:${student.name}")
Log.d(Common.TAG,"MyFragment2->age:${student.age}")
tv_name.text = student.name
val age = Integer.toString(student.age)
tv_age.text= age
})
tv_action.setOnClickListener {
val liveData: MutableLiveData<Student> = model.getStudent() as MutableLiveData<Student>
liveData.value = Student("小奶狗", 22)
}
}
}
- 添加fragment到activity中,并在activity中添加代码如下:
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_view_model)
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
model.getStudent().observe(this, Observer{ student ->
Log.d(Common.TAG,"name:${student.name}")
Log.d(Common.TAG,"age:${student.age}")
tv_name.text = student.name
val age = Integer.toString(student.age)
tv_age.text= age
})
tv_action.setOnClickListener {
val liveData:MutableLiveData<Student> = model.getStudent() as MutableLiveData<Student>
liveData.value = Student("小帅哥",8)
}
}
}
- 运行日志如下:
1)直接启动 在ViewModel中延迟加载,fragment和Activity都收到了消息并更新了UI
2)点击Activity中的按钮,fragment和Activity都收到了消息并更新了UI
3)分别点击两个fragment中的按钮,fragment和Activity都收到了消息并更新了UI
注意
- ViewModel必须有无参构造器,如果没有则会抛出异常,代码如下:
/**
* @author Simple
* @date 2020/1/7
* @description :
**/
class MyViewModel constructor(name:String): ViewModel() {
private val student: MutableLiveData<Student> by lazy {
MutableLiveData<Student>().also{
loadStudent()
}
}
private fun loadStudent(){
//加载数据
Thread(
Runnable {
Thread.sleep(1000)
student.postValue(Student("小美女",5))
}
).start()
}
fun getStudent():LiveData<Student>{
return student
}
}
运行则会报错如下,孵化器报错了,这跟启动过程有关,这里不做深入:
继续深入找到报错的地方1,往上一步步追错误2,一直到ViewModelProvider中,可以看到代码如下,在此处直接调用了ViewModel的无参构造器,而ViewModel并没有无参构造器:
/**
* Simple factory, which calls empty constructor on the give class.
*/
public static class NewInstanceFactory implements Factory {
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
//报错在此处
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
至于为什么newInstance会抛出InstantiationException ,newInstance为native方法,但在newInstance的注释中就能找到答案如下,很明确了吧。
2、继承自AndroidViewModel,则必须有一个构造器只有application一个参数,否则也会报错,和1类似(内部调用了此构造器,构造器不匹配),这一点在AndroidViewModel的注释中已有说明,如下:
3、在实现数据交互与共享时候注意fragment中的代码,of方法传入的必须是activity对象,如果传this则各管各的,不能实现数据的共享
val model = ViewModelProviders.of(activity!!)[MyViewModel::class.java]
ViewModel的回收
FragmentActivity中的回收
ViewModel实现了根据生命周期保存和管理UI数据,那究竟是如何回收的呢?
从AppCompatActivity继承关系一路追进入到ComponentActivity,可以看到ComponentActivity的构造函数有如下代码实现监听,在
Activity销毁(非配置更改(如屏幕旋转))的时候调用ViewModelStore的clear方法
public ComponentActivity() {
...
省略部分代码
...
}
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
...
省略部分代码
...
}
再进入ViewModelStore的clear方法,代码如下,遍历了该ViewModelStore持有的所有的ViewModel ,调用ViewModel 的clear方法并且清空Map。
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
ViewModel的clear方法如下,可以看出进行了协程相关的清除操作,以及调用了一个空方法onCleared,我们可以在这个方法里面实现一些资源的释放工作。
@MainThread
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
Fragment中的回收
从Fragment的getViewModelStore方法一路追进去,最终到FragmentManagerViewModel的getViewModelStore方法
代码如下:
Fragment.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
FragmentManagerImp.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
FragmentManagerViewModel.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
已知销毁的时候调用了ViewModelStore 的clear方法,所以在FragmentManagerViewModel中找到方法
clearNonConfigState调用了ViewModelStore.clear方法,FragmentManagerViewModel#clearNonConfigState方法只有在
FragmentManagerImp中调用了,如下:
FragmentManagerViewModel#clearNonConfigState
void clearNonConfigState(@NonNull Fragment f) {
if (FragmentManagerImpl.DEBUG) {
Log.d(FragmentManagerImpl.TAG, "Clearing non-config state for " + f);
}
// Clear and remove the Fragment's child non config state
FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
if (childNonConfig != null) {
childNonConfig.onCleared();
mChildNonConfigs.remove(f.mWho);
}
// Clear and remove the Fragment's ViewModelStore
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore != null) {
//清除并且移除管理
viewModelStore.clear();
mViewModelStores.remove(f.mWho);
}
}
FragmentManagerImp中的调用
...
省略
...
if (beingRemoved || mNonConfig.shouldDestroy(f)) {//是否销毁 shouldDestroy
boolean shouldClear;
if (mHost instanceof ViewModelStoreOwner) {
shouldClear = mNonConfig.isCleared();
} else if (mHost.getContext() instanceof Activity) {
Activity activity = (Activity) mHost.getContext();
shouldClear = !activity.isChangingConfigurations();
} else {
shouldClear = true;
}
if (beingRemoved || shouldClear) {
mNonConfig.clearNonConfigState(f);//清除
}
f.performDestroy();
dispatchOnFragmentDestroyed(f, false);
}
总结
1、关于Jetpack中的ViewModel就介绍到此处。
2、自勉,要多思考多解决问题,想到问题不要忽略直接跳过 ,多思考多查源码,收获是不一样的:
如上述的ViewHolder是怎么回收的。
如fragment间的数据共享等,不要只停留在简单的使用上。
3、自勉,看源码的时候也要注意注释,很多的问题已经在注释中说的很清楚了:
如上述注意事项中的1和2
4、自勉,问题多思考多验证,遇到问题找根源:
如上述注意事项中的3
推荐阅读
-
Android项目实战之Glide 高斯模糊效果的实例代码
-
Android之ScrollView嵌套ListView和GridView冲突的解决方法
-
Android之RAS加密算法测试实例
-
Android控件之SlidingDrawer(滑动式抽屉)详解与实例分享
-
Android学习笔记之Shared Preference
-
Android控件之TabHost用法实例分析
-
Android UI之ImageView实现图片旋转和缩放
-
Android开发之RadioGroup的简单使用与监听示例
-
详解Android开发技巧之PagerAdapter实现类的封装
-
Android开发之DialogFragment用法实例总结