Android Jetpack組件之ViewModel篇

PS:原文首發於微信公衆號:躬行之(jzman-blog)

前面學習了 LiveData 和 Lifecycle 架構組件的使用:

ViewModel 具有生命週期意識,會自動存儲和管理 UI 相關的數據,即使設備配置發生變化後數據還會存在,我們就不需要在 onSaveInstanceState 保存數據,在 onCreate 中恢復數據了,使用 ViewModel 這部分工作就不需要我們做了,很好地將視圖與邏輯分離開來。

  1. ViewModel生命週期
  2. ViewModel的源碼分析
  3. 什麼是ViewModelStore
  4. 什麼是ViewModelStoreOwner
  5. 如何簡化Fragment之間的通信

ViewModel生命週期

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 的切換就可以了,測試效果如下:

jzman-blog

使用 ViewModel 的優勢如下:

  1. Activity 不在介入子 Fragment 之間的通信了,職責更單一。
  2. Fragment 之間除了使用同一個 ViewModel 的實例,其他互不相同,任何一個 Fragment 都可單獨工作。
  3. 每個 Fragment 都有自己的生命週期,可以隨意替換和移除都不會互相影響另一個 Fragment 的正常工作。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章