Android架構組件——ViewModel

陳曉松快點跑IP屬地: 廣東
0.621字數 2,310

概述

ViewModel,從字面上理解的話,它肯定是跟視圖(View)以及數據(Model)相關的。正像它字面意思一樣,它是負責準備和管理和UI組件(Fragment/Activity)相關的數據類,也就是說ViewModel是用來管理UI相關的數據的,同時ViewModel還可以用來負責UI組件間的通信。

之前存在的問題

ViewModel用來存儲和管理UI相關的數據,可于將一個Activity或Fragment組件相關的數據邏輯抽象出來,并能適配組件的生命周期,如當屏幕旋轉Activity重建后,ViewModel中的數據依然有效。

引入ViewModel之前,存在如下幾個問題:

  • 通常Android系統來管理UI controllers(如Activity、Fragment)的生命周期,由系統響應用戶交互或者重建組件,用戶無法操控。當組件被銷毀并重建后,原來組件相關的數據也會丟失,如果數據類型比較簡單,同時數據量也不大,可以通過onSaveInstanceState()存儲數據,組件重建之后通過onCreate(),從中讀取Bundle恢復數據。但如果是大量數據,不方便序列化及反序列化,則上述方法將不適用。
  • UI controllers經常會發送很多異步請求,有可能會出現UI組件已銷毀,而請求還未返回的情況,因此UI controllers需要做額外的工作以防止內存泄露。
    當Activity因為配置變化而銷毀重建時,一般數據會重新請求,其實這是一種浪費,最好就是能夠保留上次的數據。
  • UI controllers其實只需要負責展示UI數據、響應用戶交互和系統交互即可。但往往開發者會在Activity或Fragment中寫許多數據請求和處理的工作,造成UI controllers類代碼膨脹,也會導致單元測試難以進行。我們應該遵循職責分離原則,將數據相關的事情從UI controllers中分離出來。

ViewModel基本使用

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

    private void loadUsers() {
        // 異步調用獲取用戶列表
    }
}

新的Activity如下:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新 UI
        });
    }
}

如果Activity被重新創建了,它會收到被之前Activity創建的相同MyViewModel實例。當所屬Activity終止后,框架調用ViewModel的onCleared()方法清除資源。

因為ViewModel在指定的Activity或Fragment實例外存活,它應該永遠不能引用一個View,或持有任何包含Activity context引用的類。如果ViewModel需要Application的context(如獲取系統服務),可以擴展AndroidViewmodel,并擁有一個構造器接收Application。

在Fragment間共享數據

一個Activity中的多個Fragment相互通訊是很常見的。之前每個Fragment需要定義接口描述,所屬Activity將二者捆綁在一起。此外,每個Fragment必須處理其他Fragment未創建或不可見的情況。通過使用ViewModel可以解決這個痛點,這些Fragment可以使用它們的Activity共享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 onActivityCreated() {
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends LifecycleFragment {
    public void onActivityCreated() {
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // update UI
        });
    }
}

注意:上面兩個Fragment都用到了如下代碼來獲取ViewModel,getActivity()返回的是同一個宿主Activity,因此兩個Fragment之間返回的是同一個SharedViewModel對象。

SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

這種方式的好處包括:

  • Activity不需要做任何事情,也不需要知道通訊的事情
  • Fragment不需要知道彼此,除了SharedViewModel進行聯系。如果它們(Fragment)其中一個消失了,其余的仍然能夠像往常一樣工作。
  • 每個Fragment有自己的生命周期,而且不會受其它Fragment生命周期的影響。事實上,一個Fragment替換另一個Fragment,UI的工作也不會受到任何影響。

ViewModel的生命周期

ViewModel對象的范圍由獲取ViewModel時傳遞至ViewModelProvider的Lifecycle所決定。ViewModel始終處在內存中,直到Lifecycle永久地離開—對于Activity來說,是當它終止(finish)的時候,對于Fragment來說,是當它分離(detached)的時候。


viewmodel-lifecycle.png

上圖左側為Activity的生命周期過程,期間有一個旋轉屏幕的操作;右側則為ViewModel的生命周期過程。

一般通過如下代碼初始化ViewModel:

viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);

this參數一般為Activity或Fragment,因此ViewModelProvider可以獲取組件的生命周期。

Activity在生命周期中可能會觸發多次onCreate(),而ViewModel則只會在第一次onCreate()時創建,然后直到最后Activity銷毀。

ViewModel相關類圖

借用Android架構組件(三)——ViewModel的類圖:

這里寫圖片描述

  • ViewModelProviders是ViewModel工具類,該類提供了通過Fragment和Activity得到ViewModel的方法,而具體實現又是由ViewModelProvider實現的。

  • ViewModelProvider是實現ViewModel創建、獲取的工具類。在ViewModelProvider中定義了一個創建ViewModel的接口類——Factory。ViewModelProvider中有個ViewModelStore對象,用于存儲ViewModel對象。

  • ViewModelStore是存儲ViewModel的類,具體實現是通過HashMap來保存ViewModle對象。

  • ViewModel是個抽象類,里面只定義了一個onCleared()方法,該方法在ViewModel不在被使用時調用。ViewModel有一個子類AndroidViewModel,這個類是便于要在ViewModel中使用Context對象,因為我們前面提到不能在ViewModel中持有Activity的引用。

  • ViewModelStores是ViewModelStore的工廠方法類,它會關聯HolderFragment,HolderFragment有個嵌套類——HolderFragmentManager。

ViewModel相關時序圖

追溯創建一個ViewModel的源碼,會察覺需要的步驟有點多。下面以在Fragment中得到ViewModel對象為例看下整個過程的時序圖。
借用Android架構組件(三)——ViewModel的時序圖:

這里寫圖片描述

時序圖看起來比較復雜,但是它只描述了兩個過程:

  1. 得到ViewModel對象。
  2. HolderFragment被銷毀時,ViewModel收到onCleared()通知。

ViewModel相關源碼分析

ViewModelProviders類的具體實現:

public class ViewModelProviders {

    private static Application checkApplication(Activity activity) {
        Application application = activity.getApplication();
        if (application == null) {
            throw new IllegalStateException("Your activity/fragment is not yet attached to "
                    + "Application. You can't request ViewModel before onCreate call.");
        }
        return application;
    }

    private static Activity checkActivity(Fragment fragment) {
        Activity activity = fragment.getActivity();
        if (activity == null) {
            throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
        }
        return activity;
    }

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(checkActivity(fragment)));
        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
    }

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

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

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

ViewModelProviders提供了四個of()方法,四個方法功能類似,其中of(FragmentActivity activity, Factory factory)和of(Fragment fragment, Factory factory)提供了自定義創建ViewModel的方法。
1. 判斷Fragment的是否Attached to Activity,Activity的Application對象是否為空。
2. 創建ViewModel對象看似很簡單,一行代碼搞定。

new ViewModelProvider(ViewModelStores.of(fragment), factory)

先看看ViewModelStores.of()方法:

    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        return holderFragmentFor(fragment).getViewModelStore();
    }

繼續深入發現其實是實現了一個接口:

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

holderFragmentFor()是HolderFragment的靜態方法,HolderFragment繼承自Fragment。我們先看holderFragment()方法的具體實現

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(Fragment fragment) {
        return sHolderFragmentManager.holderFragmentFor(fragment);
    }

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }

繼續看HolderFragmentManager.holderFragmentFor()方法的具體實現

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

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);
            }
        }
    };

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

public HolderFragment() {
    //這個是關鍵,這就使得Activity被recreate時,Fragment的onDestroy()和onCreate()不會被調用
    setRetainInstance(true); 
}

setRetainInstance(boolean) 是Fragment中的一個方法。將這個方法設置為true就可以使當前Fragment在Activity重建時存活下來, 如果不設置或者設置為 false, 當前 Fragment 會在 Activity 重建時同樣發生重建, 以至于被新建的對象所替代。

在setRetainInstance(boolean)為true的 Fragment 中放一個專門用于存儲ViewModel的Map, 自然Map中所有的ViewModel都會幸免于Activity重建,讓Activity, Fragment都綁定一個這樣的Fragment, 將ViewModel存放到這個 Fragment 的 Map 中, ViewModel 組件就這樣實現了。

到此為止,我們已經得到了ViewStore對象,前面我們在創建ViewModelProvider對象是通過這行代碼實現的new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory)現在再看下ViewModelProvider的構造方法

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

現在就可以通過ViewModelProvider.get()方法得到ViewModel對象,繼續看下該方法的具體實現

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key); //從緩存中查找是否有已有ViewModel對象。

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass); //創建ViewModel對象,然后緩存起來。
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

ViewModelProvider.get()方法比較簡單,注釋中都寫明了。最后我們看下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);
    }

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

ViewModelStore是緩存ViewModel的類,put()、get()方法用于存取ViewModel對象,另外提供了clear()方法用于清空緩存的ViewModel對象,在該方法中會調用ViewModel.onCleared()方法通知ViewModel對象不再被使用。

ViewModel收到onCleared()通知
HolderFragment的onDestroy()方法

    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

在onDestroy()方法中調用了ViewModelStore.clear()方法,我們知道在該方法中會調用ViewModel的onCleared()方法。在你看了HolderFragment源碼后,或許你會有個疑問,mViewModelStore保存的ViewModel對象是在哪里添加的呢? 細心的話,你會發現在ViewModelProvider的構造方法中,已經將HolderFragment中的ViwModelStore對象mViewModelStore的引用傳遞給了ViewModelProvider中的mViewModelStore,而在ViewModelProvider.get()方法中會向mViewModelStore添加ViewModel對象。

總結

  • ViewModel職責是為Activity或Fragment管理、請求數據,具體數據請求邏輯不應該寫在ViewModel中,否則ViewModel的職責會變得太重,此處需要一個引入一個Repository,負責數據請求相關工作。具體請參考 Android架構組件。
  • ViewModel可以用于Activity內不同Fragment的交互,也可以用作Fragment之間一種解耦方式。
  • ViewModel也可以負責處理部分Activity/Fragment與應用其他模塊的交互。
  • ViewModel生命周期(以Activity為例)起始于Activity第一次onCreate(),結束于Activity最終finish時。

官方:
https://developer.android.google.cn/topic/libraries/architecture/viewmodel.html

©著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
11人點贊
陳曉松快點跑喜歡心理學愛寫程序的旅游博主
總資產14共写了5.1W字获得139个赞共28个粉丝
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容