前言
在Android
开发过程中,为了避免Activity
过于臃肿,我们采用了LifeCycle
来解耦生命周期相关的业务逻辑。但是在开发中我们仍然致力于解决另一个问题,那就是数据和ui分离
。今天就来讲讲用来解决Activity
臃肿及其他相关问题的ViewModel
的使用
今天涉及知识由有:
- 为什么要用
ViewModel
-
ViewModel
初始化方式 - 封装
BaseViewModel
的使用 -
ViewModel
使用注意事项 -
BaseViewModle
源码
一. 为什么要用 ViewModel
这里主要讲讲使用ViewModel
的好处:
- 达到数据,图形分离(解耦)
-
ViewModel
实现数据持有不丢失 - 基于第二点,还能实现
Activity
与Fragment
间数据共享
二. ViewModel 初始化方式
网上多出现ViewModel
的使用需要引入jar
,但我在实际使用过程中,发现不需要添加额外依赖,显示ViewModel
是在androidx.lifecycle
包下。
以ViewModel
在MainActivity
中使用为例,首先我们要建一个MainViewModel
继承自ViewModel
,然后在MainActivity
中实例化它如下:
mainViewModel= ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(MainViewModel::class.java)
这里采用的是工厂模式初始化。this
是当前Activity
实例,从初始化方法的源码中可以看出
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
参数应该为ViewModelStoreOwner owner
,而追踪MainActivity
源码可发现:
MainActivity -> AppCompatActivity -> FragmentActivity -> ComponentActivity
然后看ComponentActivity
:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware,
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
ActivityResultCaller {
可以发现ComponentActivity
是ViewModelStoreOwner
实现类,即MainActivity
是ViewModelStoreOwner
实例,所以初始化时可以用this
代替ViewModelStoreOwner owner
参数。
当然,我们还可以用以下方式在MainActivity
中初始化ViewModel
对象:
mainViewModel= ViewModelProvider(this).get(MainViewModel::class.java)
当然,我们还可以通过自定义工厂模式来实现ViewModel
的初始化,这个接下来讲。
三. 封装 BaseViewModel 的使用
既然有以上两种初始化ViewModel
的方式,我们为啥还要自定义工厂模式来实现ViewModel
的初始化呢?原因是我们在开发的时候不免会涉及到ViewModel
传值的问题,很显然,以上两种方式均不能在初始化时将MainActivity
的数据传输到ViewModel
中去。于是我封装了一个BaseViewModel
用以实现MainActivity
向ViewModel
传值的目的。下面以简单例子来讲解ViewModel
的好处。先继承BaseViewModel
实现MainViewModel
用以缓存界面MainActivity
中数据:
class MainViewModel : BaseViewModel {
constructor()
constructor(savedInstanceState: Bundle?, any:Any?):super(savedInstanceState,any){
}
var mNumber:Int=5
}
由于我对BaseViewModel
做了处理,所以我们在继承BaseViewModel
实现MainViewModel
时,必须写一阶构造函数constructor()
,否则会在使用过程中报错。
接着看MainActivity
代码:
@RequiresApi(Build.VERSION_CODES.N)
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var mBingding: ActivityMainBinding
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBingding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBingding.root)
initData(savedInstanceState)
setListener()
}
private var initData:(Bundle?)->Unit = {savedInstanceState->
mBingding.tvName.text = "我是谁"
//初始化mainViewModel
mainViewModel=BaseViewModel.getViewModel(this,object: BaseViewModel.BaseViewModelFactory(savedInstanceState,"小学") {
override fun getChildViewModel(savedInstanceState: Bundle?, any: Any?): BaseViewModel {
return MainViewModel(savedInstanceState,any)
}
},MainViewModel::class.java) as MainViewModel
mBingding.tvName.text = mainViewModel.mNumber.toString()
}
private var setListener = {
mBingding.mBtnTest.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
R.id.mBtnTest -> {
LogUtil.i("====我点击了=====")
plusNumber()
}
else -> {
}
}
}
private var plusNumber = {
mainViewModel.mNumber=mainViewModel.mNumber+1
mBingding.tvName.text=mainViewModel.mNumber.toString()
}
override fun onDestroy() {
super.onDestroy()
}
}
运行效果图如下:
这里我们可以发现,通过
ViewModel
来保存数据以后,我们在屏幕旋转时,之前的数据得以保留,而不是回到初始值。这就是ViewModel
数据持有的独特魅力。
四. ViewModel 使用注意事项
4.1 ViewModel 生命周期
ViewModel
生命周期在网上有一幅图
即
Activity
在系统配置变化导致的重建不会销毁ViewModel
,ViewModel
对象会保留并关联到新的Activity,只有在Activity
正常销毁(系统不会重建Activity
)时,ViewModel
才会被销毁。通过代码:
mainViewModel= ViewModelProvider(this).get(MainViewModel::class.java)
追踪源码:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
继续看this
方法:
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
可发现mViewModelStore
被赋值,然后接着看.get(MainViewModel::class.java)
这部分:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
接着追get(DEFAULT_KEY + ":" + canonicalName, modelClass)
:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
可以看到mViewModelStore
中若有viewModel
则直接取,没有的话,则由mFactory
创建。mFactory
创建方法如下:
@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);
}
}
至此,我们可以发现,一个Activity
中只有一个ViewModelStore
,ViewModelStore
可存储多个ViewModel
对象,ViewModel
初始化流程是如果ViewModelStore
无ViewModel
则new
一个ViewModel
并放到ViewModelStore
中存储,如果ViewModelStore
有则直接取出来用。
4.2 ViewModel 不能持有的对象
鉴于ViewModel
生命周期跨越的长度比较大(包含一个Activity
的整个生命周期),我们可以利用ViewModel
来做Activity
与Fragment
间的传值处理。但是也由于生命周期过长的问题,我们不能在ViewModel
中出现以下两种对象:
-
ui
控件实例 -
Activity/Fragment
实例
若ViewModel
中出现UI
控件,会导致程序崩溃,出现Activity/Fragment
实例,轻则内存泄漏,重则崩溃
4.3 ViewModel 中使用 Application
ViewModel
中不能出现Activity/Fragment
实例,那我们在ViewModel
中要使用Context
对象怎么办?这时我们不能用Activity/Fragment
的context
,而要使用Application
的context
,我们可以让自己的ViewModel
继承AndroidViewModel
类,类似下面这样:
class NeViewModel(application: Application) : AndroidViewModel(application) {
}
由于AndroidViewModel
是ViewModel
的一个子类,所以也可以用
neViewModel= ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(NeViewModel::class.java)
和
neViewModel= ViewModelProvider(this).get(NeViewModel::class.java)
的方式初始化。
4.4 ViewModel 与 savedInstanceState
我们知道savedInstanceState
能在activity
异常时提供数据的瞬时保存与恢复,现在ViewModel
也能达到数据瞬时存储和恢复的目的,那是不是意味着我们有了ViewModel
后就不需要savedInstanceState
了呢?
答案是否定的。为了程序的健壮性,我们得同时使用savedInstanceState
和ViewModel
。ViewModel
存储数据较多,数据持有环境一般为Activity
配置发生变化导致的重启,如果Activity
内存溢出导致崩溃重启,ViewModel
数据也会丢失,这时就需要savedInstanceState
协助。但是savedInstanceState
是一个Bundle
,只能存储少数数据。所以需要ViewModel
和savedInstanceState
结合使用。
五. BaseViewModle 源码
下面给出BaseViewModle
源码: