深入了解架構(gòu)組件之ViewModel

官方文檔連接(需翻墻)

一党巾、介紹

ViewModel類是被設(shè)計用來以可感知生命周期的方式存儲和管理 UI 相關(guān)數(shù)據(jù),ViewModel中數(shù)據(jù)會一直存活即使 activity configuration發(fā)生變化榛鼎,比如橫豎屏切換的時候伐蒂。

以上是官網(wǎng)的話鸥昏,就不翻譯了沒啥意思存和,英語也不好。還是以我的大白話說說吧蜓席。
先來看 ViewModel 可以解決那些痛點。

1课锌、數(shù)據(jù)持久化

我們知道在屏幕旋轉(zhuǎn)的 時候 會經(jīng)歷 activity 的銷毀與重新創(chuàng)建厨内,這里就涉及到數(shù)據(jù)保存的問題,顯然重新請求或加載數(shù)據(jù)是不友好的渺贤。在 ViewModel 出現(xiàn)之前我們可以用 activity 的onSaveInstanceState()機制保存和恢復(fù)數(shù)據(jù)雏胃,但缺點很明顯,onSaveInstanceState只適合保存少量的可以被序列化志鞍、反序列化的數(shù)據(jù)瞭亮,假如我們需要保存是一個比較大的 bitmap list 固棚,這種機制明顯不合適。
由于 ViewModel 的特殊設(shè)計此洲,可以解決此痛點。
先來看下 ViewModel 生命周期圖:


image.png

由圖可知娶桦,ViewModel 生命周期是貫穿整個 activity 生命周期,包括 Activity 因旋轉(zhuǎn)造成的重創(chuàng)建衷畦,直到 Activity 真正意義上銷毀后才會結(jié)束。既然如此知牌,用來存放數(shù)據(jù)再好不過了祈争。

2、異步回調(diào)問題

通常我們 app 需要頻繁異步請求數(shù)據(jù)送爸,比如調(diào)接口請求服務(wù)器數(shù)據(jù)铛嘱。當(dāng)然這些請求的回調(diào)都是相當(dāng)耗時的,之前我們在 Activity 或 fragment里接收這些回調(diào)袭厂。所以不得不考慮潛在的內(nèi)存泄漏情況墨吓,比如 Activity 被銷毀后接口請求才返回。處理這些問題纹磺,會給我們增添好多復(fù)雜的工作帖烘。
但現(xiàn)在我們利用 ViewModel 處理數(shù)據(jù)回調(diào),可以完美的解決此痛點橄杨。

3秘症、分擔(dān) UI controller負(fù)擔(dān)

從最早的 MVC 到目前流行的 MVP照卦、MVVM,目的無非是 明確職責(zé)乡摹,分離 UI controller 負(fù)擔(dān)役耕。
UI controller 比如 Activity 、Fragment 是設(shè)計用來渲染展示數(shù)據(jù)聪廉、響應(yīng)用戶行為瞬痘、處理系統(tǒng)的某些交互。如果再要求他去負(fù)責(zé)加載網(wǎng)絡(luò)或數(shù)據(jù)庫數(shù)據(jù)板熊,會讓其顯得臃腫和難以管理框全。所以為了簡潔、清爽干签、絲滑津辩,我們可以分離出數(shù)據(jù)操作的職責(zé)給 ViewModel。

4容劳、Fragments 間共享數(shù)據(jù)

(可以先看下后面的用法介紹)

比如在一個 Activity 里有多個fragment喘沿,這fragment 之間需要做某些交互。我之前的做法是接口回調(diào)鸭蛙,需要統(tǒng)一在 Activity 里管理摹恨,并且不可避免的 fragment 之間還得互相持有對方的引用。仔細想想就知道這是很翔的一件事晒哄,耦合度高不說寝凌,還需要大量的容錯判斷(比如對方的 fragment 是否還活著)较木。

那么用 ViewModel 是怎么樣的呢(官網(wǎng)例子):
(activity 與其內(nèi)部的 fragment 可以共用一個ViewModel)

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

仔細體會下這樣的好處會發(fā)現(xiàn):
1、Activity 不需要做任何事致开,甚至不知道這次交互双戳,完美解耦。
2峭竣、Fragment 只需要 與ViewModel交互皆撩,不需要知道對方 Fragment 的狀態(tài)甚至是否存在毅访,更不需要持有其引用。所有當(dāng)對方 Fragment 銷毀時蟆融,不影響本身任何工作型酥。
3弥喉、Fragment 生命周期互不影響由境,甚至 fragment 替換成其他的 也不影響這個系統(tǒng)的運作虏杰。

二、用法簡介

ViewModel一般配合 LiveData 使用瘸彤,LiveData可以參考我另一篇文章质况。
首先结榄,獲取 ViewModel 實例潭陪,通過提供的類ViewModelProviders:

 MyViewModel model = ViewModelProviders.of(activity).get(MyViewModel.class);
或
 MyViewModel model = ViewModelProviders.of(fragment).get(MyViewModel.class);

或帶有 Factory 的

 MyViewModel model = ViewModelProviders.of(activity老厌,factory).get(MyViewModel.class);

VM 內(nèi)部操作:

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.
    }
}

然后枝秤,可在 activity 觀察數(shù)據(jù)變化:

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 生命周期開始的時刻著手分析薇溃,那么什么時候開始的呢沐序?廢話策幼,當(dāng)然是從我們實例化它開始特姐,那么我們什么時候?qū)嵗厥虻倬W(wǎng)的原話是:
You usually request a ViewModel the first time the system calls an activity object's onCreate() method.
沒錯滤钱,我們一般在 onCreate 里初始化件缸。

ViewModel 的出生:

實例化的代碼很簡單,我們慢慢剖析争剿,做了哪些事情

ViewModelProviders.of(activity蚕苇,factory).get(MyViewModel.class)

1涩笤、 首先是ViewModelProviders 的 of 方法:

    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
    }

    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @NonNull Factory factory) {
        checkApplication(activity);
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

參數(shù)有 activity 與 fragment 的我就只貼 activity 的了 蹬碧,重點看這里引出了一個 Factory恩沽,不帶Factory的方法只是通過initializeFactoryIfNeeded初始化了一個sDefaultFactory(Factory的實現(xiàn)類):

    /**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

只有一個 create 方法罗心,用腳指頭想想也知道肯定是用來初始化viewmodel的渤闷。先放著里等會用到再說。繼續(xù)看 of 方法:

return  new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory)

出現(xiàn)了兩個新的類ViewModelProvider與ViewModelStores爷贫,先看ViewModelProvider:

public class ViewModelProvider {

    private static final String DEFAULT_KEY =
            "android.arch.lifecycle.ViewModelProvider.DefaultKey";
 
    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;
    ……
    @NonNull
    @MainThread
    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;
    }

}

多余的都省略了卷员,此類很簡單 就是維護了 一個mFactory毕骡,一個新出現(xiàn)的類ViewModelStore(待會會講),并提供了用mFactory和ViewModelStore生成 ViewModel 的 get 方法窿撬。哇劈伴,這里就已經(jīng)看到 ViewModel 最終實例化的地方了握爷,但是別著急還有好多東西呢追城。

再來看

ViewModelStores.of(activity)

干了啥座柱。先看ViewModelStores:

/**
 * Factory methods for {@link ViewModelStore} class.
 */
@SuppressWarnings("WeakerAccess")
public class ViewModelStores {

    private ViewModelStores() {
    }

    /**
     * Returns the {@link ViewModelStore} of the given activity.
     *
     * @param activity an activity whose {@code ViewModelStore} is requested
     * @return a {@code ViewModelStore}
     */
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }

    /**
     * Returns the {@link ViewModelStore} of the given fragment.
     *
     * @param fragment a fragment whose {@code ViewModelStore} is requested
     * @return a {@code ViewModelStore}
     */
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        return holderFragmentFor(fragment).getViewModelStore();
    }
}

只有兩個 of 方法色洞,holderFragmentFor為 HolderFragment 初始化的靜態(tài)方法, 劇透一下HolderFragment 發(fā)揮了至關(guān)重要的作用景用,這里先不講其重要作用 只看getViewModelStore()

public class HolderFragment extends Fragment {
……
    private ViewModelStore mViewModelStore = new ViewModelStore();
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
……
}

沒啥說的伞插,看ViewModelStore:

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.get(key);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
        mMap.put(key, viewModel);
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

很明顯是一個用來存放 ViewModel 實例的類媚污,內(nèi)部維護了一個 HashMap 存放 ViewModel耗美,
并提供了 get航缀,put芥玉,clear方法。

至此ViewModelProviders of 做了哪些事情呢:
1赶袄、初始化了ViewModelProvider內(nèi)部維護了 用于創(chuàng)建 VM 的 Factory饿肺,和用戶存放 VM 的ViewModelStore敬辣;
2购岗、初始化了 用來生成 ViewModel 的 Factory(默認(rèn)為DefaultFactory)喊积;
3乾吻、通過ViewModelStores的靜態(tài)方法實例化了 HolderFragment绎签,并實例化了ViewModelStore

2诡必、然后是ViewModelProvider的 get 方法:

上面已經(jīng)貼過了,再貼一下吧:

ViewModelProviders.of(activity蟋字,factory).get(MyViewModel.class)鹊奖;

    @NonNull
    @MainThread
    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;
    }

邏輯不復(fù)雜,先看ViewModelStore是不是已經(jīng)存了唱捣,沒有的話就通過 factory 實例化, 并存到 ViewModelStore 中垫竞。

ViewModel 的死亡:

再來看 ViewModel 是如何結(jié)束其一生的。

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

Vm 只有一個onCleared方法徐裸,那么他是在何時調(diào)用的呢
這里就要介紹之前提到的 HolderFragment了啸盏,

public class HolderFragment extends Fragment {
    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();
    }

    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }

    static class HolderFragmentManager {
        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
        private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
        ……
         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;
        }
  ……
}

Vm 創(chuàng)建的時候提到過 實例化了一個 HolderFragment 。并且實例化的時候通過上面createHolderFragment 方法將其fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
我們知道commit 之后 fragment 將會擁有靈魂潜圃,獲得生命周期谭期。再看其onDestroy方法里
調(diào)用了 mViewModelStore.clear();

 public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }

至此隧出,Google 充分利用了 fragment 生命周期的特性胀瞪,使得 Vm 完成了 onCleared圆雁。
(這里我不得不說一下幔摸,Google 的 lifecycle 與 ViewModel 全都是利用 fragment 的一些特性去玩這些生命周期既忆,這么喜歡用 fragment是為什么我現(xiàn)在還沒完全領(lǐng)悟)

那么問題來了 為什么橫豎屏切換 ViewModel 不會 onCleared?
看 HolderFragment 的構(gòu)造方法里有個
setRetainInstance(true);
所以一切都了然了患雇,(如果你沒有了然就百度一下這個方法是干嘛的)
Google 真是充分的利用 fragment 特點跃脊。

四、注意

官網(wǎng)用一個大大的紅色感嘆號表明:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
由于 ViewModel 生命周期可能長與 activity 生命周期苛吱,所以為了避免內(nèi)存泄漏 Google 禁止在 ViewModel 中持有 Context 或 activity 或 view 的引用酪术。
這個讓我糾結(jié)了好久,后來發(fā)現(xiàn) 有一個 AndroidViewModel 類翠储,內(nèi)部維護了一個 ApplicationContext 實在不行也可以用著個

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绘雁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子援所,更是在濱河造成了極大的恐慌庐舟,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡摊求,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門凿渊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搪锣,“玉大人狗超,你說我怎么就攤上這事殴胧。” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵夯膀,是天一觀的道長俺猿。 經(jīng)常有香客問我,道長汽馋,這世上最難降的妖魔是什么驱敲? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任我碟,我火速辦了婚禮掸冤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布滔驶。 她就那樣靜靜地躺著插佛,像睡著了一般蚌铜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天生兆,我揣著相機與錄音,去河邊找鬼。 笑死映皆,一個胖子當(dāng)著我的面吹牛从隆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播内边,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼线罕!你這毒婦竟也來了唆樊?” 一聲冷哼從身側(cè)響起英古,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤舌缤,失蹤者是張志新(化名)和其女友劉穎介牙,沒想到半個月后环础,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡勺三,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抖誉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殊轴。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖袒炉,靈堂內(nèi)的尸體忽然破棺而出旁理,到底是詐尸還是另有隱情,我是刑警寧澤我磁,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布孽文,位于F島的核電站,受9級特大地震影響夺艰,放射性物質(zhì)發(fā)生泄漏芋哭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一郁副、第九天 我趴在偏房一處隱蔽的房頂上張望减牺。 院中可真熱鬧,春花似錦霞势、人聲如沸烹植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽草雕。三九已至,卻和暖如春固以,著一層夾襖步出監(jiān)牢的瞬間墩虹,已是汗流浹背嘱巾。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诫钓,地道東北人旬昭。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像菌湃,于是被迫代替她去往敵國和親问拘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容