本文目标
理解并掌握ViewModel组件用法和原理
1.什么是ViewModel
- 具备宿主生命周期感知能力的数据存储组件
-
ViewModel
保存的数据,在页面因配置变更导致页面销毁重建之后依然是存在的
配置变更:横竖屏切换、分辨率调整、权限变更、系统字体样式变更...
ViewModel
是如何做到页面销毁了还能恢复期数据呢?
其实就是ViewModel
实例被保存了下来,页面重建之后获取的ViewModel
是同一个
2.基本用法
常规用法:存储的数据,仅仅只能当页面因为配置变更导致的销毁再重建时可复用,复用的是ViewModel
的实例对象整体
public class MyGirlViewModel extends ViewModel {
//定义一个对象,相当于一个用来存放数据的仓库
private static MutableLiveData<List<Girl>> mLiveData;
/**
* 用于获取数据
*/
public LiveData<List<Girl>> getDataList() {
if (mLiveData == null) {
mLiveData = new MutableLiveData<>();
loadData();
}
return mLiveData;
}
private void loadData() {
List<Girl> data = new ArrayList<>();
data.add(new Girl(R.drawable.f1, "一星", "****"));
data.add(new Girl(R.drawable.f2, "一星", "****"));
data.add(new Girl(R.drawable.f3, "一星", "****"));
data.add(new Girl(R.drawable.f4, "一星", "****"));
data.add(new Girl(R.drawable.f5, "一星", "****"));
data.add(new Girl(R.drawable.f6, "一星", "****"));
data.add(new Girl(R.drawable.f7, "一星", "****"));
data.add(new Girl(R.drawable.f8, "一星", "****"));
data.add(new Girl(R.drawable.f9, "一星", "****"));
data.add(new Girl(R.drawable.f10, "一星", "****"));
//把这些数据存到仓库里
mLiveData.setValue(data);
}
//提供一个方法来改变数据
public void changeValue(int position, String value) {
//把数据取出来,然后更改其中某个条目的值,在把数据塞会仓库里
List<Girl> list = mLiveData.getValue();
Girl girl = list.get(position);
girl.setLike(value);
mLiveData.setValue(list);
}
}
public class MainActivity extends AppCompatActivity {
private ListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = findViewById(R.id.listview);
initListView();
}
private void initListView() {
MyGirlViewModel myGirlViewModel = new ViewModelProvider(this).get(MyGirlViewModel.class);
LiveData<List<Girl>> liveData = myGirlViewModel.getDataList();
//监听数据的改变,数据改变就会调用onChanged方法
liveData.observe(this, new Observer<List<Girl>>() {
/**
* 当我们的数据发生变化的时候,我们可以在这个onChanged中进行处理
*/
@Override
public void onChanged(List<Girl> girls) {
Log.i("yd","onChanged刷新一次");
mListView.setAdapter(new GirlAdapter(MainActivity.this, girls));
}
});
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
myGirlViewModel.changeValue(position,"哈哈哈哈哈哈哈哈哈");
return false;
}
});
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
}
}
跨页面数据共享
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
public void sendData(View view) {
//一定要在主线程中调用
MyGirlViewModel myGirlViewModel = new ViewModelProvider(this).get(MyGirlViewModel.class);
myGirlViewModel.changeValue(0,"我要改变的是第1条数据呀呀呀呀呀呀呀");
//必须是MainActivity 可见才能收到信息,如果不可见哪怕发送了信息也是收不到的
finish();
}
}
3.配置变更ViewModel复用实现原理
准确点来说,应该是ViewModel
如何做到在宿主销毁了,还能继续存在.以至于页面恢复重建后,还能接着复用
肯定是前后获取到的是同一个ViewModel
实例对象
我们先来看下获取ViewModel
实例的过程
MyGirlViewModel myGirlViewModel = new ViewModelProvider(MainActivity.this).get(MyGirlViewModel.class);
ViewModelProvider
本质是从传递进去的ViewModelStore
来获取实例,如果没有,则利用factory
去创建
3.1 ViewModelProvider的创建
public class ViewModelProvider {
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
//把ViewModelStore 和 Factory 都保持到成员变量中
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
}
在创建ViewModelProvider
的时候需要传参数ViewModelStoreOwner
,
我们的MainActivity
是继承自顶层ComponentActivity
然后实现了ViewModelStoreOwner
接口的,所以我们可以直接传MainActivity
,可以发现创建ViewModelProvider
的时候会把ViewModelStore
和 Factory
都保存到成员变量中,那我们来看下ViewModelStore
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();
}
}
其实ViewModelStore
中就是封装了个HashMap
,用来存储ViewModel
然后我们来看下 Factory
,该工厂就是创造ViewModel
用的
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
就是说,当我们来获取ViewModel
实例的时候,如果在ViewModelStore
中获取不到,就会用Factory
去创造一个实例,
ok,到这里ViewModelProvider
对象已经创建完毕,接下来看ViewModelProvider
的get()
方法
3.2 ViewModelProvider调用get()方法
public class ViewModelProvider {
private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";
@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);
}
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//1.从mViewModelStore中根据key去找ViewModel
ViewModel viewModel = mViewModelStore.get(key);
//2.判断viewModel该实例是不是我们传入的modelClass类型的
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.
}
}
//3.如果不是就通过工厂来创建一个viewModel实例
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//4.创建完成之后存到mViewModelStore 的 map中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
}
当调用get()
方法的时候,我们只是传递了一个ViewModel
的.class
对象,就会把我们ViewModel
的className
名字拼接上DEFAULT_KEY
作为Key
,
这就是在ViewModelStore
中存储的Key
,Value
是我们的ViewModel
的.class
对象
我们知道了ViewModel
是从ViewModelStore
中获取的,那既然想做到ViewModel
实例的复用,那就是说ViewModelStore
也要复用,关键点就在这里
3.3 ViewModelStore
是在哪里获取的?
通过看ViewModelProvider
的构造方法,我们可以发现是在owner.getViewModelStore()
中获取的
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
追进去发现,owner.getViewModelStore()
这行代码是在ComponentActivity
中实现的,
也就是说ViewModelStore
是在ComponentActivity
中获取的
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
//保存用来配置变更后还想保存的数据
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
//1.viewModelStore是根据getLastNonConfigurationInstance()获取的
NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
//2.从NonConfigurationInstances中把viewModelStore提出来变成成员变量
mViewModelStore = nc.viewModelStore;
}
//3.如果mViewModelStore为空,则就创建一个
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}
在ComponentActivity
中的getViewModelStore
方法中我们发现
- 1.
viewModelStore
是根据getLastNonConfigurationInstance()
获取的 - 2.从
NonConfigurationInstances
中把viewModelStore
提出来变成成员变量 - 3.如果
mViewModelStore
为空,则就创建一个
那这个只是ViewModelStore
的获取,那我们想知道ViewModelStore
是在什么地方存的呢?
3.3 ViewModelStore
是在哪里存储的?
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
//保存用来配置变更后还想保存的数据
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
//什么时候被触发呢?
//是在`Activity`的`retainNonConfigurationInstances()`方法调用的
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
//如果我们想在activity保存一下数据,就是说因配置变更页面被销毁了,重建的时候继续复用
//我们就可以重写onRetainCustomNonConfigurationInstance()这个方法,然后获取的时候可以用getLastNonConfigurationInstance()
//不过这种方式已经被废弃掉了,了解即可,现在都推荐使用ViewModel
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
//如果viewModelStore 为空则就直接 return 空了
if (viewModelStore == null && custom == null) {
return null;
}
//如果viewModelStore 不为空,就创建NonConfigurationInstances对象并把viewModelStore存进去
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
}
ViewModelStore
是在ComponentActivity
的onRetainNonConfigurationInstance()
方法中存储的.
该方法中的onRetainCustomNonConfigurationInstance()
这行代码,
如果我们想在activity
保存一下数据,就是说因配置变更页面被销毁了,重建的时候继续复用
我们就可以重写onRetainCustomNonConfigurationInstance()
这个方法,然后获取的时候可以用getLastNonConfigurationInstance()
不过这种方式已经被废弃掉了,了解即可,现在都推荐使用ViewModel
小总结一下:
我们已经知道了ViewModelStore
是在onRetainNonConfigurationInstance()
方法中存储的,
但是我们要知道onRetainNonConfigurationInstance()
在什么情况下才会被调用,
只有这个方法调用了,我们的ViewModelStore
才能存到NonConfigurationInstances
中,这才能实现复用
3.4 onRetainNonConfigurationInstance()
方法又是在哪被调用的呢?
是在ComponentActivity
的父类Activity
的retainNonConfigurationInstances()
方法调用的
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback,
AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
//该方法又是在哪里调用的呢?是在ActivityTread中调用的
NonConfigurationInstances retainNonConfigurationInstances() {
//调用该方法
Object activity = onRetainNonConfigurationInstance();
......
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
public Object onRetainNonConfigurationInstance() {
return null;
}
}
那retainNonConfigurationInstances()
方法又是在哪里调用的呢?答案是在ActivityThread
中被调用的,
3.4 因配置变更重新启动一个activity
,会执行ActivityThread
的handleRelaunchActivity()
方法,继而调用retainNonConfigurationInstances()
我们都知道程序启动的时候首先要调用ActivityThread
的main
方法,
如果我们正常启动一个activity
的时候会调用ActivityThread
的handleLaunchActivity()
方法,
如果是因为配置变更重新启动一个activity
的时候,是走的handleRelaunchActivity()
方法,
这里先告诉大家,retainNonConfigurationInstances()
方法是在handleRelaunchActivity()
中的handleDestroyActivity()
方法中的performDestroyActivity()
方法中调用的,具体源码如下
public final class ActivityThread extends ClientTransactionHandler {
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
public static final class ActivityClientRecord {
//这个字段存储的就是因配置变更导致的被销毁的那个activity存留下来的数据
Activity.NonConfigurationInstances lastNonConfigurationInstances;
}
//正常启动一个activity会走这个方法
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {
..........
}
//1.因为配置变更重新启动一个`activity`的时候会调用这个方法
@Override
public void handleRelaunchActivity(ActivityClientRecord tmp,PendingTransactionActions pendingActions) {
.....
//mActivities这个集合存储的是当前app已经打开的所有activity
ActivityClientRecord r = mActivities.get(tmp.token);
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
.....
}
//2.因为配置变更重新启动一个`activity`的时候会调用这个方法
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
PendingTransactionActions pendingActions, boolean startsNotResumed,
Configuration overrideConfig, String reason) {
final Intent customIntent = r.activity.mIntent;
//重新创建一个activity之前要把发生配置变更的activity销毁
if (!r.paused) {
performPauseActivity(r, false, reason, null /* pendingActions */);
}
if (!r.stopped) {
callActivityOnStop(r, true /* saveState */, reason);
}
//该方法的第4个参数getNonConfigInstance,这里面传的是true
//经过这个方法后,ActivityClientRecord 这个r对象就包含了那个被销毁的activity所存留下来的对象
handleDestroyActivity(r.token, false, configChanges, true, reason);
......
//重新打开一个新的activity
handleLaunchActivity(r, pendingActions, customIntent);
}
//重要方法,经过这个方法后,ActivityClientRecord 这个r对象就包含了那个被销毁的activity所存留下来的对象
@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,configChanges, getNonConfigInstance, reason);
}
//该方法是真正执行Activity的destroy方法的
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (getNonConfigInstance) {//此处为true
//该行代码就会调用activity的retainNonConfigurationInstances方法,
//从而把Activity受到配置变更而不想丢失的数据给保存起来,那我们的viewModelStore对象也就被保存起来了
r.lastNonConfigurationInstances= r.activity.retainNonConfigurationInstances();
}
return r;
}
}
-
1.当一个
activity
重建的时候就会执行ActivityThread
中的handleRelaunchActivity()
,然后会经过一系列调用,
handleRelaunchActivity()
--->handleDestroyActivity()
--->performDestroyActivity()
--->r.activity.retainNonConfigurationInstances();
-
2.
retainNonConfigurationInstances()方法
,作用就是把Activity
受到配置变更而不想丢失的数据给保存起来,那我们的viewModelStore
对象也就被保存起来了,数据都会存在ActivityClientRecord
中,其中有个字段Activity.NonConfigurationInstances lastNonConfigurationInstances
,这个字段存储的就是因配置变更导致的被销毁的那个activity
存留下来的数据 -
3.当
handleDestroyActivity()
这个方法被调用完,我们的ActivityClientRecord
这个r对象
就包含了那个被销毁的activity
所存留下来的数据,最后会调用handleLaunchActivity()
重新打开一个activity
,接下来handleLaunchActivity()
中就调用performLaunchActivity()
方法
3.5handleLaunchActivity
方法创建新的Activity
并启动
public final class ActivityThread extends ClientTransactionHandler {
//正常启动一个activity会走这个方法
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {
final Activity a = performLaunchActivity(r, customIntent);
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
//1.新建一个activity
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
}
//2.执行attach方法把r.lastNonConfigurationInstances传进去了
activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken);
}
}
- 1.不难发现,在
performLaunchActivity()
方法中会通过newActivity()
的形式创建activity
- 2.执行
attach
方法把r.lastNonConfigurationInstances
传进去了
3.6Activity
最终把lastNonConfigurationInstances
对象保存了起来
public class Activity{
@UnsupportedAppUsage
NonConfigurationInstances mLastNonConfigurationInstances;
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
//保存了起来
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
}
}
所以在getLastNonConfigurationInstance()
方法中能拿到mLastNonConfigurationInstances
对象,那我们就能拿到我们的ViewModelStore
,从而完成我们实例对象的复用
总结
问题1:ViewModel
是如何实现因配置变更还能还原数据的呢?
准确点来说,应该是ViewModel
如何做到在宿主销毁了,还能继续存在.以至于页面恢复重建后,还能接着复用
- 1.首先我们的
ViewModel
是存到ViewModelStore
中的HashMap
中,然后我们这个ViewModelStore
是存到ComponentActivity
的NonConfigurationInstances
静态内部类中 - 2.当我们的
Activity
因为配置变更发生改变的时候,会调用ActivityThread
中的handleRelaunchActivity
,这里面有个handleDestroyActivity()
执行完这个方法后,就把Activity
受到配置变更而不想丢失的数据给保存起来,那我们的viewModelStore
对象也就被保存起来了 - 3.继而继续调用
handleLaunchActivity
方法创建新的Activity
并启动
问题2:ViewModel
和onSaveIntanceState
方法有什么区别呢?
- 1.
onSaveIntanceState
只能存储轻量级的key-value
键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在ActivityRecord
中 - 2.
ViewModel
可以存放任意Object
数据,因配置变更导致的页面被回收才有效,此时存在ActivityThread#ActivityClientRecord
中
网友评论