ViewModel 原始碼分析

ppjunSpirit發表於2018-07-31

簡單介紹

ViewModel 原始碼分析
從圖可以看出來,ViewModel 與 LiveData 和 Paging 是谷歌新元件,同時它是 android.arch.lifecycle 包裡面的類,可以支援 activity 和 fragment 共享資料(比如在 fragment 獲取 activity 搜尋框的內容)當然 activity 銷燬了資料就不存在了;又或者是 fragment 與子 fragment 共享資料(比如 fragment 裡面巢狀了 viewpager , viewpager 裡又有 fragemnt)。

使用方法

//build.gralde
implementation 'android.arch.lifecycle:extensions:1.1.1'  //包括了 viewmodel 和 livedata
implementation 'android.arch.lifecycle:compiler:1.1.1'
implementation 'android.arch.lifecycle:common-java8:1.1.1'
複製程式碼

建立一個類傳字串,然後在 activity 例項化並且賦值。

//KeywordModel.kt
class KeywordModel : ViewModel() {

    var keyword: MutableLiveData<String>? = MutableLiveData()

    fun getKeyWord(): LiveData<String> {

        return keyword!!
    }

    fun setKeyWord(key: String) {
        keyword?.value = key
    }
}
//MainActivity.kt
val keywordModel=ViewModelProviders.of(this).get(KeywordModel::class.java)  //在activity例項化

keywordModel.setKeyWord("siri")  //賦值

複製程式碼

在fragment 獲取值

//ExampleFragment.kt
val keywordModel = ViewModelProviders.of(activity as MainActivity).get(KeywordModel::class.java)

keywordModel.getKeyWord().observe(this, Observer<String> {
            mPresenter.requestArticleList(requireNotNull(it), true)
        })
複製程式碼

以上就完成了 ViewModel 的基本使用了。

原始碼分析

在分析原始碼之前,應該思考一下,如果這個功能由你來寫,你會是怎麼實現,用什麼資料結構去存資料,然後根據這個功能,可以擴充出什麼其他用途。

首先看建立方法。ViewModelProviders 的 of 方法可以傳入 fragment 或者是 activity,就是說你可以在fragment 或者 activity 建立 viewmodel,而這個 viewmodel 只會依賴你的建立的 activity 或者是 fragment(都是同一個viewmodel)。我們先來分析 傳入引數是 activity 的這種情況。

//ViewModelProviders.java
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }

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(ViewModelStores.of(activity), factory);
    }
複製程式碼

呼叫了兩個引數的 of 方法,在 of 方法裡面,通過 activity 來獲取 applicaiton 從而來建立預設的 AndroidViewModelFactory 工廠。 這個工廠是 ViewModelProvider 的靜態內部類。主作用是同是通過反射來獲取 viewmodel

//AndroidViewModelFactory.java
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (Exception e) {
                    throw new RuntimeException("", e);
                }
            }
            return super.create(modelClass);
        }
複製程式碼

還有另外一個引數 ViewModelStores.of(activity),判斷了當前activity有沒有實現 ViewModelStoreOwner 介面,而我們沒實現,就下一步吧。呼叫了 HolderFragment 的 holderFragmentFor 來建立一個 holderFragment, 新增到 activity 上(當然這個fragment 沒介面),而這個 fragment 上有 mViewModelStore 的例項。

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

而在 ViewModelStore 裡面有 hashmap ,通過 key 獲取儲存 viewmodel 。所以 viewmodel 使用了hashmap來儲存 viewmodel 啦。

//ViewModelStore.java
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();
    }
}
複製程式碼

而看回 ViewModelProvider 這個類,通過get方法來獲取viewmodel,從下面的的 get 方法可以看到 hashmap 的 key 是由DEFAULT_KEY 字首和的 viewmodel 的 canonicalName 來組成。並且get的時候 會先從hashmah 中獲取viewmodel ,不存在這個viewmodel,再從 mFactory 裡面建立 viewmodel ,並存進 hashmap。

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

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 = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

複製程式碼

同理

當viewmodelproviders.of 傳入fragment時,也會走上面的流程,只不過生成holderfragment的時候用getChildFragmentManager()

//HolderFragment.java
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;
        }
複製程式碼

小總結

  1. ViewModel 是從 ViewModelproviders 通過 viewmodelandroidfactory 工廠 和 holderfragment 獲取到一個 viewmodelprovider ,呼叫viewmodelprovider 的 get 方法從 hashmap 獲取對用的 viewmodel。 管理 hashmap 的ViewModelStore 在 holderfragment 裡面,而這個holderfragment為什麼不會隨著 activity 的重建而不銷燬呢,這是因為對應的 holderfragment 設定了setRetainInstance(true)。不會隨著重建 activity 而銷燬。
  2. 可以建立多個 viewmodel,而 activity 和 fragment 中 holderfragment 只有一個。

ps:在 RxPermissions 中也用到setRetainInstance這個屬性。

2018年08月27日09:22:29更新

ViewModel 儲存 Activity Fragment View 等例項時,會因為 Activity 或者 Fragment 的銷燬 造成記憶體洩漏,所以 ViewModel 不可以儲存 Activity Fragment View 等例項。

相關文章