MVVM 系列之三:ViewModel,最新 Android 开发进阶
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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=".user.UserActivity">
<TextView
android:id="@+id/tv_show"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="UserActivity"
android:gravity="center"
android:textSize="20sp"
android:textAllCaps="false"
/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_show"
android:layout_marginTop="20dp"
android:visibility="gone"
/>
<Button
android:id="@+id/button"
android:layout_width="200dp"
android:layout_height="40dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar"
android:layout_marginTop="20dp"
android:text="点击获取用户信息"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
页面有个按钮用于点击获取用户信息,有个 TextView 展示用户信息。 在 onCreate()中先 创建 ViewModelProvider 实例,传入的参数是 ViewModelStoreOwner,Activity 和 Fragment 都是其实现。然后通过 ViewModelProvider 的 get 方法 获取 ViewModel 实例,然后就是 观察 ViewModel 中的 LiveData。
运行后,点击按钮 会弹出进度条,2s 后展示用户信息。接着旋转手机,我们发现用户
信息依然存在。来看下效果:
旋转手机后确实是重建了 Activity 的,日志打印如下:
2021-01-06 20:35:44.984 28269-28269/com.hfy.androidlearning I/UserActivity: onStop:
2021-01-06 20:35:44.986 28269-28269/com.hfy.androidlearning I/UserActivity: onDestroy:
2021-01-06 20:35:45.025 28269-28269/com.hfy.androidlearning I/UserActivity: onCreate:
总结下:
ViewModel 的使用很简单,作用和原来的 Presenter 一致。只是要结合 LiveData,UI 层观察即可。
ViewModel 的创建必须通过 ViewModelProvider。
注意到 ViewModel 中没有持有任何 UI 相关的引用。
旋转手机重建 Activity 后,数据确实恢复了。
2.2 Fragment 间数据共享
Activity 中的多个 Fragment 需要相互通信是一种很常见的情况。假设有一个 ListFragment,用户从列表中选择一项,会有另一个 DetailFragment 显示选定项的详情内容。在之前 你可能会定义接口或者使用 EventBus 来实现数据的传递共享。
现在就可以使用 ViewModel 来实现。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:
public class ShareViewModel extends ViewModel {
public MutableLiveData<String> selected = new MutableLiveData<>();
}
public class Share2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share2);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".share.ShareActivity">
<fragment
android:id="@+id/fragment_list"
android:name="com.gs.createmvvm210422.share2.MyList2Fragment"
android:layout_width="100dp"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/fragment_detail"
/>
<fragment
android:id="@+id/fragment_detail"
android:name="com.gs.createmvvm210422.share2.Detail2Fragment"
android:layout_width="200dp"
android:layout_height="match_parent"
app:layout_constraintLeft_toRightOf="@+id/fragment_list"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
public class MyList2Fragment extends ListFragment {
private List<String> list;
private ShareViewModel shareViewModel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, null);
list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add("item" + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_list_item_1, list);
setListAdapter(adapter);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
shareViewModel = ViewModelProviders.of(requireActivity()).get(ShareViewModel.class);
}
@Override
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
shareViewModel.selected.setValue(list.get(position));
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#2ebbfb">
<ListView
android:id="@+id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
public class Detail2Fragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_detail, null);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView tvShow = view.findViewById(R.id.tv_show);
ShareViewModel model = ViewModelProviders.of(requireActivity()).get(ShareViewModel.class);
// ShareViewModel model = new ViewModelProvider(requireActivity()).get(ShareViewModel.class);
model.selected.observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(String s) {
tvShow.setText(s);
}
});
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#FFF0F5">
<TextView
android:id="@+id/tv_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Text"
android:textSize="20sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
代码很简单,ListFragment 中在点击 Item 时更新 ViewModel 的 LiveData 数据,然后 DetailFragment 监听这个 LiveData 数据即可。
要注意的是,这两个 Fragment 通过 ViewModelProvider 获取 ViewModel 时 传入的都是它们宿主 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。
此方法具有以下 优势:
Activity 不需要执行任何操作,也不需要对此通信有任何了解。
除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
最后来看下效果:
3、源码分析
======
经过前面的介绍,我们知道 ViewModel 的核心点 就是 因配置更新而界面(Activity/Fragment)重建后,ViewModel 实例依然存在,这个如何实现的呢? 这就是我们源码分析的重点了。
在获取 ViewModel 实例时,我们并不是直接 new 的,而是使用 ViewModelProvider 来获取,猜测关键点应该就在这里了。
3.1 ViewModel 的存储和获取
先来看下 ViewModel 类:
public abstract class ViewModel {
...
private volatile boolean mCleared = false;
//在 ViewModel 将被清除时调用
//当 ViewModel 观察了一些数据,可以在这里做解注册 防止内存泄漏
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
...
onCleared();
}
...
}
ViewModel 类 是抽象类,内部没有啥逻辑,有个 clear()方法会在 ViewModel 将被清除时调用。
然后 ViewModel 实例的获取是通过 ViewModelProvider 类,见名知意,即 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;
}
例子中我们使用的是只需传 ViewModelStoreOwner 的构造方法,最后走到两个参数 ViewModelStore、factory 的构造方法。继续见名知意:ViewModelStoreOwner——ViewModel 存储器拥有者;ViewModelStore——ViewModel 存储器,用来存 ViewModel 的地方;Factory——创建 ViewModel 实例的工厂。
ViewModelStoreOwner 是个接口:
public interface ViewModelStoreOwner {
//获取 ViewModelStore,即获取 ViewModel 存储器
ViewModelStore getViewModelStore();
}
实现类有 Activity/Fragment,也就是说 Activity/Fragment 都是 ViewModel 存储器的拥有者,具体是怎样实现 获取 ViewModelStore 的呢?
先不急,我们先看 ViewModelStore 如何存储 ViewModel、以及 ViewModel 实例如何获取的。
/**
用于存储 ViewModels.
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());
}
/**
调用 ViewModel 的 clear()方法,然后清除 ViewModel
如果 ViewModelStore 的拥有者(Activity/Fragment)销毁后不会重建,那么就需要调用此方法
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
ViewModelStore 代码很简单,viewModel 作为 Value 存储在 HashMap 中。
再来看下创建 ViewModel 实例的工厂 Factory,也就是 NewInstanceFactory:
public static class NewInstanceFactory implements Factory {
...
@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);
}
}
}
很简单,就是通过传入的 class 反射获取 ViewModel 实例。
回到例子中,我们使用viewModelProvider.get(UserViewModel.class)
来获取 UserViewModel 实例,那么来看下 get()方法:
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");
//拿到 Key,也即是 ViewModelStore 中的 Map 的用于存 ViewModel 的 Key
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//从 ViewModelStore 获取 ViewModel 实例
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
//如果从 ViewModelStore 获取到,直接返回
return (T) viewModel;
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
//没有获取到,就使用 Factory 创建
viewModel = (mFactory).create(modelClass);
}
//存入 ViewModelStore 然后返回
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
逻辑很清晰,先尝试从 ViewModelStore 获取 ViewModel 实例,key 是"androidx.lifecycle.ViewModelProvider.DefaultKey:xxx.SharedViewModel",如果没有获取到,就使用 Factory 创建,然后存入 ViewModelStore。
到这里,我们知道了 ViewModel 如何存储、实例如何获取的,但开头说的分析重点:“因配置更新而界面重建后,ViewModel 实例依然存在”,这个还没分析到。
3.2 ViewModelStore 的存储和获取
回到上面的疑问,看看 Activity/Fragment 是怎样实现 获取 ViewModelStore 的,先来看 ComponentActivity 中对 ViewModelStoreOwner 的实现:
//ComponentActivity.java
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
//activity 还没关联 Application,即不能在 onCreate 之前去获取 viewModel
throw new IllegalStateException("Your activity is not yet attached to the "
"Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
//如果存储器是空,就先尝试 从 lastNonConfigurationInstance 从获取
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
评论