一.简介
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
ViewModel生命周期
官网:ViewModel 概览 | Android 开发者 | Android Developers
二.基本使用
1.Gradle配置
非androidX项目
implementation "android.arch.lifecycle:extensions:1.1.1"
AndroidX项目
implementation 'androidx.appcompat:appcompat:1.2.0'
这一行依赖,已经可以使用Jetpack的好多组件了,比如ViewModel,比如LiveData等等。当然也可以单个引入,具体见官方文档
https://developer.android.google.cn/jetpack/androidx/releases/lifecycle?hl=zh_cn
2.代码
ViewModelProvider.Factory实现类
package com.wjn.networkdemo.viewmodel;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
/**
* ViewModelProvider.Factory接口实现类 获取Factory对象 获取ViewModelProvider对象时的第二个参数
*/
public class ViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return modelClass.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
ViewModel实现类
package com.wjn.networkdemo.viewmodel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.wjn.networkdemo.retrofit.only.FanYiService;
import java.io.IOException;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class FanYiViewModel extends ViewModel {
/**
* Retrofit模块 详解:https://blog.csdn.net/weixin_37730482/category_6875815.html
*/
private Retrofit mRetrofit;
private FanYiService mFanYiService;
private String mBaseUrl = "http://fanyi.youdao.com/";
private MutableLiveData<String> mListLiveData;
private MutableLiveData<Boolean> mLoadingLiveData;
public FanYiViewModel() {
//获取Retrofit对象
mRetrofit = new Retrofit.Builder()
.baseUrl(mBaseUrl)//设置BaseUrl 必须以'/'结尾
.build();
mListLiveData = new MutableLiveData<>();
mLoadingLiveData = new MutableLiveData<>();
}
/**
* 获取翻译数据
*/
public void getFanYiData() {
if (null != mRetrofit) {
//进度条状态
mLoadingLiveData.setValue(false);
//Retrofit接口请求
mFanYiService = mRetrofit.create(FanYiService.class);
Call<ResponseBody> getCall = mFanYiService.getFanYiByGet();
getCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
String result = null;//报文 获取报文必须判断响应是否成功
if (response.isSuccessful()) {
try {
result = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
}
//进度条状态
mLoadingLiveData.setValue(true);
//接口报文
mListLiveData.setValue(result);
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//进度条状态
mLoadingLiveData.setValue(true);
//接口报文
mListLiveData.setValue("");
}
});
}
}
/**
* 获取请求结果的LiveData对象
*/
public MutableLiveData<String> getListLiveData() {
return mListLiveData;
}
/**
* 获取加载中的LiveData对象
*/
public MutableLiveData<Boolean> getLoadingLiveData() {
return mLoadingLiveData;
}
}
Activity测试类
package com.wjn.networkdemo.viewmodel;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import com.wjn.networkdemo.R;
public class ViewModelActivity extends AppCompatActivity {
private ProgressBar mProgressBar;
private TextView mTextView;
private FanYiViewModel mFanYiViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewmodel);
initView();
}
/**
* 初始化View
*/
private void initView() {
mProgressBar = findViewById(R.id.activity_viewmodel_pb);
mTextView = findViewById(R.id.activity_viewmodel_tv);
initViewModel();
//请求接口
findViewById(R.id.activity_viewmodel_textview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mFanYiViewModel.getFanYiData();
}
});
}
/**
* 初始化ViewModel
*/
private void initViewModel() {
//获取ViewModelProvider实例
ViewModelProvider viewModelProvider = new ViewModelProvider(this, new ViewModelFactory());
//获取ViewModel实例
mFanYiViewModel = viewModelProvider.get(FanYiViewModel.class);
//观察接口请求
mFanYiViewModel.getListLiveData().observe(this, s -> {
mTextView.setText(s);
Log.d("ViewModelActivity", "接口请求s----:" + s);
Log.d("ViewModelActivity", "接口请求线程----:" + Thread.currentThread().getName());
});
//观察接口请求中...
mFanYiViewModel.getLoadingLiveData().observe(this, aBoolean -> {
mProgressBar.setVisibility(aBoolean ? View.GONE : View.VISIBLE);
Log.d("ViewModelActivity", "观察接口请求aBoolean----:" + aBoolean);
Log.d("ViewModelActivity", "观察接口请求线程----:" + Thread.currentThread().getName());
});
}
}
3.结果
D/ViewModelActivity: 观察接口请求aBoolean----:false
D/ViewModelActivity: 观察接口请求线程----:main
D/ViewModelActivity: 观察接口请求aBoolean----:true
D/ViewModelActivity: 观察接口请求线程----:main
D/ViewModelActivity: 接口请求s----:{"translation":["蓝色的"],"basic":{"us-phonetic":"bluː","phonetic":"bluː","uk-phonetic":"bluː","explains":["adj. 蓝色的;忧郁的,悲观的;(由于冷或呼吸困难)发青的,青紫的;(电影、玩笑或故事)色情的,黄色的;(肉)未熟的;(政治上)保守的","n. 蓝色;蓝色物品;(牛津或剑桥大学的运动员)蓝色荣誉者;失误;红发人;打架","vt. (使)变成蓝色;把......染成蓝色;给\u2026\u2026上蓝色漂白剂;挥霍(钱财)","n. (Blue) (英、美、加、澳、新)布卢(人名)"]},"query":"blue","errorCode":0,"web":[{"value":["蓝色的","蓝色","刀枪不入"],"key":"blue"},{"value":["钴蓝色","艳蓝色","钴蓝","群青"],"key":"Cobalt blue"},{"value":["蓝月","蓝月亮","千载难逢的时机","蓝色月亮"],"key":"Blue Moon"}]}
D/ViewModelActivity: 接口请求线程----:main
三.基本讲解
1.ViewModel用于代替MVP中的Presenter层
<1> 前言
在MVP模式中Presenter层需要持有IView接口来回调结果给界面(Activity/Fargment)。而Presenter层常常调用Model层进行异步网络请求。因为网络请求时间不固定。所以可能Presenter层会保存很长一段时间,如果Presenter层持有View层(Activity/Fargment)的上下文对象。这样就可能导致View层(Activity/Fragment)已近销毁。但是还有Presenter层持有它,导致该Activity/Fragment不能被及时回收。造成内存泄漏。
<2> ViewModel用法
上述代码可以看出,ViewModel并没有持有View层。而是通过LiveData来设置变化。
具体步骤
ViewModel继承类中
1.声明LiveData对象
private MutableLiveData<String> mListLiveData;
private MutableLiveData<Boolean> mLoadingLiveData;
2.设置LiveData变化
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
String result = null;//报文 获取报文必须判断响应是否成功
if (response.isSuccessful()) {
try {
result = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
}
//进度条状态
mLoadingLiveData.setValue(true);
//接口报文
mListLiveData.setValue(result);
}
3.对外提供LiveData对象
/**
* 获取请求结果的LiveData对象
*/
public MutableLiveData<String> getListLiveData() {
return mListLiveData;
}
/**
* 获取加载中的LiveData对象
*/
public MutableLiveData<Boolean> getLoadingLiveData() {
return mLoadingLiveData;
}
View层使用
1.利用ViewModelProvider对象 获取 ViewModel继承类对象
//获取ViewModelProvider实例
ViewModelProvider viewModelProvider = new ViewModelProvider(this, new ViewModelFactory());
//获取ViewModel实例
mFanYiViewModel = viewModelProvider.get(FanYiViewModel.class);
2.拿到对应的LiveData对象 添加观察者 更新UI等操作
//观察接口请求
mFanYiViewModel.getListLiveData().observe(this, s -> {
mTextView.setText(s);
Log.d("ViewModelActivity", "接口请求s----:" + s);
Log.d("ViewModelActivity", "接口请求线程----:" + Thread.currentThread().getName());
});
//观察接口请求中...
mFanYiViewModel.getLoadingLiveData().observe(this, aBoolean -> {
mProgressBar.setVisibility(aBoolean ? View.GONE : View.VISIBLE);
Log.d("ViewModelActivity", "观察接口请求aBoolean----:" + aBoolean);
Log.d("ViewModelActivity", "观察接口请求线程----:" + Thread.currentThread().getName());
});
3.适当时机 比如点击某个按钮 调用ViewModel继承类中请求接口相关的方法
//请求接口
findViewById(R.id.activity_viewmodel_textview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mFanYiViewModel.getFanYiData();
}
});
<3> 总结
也就是说ViewModel和Presenter层的区别就是,不持有View层的引用。通过LiveData来设置数据(相等于Presenter层给View层的回调)。然后通过LiveData的观察者接收数据变化。而LiveData具有生命周期感知性,可以自动销毁。这样就避免了Activity/Fragment层可能造成的内存泄漏问题。
四.Fragment间共享数据
1.代码
ViewModel继承类
package com.wjn.networkdemo.viewmodel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class SharedViewModel extends ViewModel {
private MutableLiveData<String> mSharedLiveData;
public SharedViewModel() {
mSharedLiveData = new MutableLiveData<>();
}
/**
* 设置LiveData值
*/
public void shareValue(String value) {
mSharedLiveData.setValue(value);
}
/**
* 获取LiveData
*/
public MutableLiveData<String> getSharedLiveData() {
return mSharedLiveData;
}
}
Fragment1
package com.wjn.networkdemo.viewmodel;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.wjn.networkdemo.R;
public class Fragment1 extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_1, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//设置共享数据
SharedViewModel model = new ViewModelProvider(requireActivity(), new ViewModelFactory()).get(SharedViewModel.class);
model.shareValue("Fragment1共享给Fragment2的数据");
}
}
Fragment2
package com.wjn.networkdemo.viewmodel;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.wjn.networkdemo.R;
public class Fragment2 extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_2, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//获取Fragment1共享的数据
SharedViewModel model = new ViewModelProvider(requireActivity(), new ViewModelFactory()).get(SharedViewModel.class);
model.getSharedLiveData().observe(requireActivity(), new Observer<String>() {
@Override
public void onChanged(String s) {
Log.d("Fragment", "第二个Fragment接收的数据:" + s);
}
});
Log.d("Fragment", "第二个Fragment onViewCreated方法");
}
}
2.结果
D/Fragment: 第二个Fragment onViewCreated方法
D/Fragment: 第二个Fragment接收的数据:Fragment1共享给Fragment2的数据
3.总结
<1> 使用ViewModel来在Fragment之间共享数据。方便简洁。不需要宿主Activity做任何操作。相比EventBus和接口回调要简单的多。
<2> 因为ViewModel使用LiveData来观察数据变化,不需手动销毁。
<3> Fragment中创建ViewModel或者添加观察者时ViewModelStoreOwner对象要传宿主Activity的对象
五.源码分析
1.创建ViewModel对象
从代码中可知,创建ViewModel对象,没有使用ViewModel类。而是使用的ViewModelProvider类。
ViewModelProvider viewModelProvider = new ViewModelProvider(this, new ViewModelFactory());
点击查看两个参数的ViewModelProvider类构造方法。
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
*
* @param owner a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
* retain {@code ViewModels}
* @param factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
然后内部调用另外一个两个参数的构造方法
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in the given {@code store}.
*
* @param store {@code ViewModelStore} where ViewModels will be stored.
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
也就是咱们创建时传的两个参数是
参数1:ViewModelStoreOwner对象。即Activity/Fragment对象。两者已经实现了ViewModelStoreOwner接口。如下
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
所以可是直接传 this即可。
参数2:Factory对象。是一个接口。在ViewModelProvider类内部。一般实现
public class ViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return modelClass.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
最后传到构造方法里,参数是
参数1:变成 ViewModelStore 。
参数2:未变。
参数1变化
owner.getViewModelStore()
也就是说,通过传递的ViewModelStoreOwner对象,获取ViewModelStore对象。如果ViewModelStoreOwner对象不一致,可能造成获取ViewModelStore对象不一致。而ViewModelStore对象又是用来存储ViewModel的集合。
这就解释了,同一个Activity中好说,前面说的两个Fragment共享数据时。在两个Fragment中ViewModel的继承类都是重新创建的。参数传宿主Activity时能收到消息。参数传各自的Fragment对象时不能收到消息。就是这个原因。
2.获取具体的ViewModel继承类对象
上面说到了,使用ViewModel时,首先获取ViewModelProvider对象。那么获取到ViewModelProvider对象后。怎么获得具体的ViewModel对象呢?继续
FanYiViewModel mFanYiViewModel = viewModelProvider.get(FanYiViewModel.class);
点击get方法 get方法源码
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
* <p>
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
@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方法。继续
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
* <p>
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param key The key to use to identify the ViewModel.
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
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);
//noinspection unchecked
return (T) viewModel;
}
ViewModel viewModel = mViewModelStore.get(key);
首先根据上面生成的Key,去ViewModelStore对象的集合中取ViewModel对象。继续
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
如果获取的ViewModel对象不为空 直接返回集合中的ViewModel。否则继续。
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
如果,第二个参数的工厂对象是KeyedFactory。就用KeyedFactory创建新的ViewModel。否则,就用传入的对象创建新的ViewModel对象。即自己实现Factory接口返回的ViewModel对象。
public class ViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return modelClass.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
继续
mViewModelStore.put(key, viewModel);
return (T) viewModel;
创建完新的ViewModel后,将新的ViewModel存储到ViewModelStore对象的集合中。
3.ViewModel对象存储
上述已分析,ViewModel对象的存储是用的ViewModelStore。
ViewModelStore源码
/**
* Class to store {@code ViewModels}.
* <p>
* An instance of {@code ViewModelStore} must be retained through configuration changes:
* if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration
* changes, new instance of an owner should still have the same old instance of
* {@code ViewModelStore}.
* <p>
* If an owner of this {@code ViewModelStore} is destroyed and is not going to be recreated,
* then it should call {@link #clear()} on this {@code ViewModelStore}, so {@code ViewModels} would
* be notified that they are no longer used.
* <p>
* Use {@link ViewModelStoreOwner#getViewModelStore()} to retrieve a {@code ViewModelStore} for
* activities and fragments.
*/
public class ViewModelStore {
private final HashMap<String, ViewModel> 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<String> 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();
}
}
此类,有三个方法需要格外关注。
put方法:在上面获取具体的ViewModel对象时,如果新创建ViewModel时,会将新创建的ViewModel对象存储起来。就会调用这个方法。
get方法:在上面获取具体的ViewModel对象时,调用。获取Key对应的存储过的ViewModel对象。
clear方法:点击调用这个方法的地方,发现ComponentActivity类有调用。而从26开始或者Androidx版本的Activity都会继承这个类。也就是说都会调用这个clear方法。
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
if (lifecycle == null) {
throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
+ "constructor. Please make sure you are lazily constructing your Lifecycle "
+ "in the first call to getLifecycle() rather than relying on field "
+ "initialization.");
}
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();
}
}
}
});
}
也就是说,创建Activity时,注册了一个观察者。当LifecycleOwner拥有类也就是Activity变成ON_DESTROY状态,即销毁时候。会清空存储在集合中的ViewModel对象。
这也就证明ViewModel的生命周期是在Activity销毁时才销毁。
4.补充
onSaveInstanceState方法也可以保存页面中的数据,ViewModel也可以保存页面中的数据。那么两者有什么区别呢?
onSaveInstanceState:存储在磁盘。需要序列化。大小有限制(一般Bundle限制大小1M)。且onSaveInstanceState只能存可序列化和反序列化的对象。
ViewModel:存在内存中(集合 创建ViewModelProvider对象时,初始化 获取对应的ViewModel时,获取集合&存入结合。Activity变成ON_DESTROY状态时清空集合) 。读写速度快。大小限制就是App的可用内存。