Android Jetpack - ViewModel
Posted teletian
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jetpack - ViewModel相关的知识,希望对你有一定的参考价值。
◾︎简介
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。
举个例子:在 MVP 中,Activity/Fragment 调用 P 层的异步请求。由于 P 层要持有 Activity/Fragment 的引用用于回调接口,当 Activity/Fragment 销毁后,造成内存泄露。为此,必须做大量的工作去管理异步请求。
而 ViewModel 不持有 Activity/Fragment 的引用,不存在上述问题。ViewModel 的 UI 更新是通过观察者模式(LiveData)实现的。
ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
当屏幕旋转时,为了避免数据丢失,通常会在 onSaveInstanceState() 中保存数据,然后在 onCreate() 的 Bundle 中恢复数据。但是这种方法有限制,数据必须支持序列化和反序列化,而且数据不能太大。
而 ViewModel 在 Activity 销毁重建时,会关联到新的 Activity,不需要手动保存再恢复。ViewModel 保存的数据格式和大小也没有像 Bundle 的限制。
本文代码使用 Kotlin 讲解,若需查看 Java 代码写法,请参考文末 Sample
◾︎添加依赖
ViewModel 一般和 LiveData 一起使用。
def lifecycle_version = "2.3.1"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
◾︎使用
定义 ViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel()
private val news: MutableLiveData<String> by lazy
MutableLiveData<String>().also
fetchNews()
fun getNews(): LiveData<String>
return news
private fun fetchNews()
Thread
Thread.sleep(3000)
news.postValue("Completed!")
.start()
使用 ViewModel
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProvider(this)[MyViewModel::class.java]
model.getNews().observe(this) msg ->
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
以上代码得到的 MyViewModel 实例是默认调用 MyViewModel 的无参构造方法。
我们知道,Android 中经常要用到 Context,如果需要在 ViewModel 中使用 Context,那么就需要调用带 Context 参数的构造方法了。
class MyViewModel(private val context: Context) : ViewModel()
......
要调用有参构造方法,需要自定义 ViewModelProvider.Factory。
class MyViewModelFactory(private val context: Context) : ViewModelProvider.Factory
override fun <T : ViewModel?> create(modelClass: Class<T>): T
try
// 遍历所有构造方法
for (constructor in modelClass.constructors)
// 找出只有一个 Context 参数的构造方法
if (arrayOf(Context::class.java).contentEquals(constructor.parameterTypes))
// 利用此构造方法创建实例
return (constructor as Constructor<T>).newInstance(context)
return modelClass.newInstance()
catch (e: InstantiationException)
throw RuntimeException("Cannot create an instance of $modelClass", e)
catch (e: IllegalAccessException)
throw RuntimeException("Cannot create an instance of $modelClass", e)
最后在获取 ViewModel 实例的时候,传入自定义的 ViewModelProvider.Factory 即可。
val model = ViewModelProvider(this, MyViewModelFactory(applicationContext))[MyViewModel::class.java]
仔细观察,会发现 ViewModelProvider 中有定义好的几种 Factory,其中就有 AndroidViewModelFactory,和我们上面自定义的 Factory 一样,可以直接拿来用。
◾︎ViewModel 的生命周期
ViewModel 的生命周期取决于传入 ViewModelProvider 的 ViewModelStoreOwner,也就是 Activity/Fragment。ViewModel 一直存在内存中,直到 Activity 完成时,Fragment 分离时。
所以,屏幕旋转后,ViewModel 依然存在在内存中。屏幕旋转时,ViewModel 的生命周期如下:
◾︎在 Fragment 之间共享数据
比如一个界面左边是列表 Fragment,右边是详细信息 Fragment。点了列表 Fragment 某一项需要把该项的信息传递给详细信息 Fragment。
先定义一个用于共享数据的 ViewModel
class SharedViewModel : ViewModel()
val selected = MutableLiveData<Item>()
fun select(item: Item)
selected.value = item
然后在两个 Fragment 中分别获取 ViewModel
class ListFragment : Fragment()
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener item ->
model.select(item)
class DetailFragment : Fragment()
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> item ->
// Update the UI
)
这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例。
有了 ViewModel,Activity 不需要执行任何操作就可以实现 Fragment 间的数据共享。而且 Fragment 之间也相互独立,就算其中一个 Fragment 消失了,另一个 Fragment 也照样正常工作。
◾︎Factory
创建 ViewModel 的时候会用到 Factory。
你可能有疑问,上面的例子中哪里用到了?
val model = ViewModelProvider(this)[MyViewModel::class.java]
别急,我们慢慢看源码。
首先看 ViewModelProvider 的构造方法:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner)
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
可以看到指定了默认的 Factory
owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance())
当 owner 也就是例子中的 Activity 实现了 HasDefaultViewModelProviderFactory 接口并覆写了 getDefaultViewModelProviderFactory 方法的时候,返回它。否则返回一个 NewInstanceFactory 实例。
看到没有,虽然没有显示指定 Factory,但是内部使用了默认的 Factory。
Android 默认给开发者提供了两种 Factory。NewInstanceFactory 和 AndroidViewModelFactory
先来看 NewInstanceFactory:
public static class NewInstanceFactory implements Factory
private static NewInstanceFactory sInstance;
static NewInstanceFactory getInstance()
if (sInstance == null)
sInstance = new NewInstanceFactory();
return sInstance;
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass)
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);
重点是 create 方法,它是用来指定创建 ViewModel 的时候使用 ViewModel 的哪个构造方法的。这里的 modelClass.newInstance() 会调用无参构造方法。
再来看 AndroidViewModelFactory:
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory
private static AndroidViewModelFactory sInstance;
public static AndroidViewModelFactory getInstance(@NonNull Application application)
if (sInstance == null)
sInstance = new AndroidViewModelFactory(application);
return sInstance;
private Application mApplication;
public AndroidViewModelFactory(@NonNull Application application)
mApplication = application;
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass)
if (AndroidViewModel.class.isAssignableFrom(modelClass))
try
return modelClass.getConstructor(Application.class).newInstance(mApplication);
catch (NoSuchMethodException e)
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
catch (IllegalAccessException e)
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
catch (InstantiationException e)
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
catch (InvocationTargetException e)
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
return super.create(modelClass);
重点还是来看 create 方法。
当 AndroidViewModel.class.isAssignableFrom(modelClass) 也就是要创建的 ViewModel 继承自 AndroidViewModel 的话,会调用参数是 Application 的构造方法,也就是 AndroidViewModel 中定义的构造方法。
public class AndroidViewModel extends ViewModel
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application)
mApplication = application;
@SuppressWarnings("TypeParameterUnusedInFormals", "unchecked")
public <T extends Application> T getApplication()
return (T) mApplication;
当 ViewModel 需要用到资源文件的时候,Application 就很有用了。注意:ViewModel 中不能传 Activity,因为 ViewModel 的生命周期比较长。
AndroidViewModelFactory 使用例:
val factory = ViewModelProvider.AndroidViewModelFactory(application)
val model = ViewModelProvider(this, factory)[MyViewModel::class.java]
当然,如果系统的 Factory 不能满足的话,我们也可以自定义 Factory。
比如:
public class MyViewModelFactory(val age: Int): ViewModelProvider.Factory
override fun <T : ViewModel> create(viewModelClass: Class<T>): T
return viewModelClass.getConstructor(Int::class.java).newInstance(age)
这个 Factory 会调用 public MyViewModel(age :Int) 构造方法。
具体使用如下:
val factory = MyViewModelFactory(10)
val model = ViewModelProvider(this, factory)[MyViewModel::class.java]
◾︎Sample
以上是关于Android Jetpack - ViewModel的主要内容,如果未能解决你的问题,请参考以下文章
Android Jetpack架构组件带你了解Android Jetpack
Android Jetpack架构组件——什么是Jetpack?
Android Jetpack架构组件——什么是Jetpack?