我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:xiangzhihong,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply
在早期的Android开发中,由于应用相对较小,页面相对简单,我们会将数据请求、页面UI处理和数据加载全部放在Activity或Fragment中进行,但是随着项目的迭代,这种开发方式显得越来越臃肿,并且也不易于项目的维护和扩展。
此时,借鉴后端的后端程序的开发思路,我们对Android项目进行了分层,典型的有MVC,MVP和MVVM等项目分层,然后每层负责自己的事情即可。以现在流行的MVVM模式为例。
在JetPack架构中,ViewModel组件是一个可以感知生命周期的形式来存储和管理视图相关的数据的组件,因此它适合以下场景。
除此之外,ViewModel的好处还有很多,但是最终的目的就是为了让代码可维护性更高,降低代码的冗余程度。
我们知道,Android的Activity/Fragment是有生命周期的,我们可以在不同的生命周期函数中执行不同的操作来达到不同的目的。由于ViewModel是保存在内存中的,所以ViewModel的生命周期并不会随Activity/Fragment的生命周期发生变化 。
下图是官方给出的ViewModel与Activity的生命周期的对应关系示意图。
从上图可以看出,ViewModel会伴随着Activity/Fragment的整个生命周期,直到ViewModel绑定的Activity/Fragment执行onDestroy()方法之后才会被销毁。
1,添加gradle以来,如下所示。
dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
}
2,创建一个继承自ViewModel类的MyViewModel类,创建ViewModel类千万不能持有Context的引用,否则会引起内存泄漏,如果需要使用Context可以继承AndroidViewModel。
public class MyViewModel extends ViewModel {
private MutableLiveData<String> user;
public LiveData<String> getUsers() {
if (user == null) {
user = new MutableLiveData<String>();
loadUsers();
}
return user;
}
private void loadUsers() {
user.setValue("Android应用开发实战");
}
}
3, 为了避免内存泄漏,我们可以在onCleared()方法中进行资源释放操作。然后,我们在Activity中就可以使用MyViewModel,如下所示。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
model.getUsers().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.d(TAG, "LiveData监听数据返回:"+s);
}
});
}
}
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), { item ->
// Update the UI.
});
}
}
ViewModel类是一个抽象接口,其部分源码如下。
public abstract class ViewModel {
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
@SuppressWarnings("unchecked")
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
if (mBagOfTags == null) {
return null;
}
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
可以发现,ViewModel抽象类的主要作用就是使用HashMap存储数据。ViewModel 有一个子类AndroidViewModel,它的源码如下。
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@NonNull
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
与继承ViewModel不同,AndroidViewModel需要提供一个 Application 的 Context。
在前面的示例代码中,我们在Activity中使用ViewModelProviders.of方法来获取ViewModel实例,如下所示。
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
打开ViewModelProviders类的源码,可以发现ViewModelProviders一共有四个构造方法,都是用来创建ViewModelProvider对象,只不过参数不同而已。
public static ViewModelProvider of(@NonNull Fragment fragment) {
return of(fragment, null);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
在构建ViewModelProvider的时候需要用到ViewModelStore和Factory,下面我们来分别介绍一下它们。
ViewModelStore主要作用是存储ViewModel的容器,当我们打开ViewModelStore的源码时会发现ViewModelStore是通过HashMap来存储ViewModel的数据的。并且,ViewModelStore还提供了一个clear方法,用来清空Map集合里面的ViewModel,我们可以在Activity/Fragment的onDestroy方法执行clear方法执行ViewModel数据的清除。
protected void onDestroy() {
super.onDestroy();
if (mViewModelStore != null && !isChangingConfigurations()) {
mViewModelStore.clear();
}
}
当我们使用ViewModelProvider获取ViewModel实例时,ViewModelProvider一共提供了4个构造函数,另一个比较重要的构造函数是
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory{
mFactory = factory;
mViewModelStore = store;
}
ViewModelProvider的第二个参数是factory,它的子类有NewInstanceFactory和AndroidViewModelFactory两个,我们可以使用ViewModelProvider.AndroidViewModelFactory.getInstance
获取单例的Factory对象,NewInstanceFactory源码如下。
public static class NewInstanceFactory implements Factory {
@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);
}
}
}
而AndroidViewModelFactory的源代码如下。
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private static AndroidViewModelFactory sInstance;
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
private Application mApplication;
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
@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);
}
}
AndroidViewModelFactory实例化构造方法里面有个参数class,可以引用Context,其实是Appplication实例。在上面的代码中,如果是有application参数,则通过newInstance(application)实例化,否则调用父类的create方法然后通过newInstance()实例化。如果通过newInstance(application)实例化,就可以在ViewModel里面拿到Context,由于Application是APP全局的生命周期最长,所以就不存在内存泄露问题。
前面说过,ViewModel是不会随着Activity/Fragment的销毁而销毁的,因为ViewModel是将数据使用ViewModelStore 保存在HashMap 中,所以只要ViewModelStore不被销毁,则ViewModel的数据就不会被销毁。
众所周知,Android在横竖屏切换时会触发onSaveInstanceState,然后在还原时则会触发onRestoreInstanceState。除此之外,Android的Activity类还提供了onRetainNonConfigurationInstance和getLastNonConfigurationInstance两个方法,当设备状态发生改变时,上面的两个方法就会被系统调用。
其中,onRetainNonConfigurationInstance 方法用于处理配置发生改变时数据的保存,而getLastNonConfigurationInstance则用于恢复创建Activity时获取上次保存的数据。首先,我们来看一下onRetainNonConfigurationInstance方法,如下所示。
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
...
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
可以发现,ViewModel会将数据存储在 NonConfigurationInstances 对象中,而NonConfigurationInstances是定义在Activity里面的一个类,如下所示。
static final class NonConfigurationInstances {
Object activity;
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}
再来看一下getLastCustomNonConfigurationInstance方法,
getLastNonConfigurationInstance方法返回的数据就是NonConfigurationInstances.activity属性,如下所示。
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
现在,我们再看一下ComponentActivity 的 getViewModelStore方法,如下所示。
@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) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
可以发现,在getViewModelStore方法中我们首先会获取NonConfigurationInstances对象,不为空则从其身上拿到ViewModelStore,也就是之前保存的ViewModelStore,然后当Activity被再次创建的时候恢复数据。
需要说明的是,onRetainNonConfigurationInstance方法会在onSaveInstanceState方法之后被调用,即调用顺序同样在onStop方法和 onDestroy方法之间。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。