ViewModel
ViewModels负责为View准备数据。它们将数据公开给正在侦听更改的任何视图
ViewModel 的作用是专门存放与界面相关的数据,分担 Activity/Fragment 的逻辑,同时会维护自己独立的生命周期。如当系统配置发生变更(如切换语言等)、横竖屏切换等,可能会导致 Activity 销毁重建,假设要被销毁是 Activity A,需要被重新创建的是 Activity B,虽然他们都属于同一类型,但是是两个不同的实例对象。因此在 Activity 销毁重建的过程中,就涉及 A 在销毁时,其内部维护的数据要过渡到重建的 B 中,这就依赖于 ViewModel。
- ViewModel可以在Activity配置更改中保留其状态。它保存的数据可立即供下一个Activity实例使用,无需在onSaveInstanceState()中保存数据并手动恢复。
- ViewModel比特定的Activity或Fragment实例更长。
- ViewModel允许在Fragments之间轻松共享数据(这意味着您不再需要通过活动协调操作)。
- ViewModel将保留在内存中,直到它的作用域生命周期永久消失.
- 由于ViewModel比Activity或Fragment实例更长,因此它不应直接引用其中的任何Views或保持对上下文的引用。这可能会导致内存泄漏(https://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/)。
- 如果ViewModel需要Application上下文(例如,查找系统服务),它可以继承AndroidViewModel类并具有在构造函数中接收Application的构造函数。
由于 ViewModel 的生命周期是由系统维护的,因此不能直接在代码中通过 new 的方式创建。
======创建 AndroidViewModel=======
viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MainViewModel::class.java)
----AndroidViewModel也是继承viewModel,只是多了一层环境的封装----
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
}
Databinding+LiveData+ViewModel 实现拨号小案例
功能说明
1.数字按钮点击动态显示到UI
2.del按钮和back按钮 对UI页面数据进行删除处理
3.call按钮,调用拨号操作
源码
1.MainViewModel.class 用于数据处理和页面绑定
/**
* @author 付影影
* @desc
* @date 2022/6/10
*/
class MainViewModel(application: Application) : AndroidViewModel(application) {
val phoneInfo:MutableLiveData by lazy { MutableLiveData() }
init {
phoneInfo.value = ""
}
@SuppressLint("StaticFieldLeak")
val context:Context = application
//拨号
fun appendNumber(number:String){
phoneInfo.value = phoneInfo.value+number
}
//删除数据
fun backSpaceNumber(){
val length:Int = phoneInfo.value?.length?:0
if (length > 0){
phoneInfo.value = phoneInfo.value?.substring(0,phoneInfo.value!!.length-1)
}
}
//清除数据
fun clear(){
phoneInfo.value = ""
}
//打电话
fun callPhone(){
val intent = Intent()
intent.action = Intent.ACTION_CALL
intent.data = Uri.parse("tel:"+phoneInfo.value)
//非activity环境 启动拨号或者跳转页面,都需要配置flags,否则会崩溃、在activity环境默认启动方式
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
}
2.xml文件绑定 databinding
3.MainActivity.class 声明databinding,绑定viewmodel和lifecycle
package com.shadow.testapplication
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.shadow.testapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding:ActivityMainBinding
lateinit var viewModel:MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MainViewModel::class.java)
binding.vm = viewModel
binding.lifecycleOwner = this
}
override fun onRetainCustomNonConfigurationInstance(): Any? {
Log.d("hh","横竖屏切换")
return super.onRetainCustomNonConfigurationInstance()
}
}
源码解析
viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MainViewModel::class.java)
- ViewModelProvider 第一个参数传入的是this,MainActivity,
ComponentActivity实现了ViewModelStoreOwner接口,实现方法ViewModelStore getViewModelStore();,
getViewModelStore() 实现 主要通过 NonConfigurationInstances nc 对viewmoStore进行缓存。
ViewModelStore 主要是通过HashMap对viewmodel进行处理增加,删除
public class ViewModelStore {
private final HashMap mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set keys() {
return new HashSet<>(mMap.keySet());
}
/**
* 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();
}
}
2.ViewModelProvider 第二参数传入的是Factory,采用依赖倒置原则,面对接口开发。
NewFactory,AndroidNewFactory,通过反射 实例化viewmodel
- get 方法,把viewModel 加入viewModelStore
public T get(@NonNull String key, @NonNull Class 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;
}
为什么横竖屏切换,viewmodel能保证数据稳定性
在屏幕横竖屏转换的过程,通过nc 恢复已缓存viewmodelStore,得到viewmodel,自然能保证数据的稳定性
为什么viewmodel的生命周期那么长
ViewModel能够将数据从Activity中剥离出来。只要Activity不被销毁,ViewModel会一直存在,并且独立于Activity的配置变化,即旋转屏幕导致的Activity重建,不会影响到ViewModel
因为viewmodel只有组件激活Destoryed事件时,才会romove所有的viewmodel
ViewModel+LiveData实现Fragment间通信
Fragment可以看作是Activity的子页面,即,一个Activity中可以包含多个Fragment,这些Fragment彼此独立,但是又都属于同一个Activity。
public class ShareDataViewModel extends ViewModel
{
private MutableLiveData progress;
public LiveData getProgress()
{
if (progress == null)
{
progress = new MutableLiveData<>();
}
return progress;
}
@Override
protected void onCleared()
{
super.onCleared();
progress = null;
}
}
=================fragment 中获取viewmodel和livedata实例==============
然后订阅livedata,动态修改数据,通过liveData setValue 更新数据,两个fragment都这样操作,就可以实现数据同步更新。
//注意:这里ViewModelProviders.of(getActivity())这里的参数需要是Activity,而不能是Fragment,否则收不到监听 (老版本如此写)
final ShareDataViewModel shareDataViewModel = ViewModelProviders.of(getActivity()).get(ShareDataViewModel.class);
final MutableLiveData liveData = (MutableLiveData)shareDataViewModel.getProgress();
//通过observe方法观察ViewModel中字段数据的变化,并在变化时,得到通知
liveData.observe(this, new Observer()
{
@Override
public void onChanged(@Nullable Integer progress)
{
//监听数据变化,更新数据
seekBar.setProgress(progress);
}
});
//通过seekBar测试数据更新
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
{
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
//用户操作SeekBar时,更新ViewModel中的数据
liveData.setValue(progress);
}
});