【AAC 系列四】深入理解架構元件:ViewModel

程式亦非猿發表於2019-06-13

0. 前言

本文是深入理解「Android Architecture Components」系列文章第四篇 原始碼基於 AAC 1.1.1 版本

【AAC 系列一】Android 應用架構新時代來臨!

【AAC 系列二】深入理解架構元件的基石:Lifecycle

【AAC 系列三】深入理解架構元件:LiveData

這個連結不要點,有毒,千萬不要

在上一篇 LiveData 原理分析一文中,我們提到了 ViewModel ,它跟 LiveData 配合能夠把價值發揮到最大。

這一篇,我們就來深入淺出一下 ViewModel ,來講講 ViewModel 的使用方式、生命週期、以及它的實現原理。

1. ViewModel 概述

在深入講解 ViewModel 之前,先來簡單瞭解下 ViewModel:

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModelclass allows data to survive configuration changes such as screen rotations.

ViewModel 被設計來管理跟 UI 相關的資料, 並且能夠感知生命週期;另外 ViewModel 能夠在配置改變的情況下讓資料得以保留。ViewModel 重在以感知生命週期的方式 管理介面相關的資料。

我們知道類似旋轉螢幕等配置項改變會導致我們的 Activity 被銷燬並重建,此時 Activity 持有的資料就會跟隨著丟失,而ViewModel 則並不會被銷燬,從而能夠幫助我們在這個過程中儲存資料,而不是在 Activity 重建後重新去獲取。並且 ViewModel 能夠讓我們不必去擔心潛在的記憶體洩露問題,同時 ViewModel 相比於用onSaveInstanceState() 方法更有優勢,比如儲存相對大的資料,並且不需要序列化以及反序列化。

總之 ViewModel,優點多多,接下去我們介紹下 ViewModel 的基本使用。

2. ViewModel 的基本使用

ViewModel 的使用也非常簡單,Android 提供了一個 ViewModel 類讓我們去繼承,並且提供了 ViewModelProviders 來幫助我們例項化 ViewModel。

搬運一個官網例子如下:

a):先新增一下依賴:

def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
複製程式碼

b):自定義一個MyViewModel 繼承自ViewModel,並且包含了一個 LiveData

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}
複製程式碼

c):在 Activity 中藉助 ViewModelProviders 獲得 ViewModel 的例項,並藉助 LiveData 訂閱 users 的變化通知:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}
複製程式碼

就這樣簡單的步驟,我們就使用上了 ViewModel,即便 MyActivity 重新建立,MyActivity 拿到的 MyViewModel 都會是一個例項。

那麼問題來了, ViewModel 的生命週期到底是怎麼樣的呢?

它背後蘊藏什麼原理呢?我們們接下來看看。

3. ViewModel 的生命週期

我們在前面提到過,ViewModel 並不會因為 Activity 的配置改變銷燬而一起銷燬,那麼 ViewModel 的生命週期到底是怎麼樣的呢?

看一張官網給的圖:

【AAC 系列四】深入理解架構元件:ViewModel

可以看到 ViewModel 在 Activity 的重建時依然存活。

Why

4. ViewModel 的實現原理

回顧下我們之前使用 ViewModel 的程式碼:

MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
複製程式碼

這裡先從 ViewModelProviders.of 方法入手看看:

//ViewModelProviders
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
  //傳入了 null
  return of(activity, null);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
  //檢查合法性
  Application application = checkApplication(checkActivity(fragment));
  if (factory == null) {
    //走到這裡,返回了 AndroidViewModelFactory 的單例
    factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
  }
  return new ViewModelProvider(ViewModelStores.of(fragment), factory);
}
複製程式碼

ViewModelProviders.of() 方法幫我們在預設情況下構建了一個 AndroidViewModelFactory 工廠類,來幫助建立 ViewModel,並且返回了一個在當前 Activity 生命週期內的 ViewModelProvider

看一下 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) {
                  //...
                }
            }
            return super.create(modelClass);
        }
    }
複製程式碼

AndroidViewModelFactory 其實就是一個通過反射方法來構建 ViewModel 的工廠類,且是個單例。

看下來發現我們 ViewModel 的 class 是傳給了 ViewModelProvider.get() 方法。

來看看 get 方法:

//ViewModelProvider
@NonNull
@MainThread
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
  return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
  //先從 store 中獲取
  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 = mFactory.create(modelClass);
  mViewModelStore.put(key, viewModel);
  //noinspection unchecked
  return (T) viewModel;
}
複製程式碼

解釋下程式碼,發現它為每個 ViewModel 的 class 都構建了一個唯一的 key , 並通過這個 key 嘗試去一個叫做 ViewModelStore 的類獲取 ViewModel 的例項,如果為null ,那麼會通過 Factory 去建立,並把新的例項存入到 ViewModelStore。

主要的流程似乎就跟我們平時做快取一樣,好像沒什麼特別的東西,沒有看到一絲跟生命週期相關的處理的程式碼,這是怎麼回事?

再仔細思考一下,get 方法會優先從這個 ViewModelStore 中去拿,那麼理論上只要保證 ViewModelStore 這個類在配置變化的過程中沒有被銷燬,那麼就可以保證我們建立的 ViewModel 不會被銷燬,我們肯定漏掉了關於 ViewModelStore 的重要東西。

回過去再仔細看一下,ViewModelProvider 的構建好像並不簡單:

new ViewModelProvider(ViewModelStores.of(fragment), factory);
複製程式碼

這裡傳入了一個 通過 ViewModelStores 類建立的 ViewModelStore,並且傳入了 fragment,一定有蹊蹺。

//ViewModelStores
public static ViewModelStore of(@NonNull Fragment fragment) {
  if (fragment instanceof ViewModelStoreOwner) {
    return ((ViewModelStoreOwner) fragment).getViewModelStore();
  }
  return holderFragmentFor(fragment).getViewModelStore();
}
複製程式碼

這裡又多出來了幾個類 ViewModelStoreOwner、HolderFragment ,讓我們來追查一下。

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}
複製程式碼

ViewModelStoreOwner 跟 LifecycleOwner 類似,只是個介面定義,重點來看看holderFragmentFor(fragment) 方法返回的 HolderFragment

//HolderFragment
public static HolderFragment holderFragmentFor(Fragment fragment) {
  return sHolderFragmentManager.holderFragmentFor(fragment);
}
複製程式碼

方法又走到了 HolderFragmentManager 類,怎麼又多了個 HolderFragmentManager ,神煩啊。

static class HolderFragmentManager {
    private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
    private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();

    private ActivityLifecycleCallbacks mActivityCallbacks =
            new EmptyActivityLifecycleCallbacks() {
                @Override
                public void onActivityDestroyed(Activity activity) {
                    HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
                    if (fragment != null) {
                        Log.e(LOG_TAG, "Failed to save a ViewModel for " + activity);
                    }
                }
            };

    private boolean mActivityCallbacksIsAdded = false;

    private FragmentLifecycleCallbacks mParentDestroyedCallback =
            new FragmentLifecycleCallbacks() {
                @Override
                public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {
                    super.onFragmentDestroyed(fm, parentFragment);
                    HolderFragment fragment = mNotCommittedFragmentHolders.remove(
                            parentFragment);
                    if (fragment != null) {
                        Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment);
                    }
                }
            };

    void holderFragmentCreated(Fragment holderFragment) {
        Fragment parentFragment = holderFragment.getParentFragment();
        if (parentFragment != null) {
            mNotCommittedFragmentHolders.remove(parentFragment);
            parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                    mParentDestroyedCallback);
        } else {
            mNotCommittedActivityHolders.remove(holderFragment.getActivity());
        }
    }

    private static HolderFragment findHolderFragment(FragmentManager manager) {
        if (manager.isDestroyed()) {
            throw new IllegalStateException("Can't access ViewModels from onDestroy");
        }

        Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
        if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
            throw new IllegalStateException("Unexpected "
                    + "fragment instance was returned by HOLDER_TAG");
        }
        return (HolderFragment) fragmentByTag;
    }

    private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
        HolderFragment holder = new HolderFragment();
        fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
        return holder;
    }

    HolderFragment holderFragmentFor(FragmentActivity activity) {
        FragmentManager fm = activity.getSupportFragmentManager();
        HolderFragment holder = findHolderFragment(fm);
        if (holder != null) {
            return holder;
        }
        holder = mNotCommittedActivityHolders.get(activity);
        if (holder != null) {
            return holder;
        }

        if (!mActivityCallbacksIsAdded) {
            mActivityCallbacksIsAdded = true;
            activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
        }
        holder = createHolderFragment(fm);
        mNotCommittedActivityHolders.put(activity, holder);
        return holder;
    }

    HolderFragment holderFragmentFor(Fragment parentFragment) {
        FragmentManager fm = parentFragment.getChildFragmentManager();
        HolderFragment holder = findHolderFragment(fm);
        if (holder != null) {
            return holder;
        }
        holder = mNotCommittedFragmentHolders.get(parentFragment);
        if (holder != null) {
            return holder;
        }

        parentFragment.getFragmentManager()
                .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);
        holder = createHolderFragment(fm);
        mNotCommittedFragmentHolders.put(parentFragment, holder);
        return holder;
    }
}
複製程式碼

從原始碼中我們可以獲知 HolderFragmentManager 主要做這幾件事:

  1. 在我們想要獲取 ViewModel 例項的時候,會先構建一個 HolderFragment,將它新增我們的宿主(Activity/Fragment)中,並將它快取起來;

  2. 同時通過註冊回撥來監聽宿主的生命週期,Activity 對應 ActivityLifecycleCallbacks ,Fragment 對應 FragmentLifecycleCallbacks ,在宿主銷燬的時候清理快取;

類如其名,HolderFragmentManager負責管理 HolderFragment,看到它注入了 HolderFragment,接下去看看 HolderFragment。

HolderFragment 原始碼精簡如下:

public class HolderFragment extends Fragment implements ViewModelStoreOwner {
    private static final String LOG_TAG = "ViewModelStores";

    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String HOLDER_TAG =
            "android.arch.lifecycle.state.StateProviderHolderFragment";

    private ViewModelStore mViewModelStore = new ViewModelStore();
		//看這裡看這裡看這裡
    public HolderFragment() {
        setRetainInstance(true);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }
		...
    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }
}
複製程式碼

HolderFragment 內部持有了一個ViewModelStore,並且實現了我們之前提到的 ViewModelStoreOwner 介面,並且最為主要的是這段程式碼:

public HolderFragment() {
  setRetainInstance(true);
}
複製程式碼

Fragment.setRetainInstance(true) 方法可以實現的效果為,在 Activity 配置改變後依然儲存。

到這裡 ViewModel 實現的原理就清晰了:通過注入一個retainInstance 為 true 的 HolderFragment ,利用 Fragment 的特性來保證在 Activity 配置改變後依然能夠存活一下,並且保證了 HolderFragment 內部的 ViewModelStore 的存活,最終保證了 ViewModelStore 內部儲存的 ViewModel 快取存活,從而實現了 ViewModel 的生命週期這個特點功能。(又是 Fragment!)

5. 圖解 ViewModel

ViewModel 重點類類圖:

【AAC 系列四】深入理解架構元件:ViewModel

ViewModel原理實現序列圖:

【AAC 系列四】深入理解架構元件:ViewModel

6. 知識點梳理和彙總

重點類講解

  • ViewModel ,抽象類,用來負責準備和管理 Activity/Fragment 的資料,並且還能處理 Activity/Fragment 跟外界的通訊,通常還存放業務邏輯,類似 Presenter;ViewModel 通常會暴露 LiveData 給 Activity/Fragment;並且 Activity 配置改變並不會導致 ViewModel 回收;
  • AndroidViewModel,一個會持有 Application 的 ViewModel;
  • ViewModelStore ,負責儲存 ViewModel 的類,並且還負責在 ViewModel 被清除之前通知它,也即呼叫 ViewModel.onCleared();
  • ViewModelStoreOwner , 是抽象 “ViewModelStore 的擁有者” 的介面定義,類似 LifecycleOwner 的角色,實現了它的有 HolderFragment、FragmentActivity;
  • HolderFragment,一個 retainInstance屬性為true 並實現了 ViewModelStoreOwner 的 Fragment,用來儲存 ViewModelStore,並保證它在配置修改時不被銷燬;
  • HolderFragmentManager ,負責建立、注入、快取等管理 HolderFragment 的工作;

ViewModel 原理總結

通過注入一個 retainInstancetrue 的 HolderFragment ,利用 Fragment 的特性來保證在 Activity 配置改變後依然能夠存活下來,並且保證了 HolderFragment 內部的 ViewModelStore 的存活,最終保證了 ViewModelStore 內部儲存的 ViewModel 快取存活,從而實現 ViewModel 在 Activity 配置改變的情況下不銷燬的功能。

ViewModel 的使用注意事項

  1. 不要持有 Activity :ViewModel 不會因為 Activity 配置改變而被銷燬,所以絕對不要持有那些跟 Activity 相關的類,比如Activity 裡的某個 View,讓 ViewModel 持有 Activity 會導致記憶體洩露,還要注意的是連 Lifecycle 也不行;
  2. 不能訪問 UI :ViewModel 應該只負責管理資料,不能去訪問 UI,更不能持有它;

7. 總結

ViewModel 利用 Fragment 的特性,提供給我們一個方式在特定的生命週期內去管理跟 UI 相關的資料;能夠幫助我們把資料管理的邏輯從 Activity/Fragment 中剝離開。

實際上 ViewModel 不僅可以管理資料,而且還可以存放業務邏輯處理的程式碼,另外還能夠方便 Activity 中的不同Fragment 之間互相通訊,這個解決了以往我們 Fragment 之間通訊的一個大問題。

深入瞭解完 Lifecycle、LiveData、ViewModel 之後,可以發現它們確實非常強大,能夠切實的幫助我們解決實際開發過程中遇到的問題。

強烈推薦大家趕緊上車體驗,再晚就買不到票了啊。

8. 參考與推薦

  1. developer.android.com/topic/libra…

  2. medium.com/androiddeve…

公眾號:程式亦非猿

相關文章