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;











 
    
评论