Android的ViewModel正確使用姿勢(shì)饱亿?

看了網(wǎng)上很多對(duì)于ViewModel的講解蚜退,對(duì)比了官方的使用闰靴,自覺(jué)有點(diǎn)官方譯文科普的意思,即使看很多钻注,仍舊沒(méi)有醍醐灌頂?shù)母杏X(jué)蚂且,于是,深入源碼分析后幅恋,便想將對(duì)于ViewModel的使用以及定位做一些簡(jiǎn)單的記錄杏死,如與編者有不一樣的看法,希望在評(píng)論區(qū)一起討論捆交。文章旨在拋磚引玉淑翼,并無(wú)教學(xué)之意。

對(duì)于ViewModel的官方介紹:

ViewModel 類(lèi)旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù)品追。ViewModel類(lèi)讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存玄括。


從介紹來(lái)看,仿佛ViewModel有自己的生命周期肉瓦?看到有些文章也是這樣描述遭京,提到:ViewModel會(huì)維護(hù)自己的生命周期。那么泞莉,它真的會(huì)維護(hù)自己的生命周期嗎哪雕?

問(wèn)題一:ViewModel生命周期?

首先鲫趁,ViewModel是如何創(chuàng)建的斯嚎?

class MainViewModel : ViewModel() {}
class MainActivity : ComponentActivity() {
 private val model: MainViewModel by viewModels()
}

我通過(guò)官方給的委托函數(shù)進(jìn)行了ViewModel的初始化蹲盘,直接跟進(jìn)去查看ViewModel的創(chuàng)建笔喉,可以找到關(guān)鍵的創(chuàng)建語(yǔ)句,

ViewModelProvider(store, factory).get(MainViewModel::class.java)

直接跟到get方琺的實(shí)現(xiàn)

 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        ……略過(guò)
        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)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            if (viewModel != null) {
            }
        }
        if (mFactory instanceof KeyedFactory) {
            //走到這里,記住create方法
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        //請(qǐng)記住這個(gè)東西
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

那么可以看出來(lái)ViewModelProvider的兩個(gè)參數(shù)

  • mViewModelStore :ViewModel的復(fù)用有關(guān)
  • Factory :與創(chuàng)建有關(guān)

先不管復(fù)用規(guī)則实撒,找到當(dāng)前創(chuàng)建ViewModel的Factory幽崩,找到委托方法的factory可以看到

public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
     val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
     }
     return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

找到ComponentActivity的默認(rèn)Factory->SavedStateViewModelFactory

    @NonNull
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
    //順便看一下它的構(gòu)造方法
    public SavedStateViewModelFactory(@Nullable Application application,
            @NonNull SavedStateRegistryOwner owner,
            @Nullable Bundle defaultArgs) {
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        //這里很關(guān)鍵
        mFactory = application != null
                ? ViewModelProvider.AndroidViewModelFactory.getInstance(application)
                : ViewModelProvider.NewInstanceFactory.getInstance();
    }

可以看到苦始,在創(chuàng)建SavedStateViewModelFactory的同時(shí),又看到了兩個(gè)Factory慌申。
暫且不管陌选,追一下這個(gè)默認(rèn)的Factory是如何執(zhí)行Create方法的

 public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel && mApplication != null) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        //這句注釋很有意思,留神蹄溉。
        // doesn't need SavedStateHandle  
        if (constructor == null) {
            return mFactory.create(modelClass);
        }
        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        try {
            T viewmodel;
            if (isAndroidViewModel && mApplication != null) {
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
                viewmodel = constructor.newInstance(controller.getHandle());
            }
            return viewmodel;
    }

這段代碼是在尋找ViewModel的構(gòu)造器咨油,可以看到這段代碼的最后是要調(diào)用ViewModel的兩個(gè)帶參構(gòu)造函數(shù)。
這里是個(gè)疑點(diǎn)柒爵,為什么要傳遞參數(shù)呢?為什么會(huì)固定有一個(gè)參數(shù)役电?
先保留疑問(wèn),繼續(xù)看可以發(fā)現(xiàn)棉胀,我創(chuàng)建的ViewModel走了在SavedStateViewModelFactory構(gòu)造函數(shù)中創(chuàng)建的Factory法瑟,這里發(fā)現(xiàn)AndroidViewModelFactory派生自NewInstanceFactory冀膝,再看一下AndroidViewModelFactory的create函數(shù)是如何實(shí)現(xiàn)的

  public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            }
            return super.create(modelClass);
        }
    }

發(fā)現(xiàn)AndroidViewModelFactory會(huì)判斷AndroidViewModel是不是modelClass超類(lèi)或超接口,如果是霎挟,則直接調(diào)用構(gòu)造函數(shù)的Application參數(shù)方法窝剖,
如果不是

 public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
      return modelClass.newInstance();       
 }

直接創(chuàng)建。
那么ViewModel就創(chuàng)建完畢了酥夭,emm...等等赐纱,我不是要找它的生命周期嗎?為什么沒(méi)有看到有關(guān)生命周期的代碼熬北?
難道奧秘都在剛才遺忘的mViewModelStore中疙描?看一下它的代碼

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

這。讶隐。起胰。不就是個(gè)容器嗎?它在哪里創(chuàng)建的巫延?再回顧一下委托函數(shù)

public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

哦待错,可以看到在我注冊(cè)的Activity中有一個(gè)viewModelStore,那么它是如何創(chuàng)建的?

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.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

這里的getLastNonConfigurationInstance()只需要知道烈评,這個(gè)是一種界面恢復(fù)與保存的API,跟它一樣的有我們熟知的onSaveInstanceState / onRestoreInstanceState犯建,如果你感興趣可以查閱資料讲冠,不過(guò)這個(gè)Api在新的Sdk中已經(jīng)不建議使用了,這里一會(huì)我們繼續(xù)談适瓦。
所以說(shuō)竿开,ViewModelStore保存了每個(gè)ViewModel的實(shí)例,在頁(yè)面重構(gòu)時(shí)做了處理玻熙。讓每個(gè)界面的ViewModelStore不會(huì)因?yàn)橹貥?gòu)而消失否彩。那么,它怎樣才能消失呢嗦随?難道ViewModel會(huì)一直存在嗎列荔?
當(dāng)然不是,ViewModelStore明明有clear()方法枚尼,那么它的調(diào)用時(shí)機(jī)是怎樣的呢贴浙?


查找Clear調(diào)用.png

查找了下,第一個(gè)正是我當(dāng)前想要知道的署恍,它對(duì)應(yīng)的代碼如下

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

所以崎溃,在創(chuàng)建ComponentActivity的時(shí)候注冊(cè)了監(jiān)聽(tīng)當(dāng)生命周期到ON_DESTORY的時(shí)候并且不是更改配置,就清空Store盯质。
分析完畢袁串。

結(jié)論:ViewModel并沒(méi)有自己的生命周期概而,官方所謂的能在界面變動(dòng)時(shí)不丟失數(shù)據(jù),也是因?yàn)閂iewModel的容器在對(duì)應(yīng)的邏輯做了保存處理囱修。


問(wèn)題二:既然如此赎瑰,那么我可不可以制作一個(gè)由我控制生命周期的ViewModel呢?

當(dāng)然可以

class AppViewModelStore : ViewModelStoreOwner , LifecycleEventObserver{
    
    var store: ViewModelStore = ViewModelStore()

    override fun getViewModelStore(): ViewModelStore {
        return store
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        //在Life走到ON_PAUSE清理蔚袍。舉例說(shuō)明乡范,不建議使用。
        if (event == Lifecycle.Event.ON_PAUSE) {
            store.clear()
        }
    }
}
//使用
ViewModelProvider(AppViewModelStore()).get(MainViewModel::class.java)

問(wèn)題三:我可不可以創(chuàng)建一個(gè)自定義參數(shù)的ViewModel呢啤咽?

分析源碼可以知道晋辆,store負(fù)責(zé)ViewModel保存與銷(xiāo)毀,F(xiàn)actory負(fù)責(zé)創(chuàng)建宇整,所以直接重寫(xiě)Factory即可瓶佳。

class MyVMFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(參數(shù)) as T
    }
}
//使用
ViewModelProvider(AppViewModelStore(),MyVMFactory()).get(MainViewModel::class.java)

只需要像源碼那樣創(chuàng)建就可以,只要把握ViewModelStore的保存與銷(xiāo)毀即可鳞青。



到這里霸饲,不免有點(diǎn)失落,viewModel就這臂拓?
google真的只是幫開(kāi)發(fā)者做了這些嗎厚脉?就當(dāng)我準(zhǔn)備結(jié)束的時(shí)候,突然想起胶惰,在分析ViewModel生命周期中的一段注釋

    /**
     * Use this instead of {#onRetainNonConfigurationInstance()}.
     * Retrieve later with {#getLastCustomNonConfigurationInstance()}.
     *
     * @deprecated Use a { androidx.lifecycle.ViewModel} to store non config state.
     */

是的傻工,在getLastCustomNonConfigurationInstance()方法與onRetainNonConfigurationInstance()方法上的注釋?zhuān)屛矣肰iewModel去做配置項(xiàng)的東西,相當(dāng)于孵滞,onSaveInstanceState與onRestoreInstanceState中捆,可以用ViewModel直接配置。
看到這句話你明白了嗎坊饶?
我舉一個(gè)開(kāi)發(fā)中的例子:
當(dāng)用戶的手機(jī)在后臺(tái)很久之后泄伪,系統(tǒng)會(huì)回收應(yīng)用的資源,導(dǎo)致一些數(shù)據(jù)丟失匿级,打開(kāi)應(yīng)用會(huì)丟失數(shù)據(jù)或者崩潰蟋滴,因此很多的開(kāi)發(fā)者會(huì)把一部分?jǐn)?shù)據(jù)做本地持久化處理,沒(méi)有持久化處理的數(shù)據(jù)需要用onSaveInstanceState與onRestoreInstanceState進(jìn)行保存與恢復(fù)根蟹。然后項(xiàng)目的代碼變成了這樣

  override fun onSaveInstanceState(outState: Bundle) {
        outState.putBoolean("MBoolean", true)
        outState.putInt("MInt", 1)
        outState.putString("MString", "save Info")
        ```
        省略
        ```
        super.onSaveInstanceState(outState)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        savedInstanceState.getBoolean("MBoolean", true)
        savedInstanceState.getInt("MInt", 1)
        savedInstanceState.getString("MString", "save Info")
        ```
        省略
        ```
        super.onRestoreInstanceState(savedInstanceState)
    }

是的脓杉,這樣不容易維護(hù),大量的代碼讓人摸不著頭腦简逮,因此很多應(yīng)用都統(tǒng)一讓用戶重新加載球散。
那么用ViewModel怎么解決了這個(gè)問(wèn)題呢?

class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    fun getType(){
        ```
        業(yè)務(wù)邏輯
        ```
        savedStateHandle.get<String>("sKey")
    }

    fun putMessage(){
        ```
        業(yè)務(wù)邏輯
        ```
        savedStateHandle["sKey"] = "save info"
    }
}

只需要在ViewModel的構(gòu)造器中添加SavedStateHandle參數(shù)即可散庶。
它的創(chuàng)建是在我的第一個(gè)疑惑點(diǎn)蕉堰,在分析Factory的創(chuàng)建代碼中對(duì)于構(gòu)造器的選擇凌净。
當(dāng)一次業(yè)務(wù)代碼完成后即可選擇是否要將業(yè)務(wù)內(nèi)容保存下來(lái),當(dāng)系統(tǒng)因內(nèi)存不足回收應(yīng)用數(shù)據(jù)后屋讶,應(yīng)用仍然能第一時(shí)間回到當(dāng)前界面冰寻。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),只需要選擇保存與回調(diào)的數(shù)據(jù)皿渗。
具體代碼請(qǐng)參考

SavedStateViewModelFactory.java
 public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
     ……省略
    SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    ……
}

因篇幅問(wèn)題斩芭,不做詳細(xì)分析。
其實(shí)這些東西就是在開(kāi)發(fā)者文檔中的一些用法乐疆,只是我從一個(gè)點(diǎn)走過(guò)了全程划乖,在我沒(méi)有閱讀文檔情況下,我發(fā)現(xiàn)了這些用法挤土,于是便去文檔中進(jìn)行查找琴庵。地址在這里


最后

我突然想到,如果這樣那ViewModel的構(gòu)造函數(shù)豈不是無(wú)法自定義了仰美?并且自定義ViewModelStore也不能用了迷殿?
但是,反過(guò)來(lái)想想咖杂,Google也沒(méi)有說(shuō)ViewModel支持這些做法庆寺,而且,我的一些想法明顯是在破壞ViewModel自身的設(shè)計(jì)诉字。難怪Google由最初的教大家自定義Factory傳遞參數(shù)到只字不提止邮。


結(jié)論

  • ViewModel生命周期?
    ViewModel本身沒(méi)有生命周期奏窑,裝載ViewModel的容器ViewModelStore有生命周期。
  • 我可不可以制作一個(gè)由我控制生命周期的ViewModel呢屈扎?
    可以埃唯,但是創(chuàng)建了之后將失去ViewModel的意義。
  • 我可不可以創(chuàng)建一個(gè)自定義參數(shù)的ViewModel呢鹰晨?
    可以墨叛,但是創(chuàng)建了之后將失去ViewModel的優(yōu)點(diǎn)。
  • ViewModel到底是什么模蜡?
    正確使用它是有生命的容器
    錯(cuò)誤使用它是數(shù)據(jù)容器漠趁。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忍疾,隨后出現(xiàn)的幾起案子闯传,更是在濱河造成了極大的恐慌,老刑警劉巖卤妒,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甥绿,死亡現(xiàn)場(chǎng)離奇詭異字币,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)共缕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)洗出,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人图谷,你說(shuō)我怎么就攤上這事翩活。” “怎么了便贵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵菠镇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嫉沽,道長(zhǎng)辟犀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任绸硕,我火速辦了婚禮堂竟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘玻佩。我一直安慰自己出嘹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布咬崔。 她就那樣靜靜地躺著税稼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垮斯。 梳的紋絲不亂的頭發(fā)上郎仆,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音兜蠕,去河邊找鬼扰肌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛熊杨,可吹牛的內(nèi)容都是我干的曙旭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼晶府,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼桂躏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起川陆,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剂习,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體进倍,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡土至,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猾昆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陶因。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖垂蜗,靈堂內(nèi)的尸體忽然破棺而出楷扬,到底是詐尸還是另有隱情,我是刑警寧澤贴见,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布烘苹,位于F島的核電站,受9級(jí)特大地震影響片部,放射性物質(zhì)發(fā)生泄漏镣衡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一档悠、第九天 我趴在偏房一處隱蔽的房頂上張望廊鸥。 院中可真熱鬧,春花似錦辖所、人聲如沸惰说。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吆视。三九已至,卻和暖如春酥宴,著一層夾襖步出監(jiān)牢的瞬間啦吧,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工拙寡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丰滑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓倒庵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親炫刷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子擎宝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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