Jetpack ViewModel

在說起Jetpack的ViewModel時,我們第一反應(yīng)都會想到它就是MVVM中的VM。然而這兩者并不是等價關(guān)系,Jetpack的ViewModel它只是官方為我們提供的一個具體工具,他的作用其實就是一個數(shù)據(jù)管理容器切油。而MVVM是一種構(gòu)架思想,VM是這里面的一層名惩,用來鏈接V與M層白翻。
由于Jetpack的ViewModel的獨有特性我們可以對它進(jìn)行拓展,使它充當(dāng)MVVM的VM角色。

1.ViewModel的使用

創(chuàng)建ViewModel

public class MyViewModel extends ViewModel { 
    public int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}

獲取ViewModel對象

public class ViewModelActivity extends AppCompatActivity {
    private MyViewModel viewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        viewModel.setCount(2);
        viewModel.getCount();
    }
}

當(dāng)然上面的示例代碼沒有具體意義滤馍,但是純粹使用ViewModel就是這么簡單,先拋開ViewModel在MVVM中的使用底循,我們可以用ViewModel來做些什么呢巢株,它有怎樣的優(yōu)勢?

2.ViewModel的使用場景

2.1.橫豎屏切換時熙涤,Activity重新創(chuàng)建阁苞,可以通過ViewModel保存數(shù)據(jù)。

ViewModel與Activity生命周期關(guān)系如下:


image.png

可以看到祠挫,在屏幕旋轉(zhuǎn)Activity重新創(chuàng)建的過程中那槽,ViewModel一直保持存活。

2.2.同一個Activity下等舔,F(xiàn)ragment之間的數(shù)據(jù)共享骚灸。

我們可以將數(shù)據(jù)存放到Activity的ViewModel中,在Activity中的Fragment中通過以下代碼獲取到Activity中的ViewModel慌植,實現(xiàn)數(shù)據(jù)共享甚牲。

    viewModel = ViewModelProviders.of(activity).get(MyViewModel.class);

3.ViewModel的實現(xiàn)原理

(下面的源碼分析都基于在Activity中使用ViewModel,旨在理清流程,在Fragmeng中的原理大致相同)

3.1.ViewModel的獲取

ViewModelProviders.of(activity).get(MyViewModel.class)
--ViewModelProviders.java
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);
}

這里需要注意activity.getViewModelStore()蝶柿,它是一個ViewModelStore對象丈钙,ViewModelStore的實現(xiàn)非常簡單,內(nèi)部維護(hù)一個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);
    }
    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelProviders.of(activity)獲取到ViewModelProvider后雏赦,通過ViewModelProvider.get(modelClass)獲取ViewModel。

--ViewModelProvider.java

private static final String DEFAULT_KEY =
            "androidx.lifecycle.ViewModelProvider.DefaultKey";

 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)) {
        return (T) viewModel;
    } else {
        if (viewModel != null) {      
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

這里的mViewModelStore就是上面new ViewModelProvider(activity.getViewModelStore(), factory)時傳入的activity的ViewModelStore芙扎。
整體流程:在獲取ViewModel時星岗,先從Acactivitytivity中的ViewModelStore容器中獲取,
獲取的key為DEFAULT_KEY +ViewModel.class.getCanonicalName()纵顾。沒有獲取到時通過Factory創(chuàng)建對應(yīng)ViewModel,并添加到Acactivitytivity中的ViewModelStore容器中伍茄,下次可以直接獲取到。
所以在一個Activity中同一個class對應(yīng)的ViewModel只會存在一個施逾,這也就是上面場景2的使用基礎(chǔ)敷矫。

3.2.清楚ViewModel的獲取流程后,接下來看ViewModel怎樣做到旋轉(zhuǎn)屏幕依然保持存活的汉额。

我們知道Activity在屏幕旋轉(zhuǎn)時默認(rèn)會重新創(chuàng)建Activity,新的Activity是不會保存原Activity的數(shù)據(jù)的曹仗,那ViewModel是怎樣跨Activity對象得到保存的呢?
根據(jù)3.1我們知道ViewModel存放在Activity的ViewModelStore中蠕搜,我們看一下activity.getViewModelStore():

    --ComponentActivity.java

    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) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

第一次獲取ViewModelStore時怎茫,會先調(diào)用getLastNonConfigurationInstance()獲取到一個NonConfigurationInstances對象,并嘗試從NonConfigurationInstances中獲取ViewModelStore,沒有獲取到時才會創(chuàng)建一個ViewModelStore轨蛤;

    --ComponentActivity.java

   NonConfigurationInstances mLastNonConfigurationInstances;

   public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

   final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        ......
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ......
    }

mLastNonConfigurationInstances是ComponentActivity中的一個變量蜜宪,它在Activity創(chuàng)建時attach()中被系統(tǒng)傳入。
mLastNonConfigurationInstances.activity的保存發(fā)生在

     public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }
        if (viewModelStore == null && custom == null) {
            return null;
        }
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

最后還有一個小細(xì)節(jié)祥山,在androidx.activity.ComponentActivity的構(gòu)造方法中對當(dāng)前Activity的生命周期進(jìn)行了監(jiān)聽圃验。

    androidx.activity.ComponentActivity.java

    public ComponentActivity() {
        ......
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }

可以看到Activity在Destroy時,如果不是屏幕旋轉(zhuǎn)才會清空tViewModelStore()中的ViewModel缝呕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末澳窑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子供常,更是在濱河造成了極大的恐慌摊聋,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栈暇,死亡現(xiàn)場離奇詭異麻裁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瞻鹏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門悲立,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人新博,你說我怎么就攤上這事薪夕。” “怎么了赫悄?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵原献,是天一觀的道長。 經(jīng)常有香客問我埂淮,道長姑隅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任倔撞,我火速辦了婚禮讲仰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痪蝇。我一直安慰自己鄙陡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布躏啰。 她就那樣靜靜地躺著趁矾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪给僵。 梳的紋絲不亂的頭發(fā)上毫捣,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼蔓同。 笑死饶辙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牌柄。 我是一名探鬼主播畸悬,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼珊佣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起披粟,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤咒锻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后守屉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惑艇,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年拇泛,在試婚紗的時候發(fā)現(xiàn)自己被綠了滨巴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡俺叭,死狀恐怖恭取,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熄守,我是刑警寧澤蜈垮,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站裕照,受9級特大地震影響攒发,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晋南,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一惠猿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧负间,春花似錦偶妖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玩祟,卻和暖如春腹缩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工藏鹊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留润讥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓盘寡,卻偏偏與公主長得像楚殿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子竿痰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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