前面學習了 LiveData 和 Lifecycle 架構組件的使用:
ViewModel 具有生命週期意識,會自動存儲和管理 UI 相關的數據,即使設備配置發生變化後數據還會存在,我們就不需要在 onSaveInstanceState 保存數據,在 onCreate 中恢復數據了,使用 ViewModel 這部分工作就不需要我們做了,很好地將視圖與邏輯分離開來。
- ViewModel 生命週期
- ViewModel 的源碼分析
- 什麼是 ViewModelStore
- 什麼是 ViewModelStoreOwner
- 如何簡化 Fragment 之間的通信
ViewModel 生命週期#
從 OnCreate 獲取到 ViewModel 之後,它會一直存在,直到該 ViewModel 綁定的 View 徹底 onDestory。
ViewModel 的源碼分析#
本次創建項目是升級 Android Studio 為 3.2.1,所以直接將項目中的依賴包替換成 androidx 下面的對應包,主要配置如下:
//gradle插件
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
// ViewModel and LiveData版本
def lifecycle_version = "2.0.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
//gradle-wrapper.properties文件
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
創建 ViewModel 如下:
/**
*
* 如果需要使用Context可以選擇繼承AndroidViewModel
* Powered by jzman.
* Created on 2018/12/13 0013.
*/
public class MViewModel extends ViewModel {
private MutableLiveData<List<Article>> data;
public LiveData<List<Article>> getData(){
if (data == null){
data = new MutableLiveData<>();
data.postValue(DataUtil.getData());
}
return data;
}
}
如果需要使用 Context 可以選擇繼承 AndroidViewModel,這裡繼承 ViewModel 就可以了,然後,在 Activity 中使用就可以了,具體如下:
MViewModel mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
mViewModel.getData().observe(this, new Observer<List<Article>>() {
@Override
public void onChanged(List<Article> articles) {
for (Article article : articles) {
Log.i(TAG,article.getDesc());
}
}
});
來看一看調用過程,從 ViewModelProviders 開始,ViewModelProviders 主要提供四個靜態方法獲取對應的 ViewModelProvider,四個靜態方法如下:
public static ViewModelProvider of(@NonNull Fragment fragment)
public static ViewModelProvider of(@NonNull FragmentActivity activity)
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory)
public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory)
以第二個方法為例,其實現如下:
@NonNull
@MainThread
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);
}
可以使用用默認的 AndroidViewModelFactory,也可以自定義 Factory,直接調用上面任意一個方法創建 ViewModelProvider 即可:
那就來看一下 ViewModelProvider,ViewModelProvider 中兩個關鍵屬性:
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
當創建完 ViewModelProvider 的時候,mFactory 和 mViewModelStore 已經被初始化了,然後是 get () 方法,源碼如下:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
//獲取類名稱,在獲取內部類名稱時與getName有所區分
//getCanonicalName-->xx.TestClass.InnerClass
//getName-->xx.TestClass$InnerClass
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
然後調用帶參數 key 的 get 方法如下:
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.
}
}
//創建ViewModel
viewModel = mFactory.create(modelClass);
//從mViewModelStore中根據key獲取對應的ViewModel返回
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
此時,ViewModel 就創建好了,那 VIewModel 是如何被創建的呢,mFactory 的具體實現這裡是默認的 AndroidViewModelFactory,其創建時通過反射獲取構造方法創建的,關鍵代碼如下:
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//判斷AndroidViewModel是不是modelClass的父類或接口
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//...
//反射創建ViewModel並返回
return modelClass.getConstructor(Application.class).newInstance(mApplication);
}
return super.create(modelClass);
}
具體的 ViewModel 對象創建完成之後,就可以隨意調用具體的 ViewModel 中的方法了,前面跟源碼的時候會遇到各種封裝類,如 ViewModelStore、ViewModelStoreOwner、AndroidViewModelFactory 等,下文中將會介紹。
什麼是 ViewModelStore#
ViewModelStore 主要是用來保存當設備配置發生變化的時候保存 ViewModel 的狀態,如當前界面被重新創建或者銷毀等,對應的新的 ViewModelStore 應該和舊的 ViewModelStore 一樣保存對應 ViewModel 的所有信息,只有調用了對應的 clear () 方法才會通知這個 ViewModel 不在使用,其對應的 ViewModelStore 也不會存儲相關信息了。
該類實際上使用 HashMap 存儲相應的 ViewModel,非常簡單:
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);
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
什麼是 ViewModelStoreOwner#
這是一個接口,定義了一個方法 getViewModelStore () 用來獲取對應 ViewModel 的 ViewModelStore,同樣調用了 ViewModelStoreOwner 的 clear () 方法,則獲取不到對應的 ViewModelStore 了,源碼如下:
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
當然,具體的肯定是實現類了,實際上像 FragmentActivity 、Fragment 等都間接或直接實現了這個接口,這一點和 LifecycleOwner 一樣,源碼參考如下:
- Activity 間接實現:
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;
}
- Fragment 直接實現:
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
其保存 ViewModelStore 過程是在 Activty 或 Fragment 的上層實現中完成,對於認識 ViewModelStoreOwner 這個接口到這裡就 OK 了。
如何簡化 Fragment 之間的通信#
Fragment 之間的通信以前是使用接口通過宿主 Activity 轉發來實現的,現在可以使用同一 ViewModel 完成兩個 Fragment 之間的通信,記住一點,使用 ViewModel 進行兩個 Fragment 之間通信的時候,創建 ViewModel 使用其宿主 Activity 來創建,實現過程如下,首先創建一個 ViewModel 如下:
/**
* Powered by jzman.
* Created on 2018/12/14 0014.
*/
public class FViewModel extends ViewModel {
private MutableLiveData<String> mSelect = new MutableLiveData<>();
public void selectItem(String item) {
mSelect.postValue(item);
}
public LiveData<String> getSelect() {
return mSelect;
}
}
然後,創建 LeftFragment 如下:
public class LeftFragment extends Fragment {
private FViewModel mViewModel;
private FragmentTitleBinding titleBinding;
public LeftFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title, container, false);
titleBinding = DataBindingUtil.bind(view);
mViewModel = ViewModelProviders.of(getActivity()).get(FViewModel.class);
RvAdapter adapter = new RvAdapter(getActivity(), new RvAdapter.OnRecycleItemClickListener() {
@Override
public void onRecycleItemClick(String info) {
mViewModel.selectItem(info);
}
});
titleBinding.rvData.setLayoutManager(new LinearLayoutManager(getActivity()));
titleBinding.rvData.setAdapter(adapter);
return view;
}
}
LeftFragment 布局文件就一個 RecycleView,其 Item 的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="itemData"
type="String"/>
<variable
name="onItemClick"
type="com.manu.archsamples.fragment.RvAdapter.OnRecycleItemClickListener"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="@{() -> onItemClick.onRecycleItemClick(itemData)}">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{itemData}"
android:padding="10dp"/>
</LinearLayout>
</layout>
RecyclerView 的 Adapter 如下:
public class RvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<String> mData;
private OnRecycleItemClickListener mOnRecycleItemClickListener;
public RvAdapter(Context mContext,OnRecycleItemClickListener itemClickListener) {
this.mContext = mContext;
mData = DataUtil.getDataList();
mOnRecycleItemClickListener = itemClickListener;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recycle_item,null);
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
return new MViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MViewHolder mHolder = (MViewHolder) holder;
mHolder.bind(mData.get(position),mOnRecycleItemClickListener);
}
@Override
public int getItemCount() {
return mData.size();
}
private static class MViewHolder extends RecyclerView.ViewHolder{
RecycleItemBinding itemBinding;
MViewHolder(@NonNull View itemView) {
super(itemView);
itemBinding = DataBindingUtil.bind(itemView);
}
void bind(String info, OnRecycleItemClickListener itemClickListener){
itemBinding.setItemData(info);
itemBinding.setOnItemClick(itemClickListener);
}
}
public interface OnRecycleItemClickListener {
void onRecycleItemClick(String info);
}
}
然後,創建 RightFragment 如下:
public class RightFragment extends Fragment {
private static final String TAG = RightFragment.class.getName();
private FragmentContentBinding contentBinding;
private FViewModel mViewModel;
public RightFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container, false);
contentBinding = DataBindingUtil.bind(view);
mViewModel = ViewModelProviders.of(getActivity()).get(FViewModel.class);
mViewModel.getSelect().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
//接收LeftFragment Item 點擊事件的值
contentBinding.setData(s);
}
});
return view;
}
}
RightFragment 的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="String"/>
</data>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.LeftFragment">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:text="@{data,default=def}"/>
</FrameLayout>
</layout>
實現方式比較簡單,沒什麼多說的,使用 ViewModel 之後,宿主 Activity 就非常清爽,只負責 Fragment 的切換就可以了,測試效果如下:
使用 ViewModel 的優勢如下:
- Activity 不再介入子 Fragment 之間的通信了,職責更單一。
- Fragment 之間除了使用同一個 ViewModel 的實例,其他互不相同,任何一個 Fragment 都可單獨工作。
- 每個 Fragment 都有自己的生命週期,可以隨意替換和移除都不會互相影響另一個 Fragment 的正常工作。