Jetpack上一篇讲完了lifecycle
(原创)Jetpack系列:lifecycle
现在我们来讲ViewModel和LiveData
他们一般是配合来使用的
ViewModel用来存储数据
LiveData来监听数据的变化
这样做的好处是把数据的操作放在ViewModel里
Activity页面只负责业务逻辑即可
ViewModel还可以防止瞬态数据丢失
例如横竖屏的时候数据不会丢失
关于ViewModel的创建,有以下几种方式:
1:最原始的办法:
先创建ViewModelProvider
var viewModelProvider =
ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
然后创建ViewModel
var myViewModel = viewModelProvider.get(MyViewModel::class.java)
2:在fragment中有以下两种方式,创建属于当前fragment的ViewModel:
@MainThread
public inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
@MainThread
public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
VM::class, { requireActivity().viewModelStore },
factoryProducer ?: { requireActivity().defaultViewModelProviderFactory }
)
使用示例:
val viewModel: HomeFragViewModel by viewModels()
val activityViewModel: HomeFragViewModel by activityViewModels()
HomeFragViewModel是你自己ViewModel的类的名字
可以看到这两个方法,一个是和fragment绑定
一个是和fragment所在的Activity绑定
所以一个Activity下的多个fragment就可以通过第二种方式实现数据通信了
内部实现其实是基于这个方法:
@MainThread
public fun <VM : ViewModel> Fragment.createViewModelLazy(
viewModelClass: KClass<VM>,
storeProducer: () -> ViewModelStore,
factoryProducer: (() -> Factory)? = null
)
点进去看,归根结底也是用ViewModelProvider(store, factory).get(viewModelClass.java)的方式去做的
3:Activity里面创建Viewmodel可以这样:
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
)
点进去看,走的也是ViewModelLazy类里面的ViewModelProvider(store, factory).get(viewModelClass.java)
综上,这两张方式其实只是提供的一个快捷创建方式,
帮我们快速创建和绑定Activity/fragment而已
然后我们再来看下最基础的创建方式:
ViewModelProvider(<你的Activity 或 Fragment 实例>,<创建的factory>).get(<你的ViewModel>::class.java)
ViewModelProvider说明:
ViewModelProvider其实提供了三个构造方法给我们去创建ViewModelProvider市里
然后调用他的get方法通过反射去创建我们的Viewmodel
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
可以看到,owner传入后,其实就是调用它的getViewModelStore方法
第一个和第二个构造方法,最终走的都是第三个构造方法
ViewModelStore就是用来缓存我们的Viewmodel的对象的,在界面销毁时做一个缓存
下次再从缓存里面读。
owner的生命周期结束后,缓存也会消失
而Factory其实是一个接口,我们可以传入不同的实现:
public interface Factory {
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
ViewModelProvider的get()方法,内部其实就是调用我们传入的Factory的create
去反射创建我们的ViewModel示例。
最后做个总结:
1:ViewModelProvider用来创建ViewModel
2:Viewmodel是和owner的生命周期绑定的
3:真正创建ViewModel的是实现了Factory接口的类的create方法
4:Activity/Fragment都封装了一些自己的快捷创建Viewmodel的方法
如viewModels和activityViewModels方法
注意:第二种和第三种方法,其实是对原始方法的封装
使用这两种方法,需要导入:
implementation "androidx.activity:activity-ktx:1.4.0"
当我们要根据自己的实际需要来创建自己的ViewModel时
比如要求Viewmodel是单例,或者是全局,或者构造方法有参数
就可以自定义Factory,实现create方法即可
如下,我们自定义了一个可以构造方法可以传入参数的ViewNodel:
class UserViewModelFactory(
private val type: String
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(String::class.java).newInstance(type)
}
}
系统也提供了很多的Factory实现,大家可以去自行了解
下面我们通过一个小例子来掌握这两个东西。
首先我们创建一个自己的ViewModel类
去继承AndroidViewModel
里面有一个变量num
代码如下
/**
* viewModel类
*
*/
class MyViewModel(application: Application) : AndroidViewModel(application) {
var num:Int=0
}
然后在Activity里写一个btn按钮和一个textview
textview的文字内容使用的就是自定义Viewmodel里的num值
btn按钮点击一下,textview的num属性值就一
代码如下:
class MainActivity : AppCompatActivity() {
lateinit var viewModelProvider: ViewModelProvider
lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModelProvider =
ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
myViewModel = viewModelProvider.get(myViewModel::class.java)
mybtn.setOnClickListener {
mytv1.text = "${++myViewModel.num}"
}
mytv1.text = "${myViewModel.num}"
}
}
上面的ViewModel看起来很简单,无非是数据放在了MyViewModel里
但这不是关键,关键是引入liveData后
现在把代码修改如下:
1、新建一个MyLiveDataViewModel
class MyLiveDataViewModel : ViewModel() {
private var currentSecond: MutableLiveData<Int>? = null
fun getCurrSecond(): MutableLiveData<Int> {
if (currentSecond == null) {
currentSecond = MutableLiveData()
currentSecond!!.value = 0
}
return currentSecond!!
}
}
可以看到这里currentSecond返回的是一个MutableLiveData的整型数值
然后在我们的Activity里这样修改
class MainActivity : AppCompatActivity() {
lateinit var viewModelProvider: ViewModelProvider
lateinit var myViewModel: MyViewModel
lateinit var myLiveDataViewModel: MyLiveDataViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModelProvider =
ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
myViewModel = viewModelProvider.get(MyViewModel::class.java)
mybtn.setOnClickListener {
mytv1.text = "${++myViewModel.num}"
}
mytv1.text = "${myViewModel.num}"
myLiveDataViewModel = viewModelProvider.get(MyLiveDataViewModel::class.java)
myLiveDataViewModel.getCurrSecond().observe(this, Observer {
mytv2.text = "${it}"
})
startTimer()
}
private fun startTimer() {
Timer().schedule(timerTask {
//非ui线程,用postValue
myLiveDataViewModel.getCurrSecond().postValue(myLiveDataViewModel.getCurrSecond().value?.plus(1))
},1000,1000)
}
}
这里开起了一个子线程,每一秒钟让MyLiveDataViewModel里的数值加1
利用MutableLiveData的observe方法对数值的变化进行监听
发现数据有改变就去刷新UI
其实就是模拟请求网络数据的过程
这样一来我们就发现了
ViewModel+LiveData
可以实现数据也业务的解耦
把数据的变化放在ViewModel里面
用LiveData对这个变化进行监听
然后进行UI刷新即可
当然
不同的Activity或者Fragment
可以共用一个viewmodel的值
数据有变化,两个Activity或者Fragment界面的值也会一起变化的
LiveData里面其实也做了lifecycle的监听
在页面不显示的时候,是不会去执行UI刷新的
最后,xml代码也放上来吧
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/mytv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:text="Hello World!"
android:visibility="visible"
tools:visibility="visible" />
<Button
android:id="@+id/mybtn"
android:layout_width="124dp"
android:layout_height="91dp"
android:layout_below="@+id/mytv1"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:text="加1" />
<TextView
android:id="@+id/mytv2"
android:layout_width="124dp"
android:layout_height="91dp"
android:gravity="center"
android:layout_below="@+id/mybtn"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:text="0" />
</RelativeLayout>
上面例子用的ViewModel继承的是AndroidViewModel
用的是AndroidViewModelFactory来创建AndroidViewModel
注意,AndroidViewModelFactory传入的Context一定要是application里的Context
否则会导致内存泄漏。
因为使用ViewModel的时候,ViewModel不能够持有View、Lifecycle、Acitivity引用,
而且不能够包含任何包含前面内容的类。因为这样很有可能会造成内存泄漏。
那如果需要使用Context对象改怎么办。
这时候我们可以给ViewModel一个Application。Application是一个Context,而且一个应用也只会有Application。
那ViewModel怎么添加Application呢?其实Google还有一个AndroidViewModel。
这是一个包含Application的ViewModel。
下面是一个AndroidViewModel的源码:
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
@NonNull
public <T extends Application> T getApplication() {
//noinspection unchecked
return (T) mApplication;
}
}
可以看到只是在ViewModel的基础上添加了Application。
其实看AndroidViewModelFactory源码就得知
AndroidViewModelFactory传入的Application,最终使用反射,调用了AndroidViewModel的构造方法
把Application传给了AndroidViewModel。如下:
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
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);
}
所以我们可以总结:
1:如果我们的ViewModel需要context,可以继承AndroidViewModel,否则继承普通的ViewModel就好
2:继承了AndroidViewModel,就要用AndroidViewModelFactory,
并且传入application里的Context来创建我们自己的的AndroidViewModel。
至此,ViewModel+LiveData就讲到这里了