Jetpack的MVVM實(shí)現(xiàn) - ViewModel的使用及源碼分析

概述

這篇我們繼續(xù)講 Jetpack顶伞,具體講實(shí)現(xiàn) MVVM架構(gòu)的 ViewModel掂摔。這次的例子會(huì)將 ViewModel和 LiveData結(jié)合使用荆几,然后分析一下 ViewModel的源碼。LiveData將留到下篇文章分析储藐。

上一篇文章MVP基礎(chǔ)架構(gòu)搭建 的例子里,用了MVP的架構(gòu)實(shí)現(xiàn)了網(wǎng)絡(luò)加載的流程嘶是。這次我們要把上次 MVP架構(gòu)升級(jí)為 這次的MVVM钙勃,將上次例子的 Presenter模塊和 Model模塊去掉,只保留網(wǎng)絡(luò)加載引擎聂喇。然后結(jié)合 ViewModel和 LiveData來(lái)實(shí)現(xiàn) MVVM辖源。

1、使用

先導(dǎo)入依賴:

    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

我們需要知道希太,ViewModel是一個(gè)抽象類克饶,需要我們?nèi)?shí)現(xiàn)。所以我們先創(chuàng)建一個(gè)實(shí)現(xiàn)類:

public class DataViewModel extends ViewModel {
    // LiveData
    private MutableLiveData<String> mLiveData;
    // 網(wǎng)絡(luò)加載引擎
    private RetrofitRequestInterface retrofit;
    public DataViewModel(){
        // 注釋 1誊辉, 初始化 LiveData
        mLiveData = new MutableLiveData();
        // 注釋 2矾湃, 初始化網(wǎng)絡(luò)引擎
        retrofit = RetrofitInstance.getRetrofitInstance().create(RetrofitRequestInterface.class);
    }
    public LiveData getLiveData(){
        return mLiveData;
    }
    public void getData(String url){
        // 注釋 3, 網(wǎng)絡(luò)請(qǐng)求
        Call<ResponseBody> call = retrofit.getRequest(url);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    String result = response.body().string();
                    // 注釋 4堕澄, 用 LiveData回調(diào)
                    mLiveData.postValue(result);
                } catch (IOException e) {
                    e.printStackTrace();
                    mLiveData.postValue(e.getMessage());
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                mLiveData.postValue(t.getMessage());
            }
        });
    }
}

上面我們創(chuàng)建了一個(gè) ViewModel實(shí)現(xiàn)類邀跃,這個(gè)類的作用與MVP的 Presenter有點(diǎn)類似。上面注釋 1和注釋 2分別創(chuàng)建 LiveData對(duì)象和網(wǎng)絡(luò)引擎蛙紫,在注釋 3的地方外界調(diào)用時(shí)執(zhí)行網(wǎng)絡(luò)加載拍屑,然后在注釋 4處用 LiveData回調(diào)加載結(jié)果。

下面看界面如何調(diào)用:

public class MainActivity extends AppCompatActivity {
    private Button getData;
    private DataViewModel mViewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getData = findViewById(R.id.id_get_data);
        init();
    }
    private void init(){
        // 注釋5坑傅, 工廠設(shè)計(jì)模式創(chuàng)建 ViewModel對(duì)象
        mViewModel = new ViewModelProvider(this).get(DataViewModel.class);
        // 以 LiveData對(duì)象設(shè)置數(shù)據(jù)回調(diào) 
        mViewModel.getLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Toast.makeText(getApplicationContext(), "mag = " + s, Toast.LENGTH_SHORT).show();
            }
        });
        getData.setOnClickListener((View view) -> {
            // 網(wǎng)絡(luò)請(qǐng)求
            mViewModel.getData("https://www.baidu.com");
        });
    }
}

上面步驟分別是創(chuàng)建了 ViewModel對(duì)象僵驰、設(shè)置數(shù)據(jù)回調(diào)以及執(zhí)行網(wǎng)絡(luò)請(qǐng)求。這就是 MVVM的基礎(chǔ)實(shí)現(xiàn)唁毒。Demo: ViewModel

現(xiàn)在我們先拋出兩個(gè)問(wèn)題:

1蒜茴、上面注釋 5處為什么使用 ViewModelProvider創(chuàng)建 ViewModel對(duì)象,而不直接 new一個(gè)枉证?
2矮男、傳說(shuō) ViewModel可以避免內(nèi)存泄漏,如何做到的室谚?

下面我們來(lái)分析一下 ViewModel的源碼毡鉴。

2崔泵、源碼分析

先看上面 ViewModelProvider的構(gòu)造方法和 get() 方法:

// ViewModelProvider.java
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
 // 注釋 6, 通過(guò)ViewModelStore 擁有者的 getDefaultViewModelProviderFactory方法獲取工廠對(duì)象
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : ViewModelProvider.NewInstanceFactory.getInstance());
    }
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull ViewModelProvider.Factory factory) {
        //注釋7猪瞬,  保存工廠 Factory和 緩存對(duì)象憎瘸,ViewModelStore
        mFactory = factory;
        mViewModelStore = store;
    }
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();,
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //注釋 9, 從緩存 mViewModelStore中取 ViewModel的對(duì)象
        // Key值是 “androidx.lifecycle.ViewModelProvider.DefaultKey” + 類名
        ViewModel viewModel = mViewModelStore.get(key);
        if (modelClass.isInstance(viewModel)) {
            ......
            // 是響應(yīng)類的對(duì)象則直接返回
            return (T) viewModel;
        }
        ......
        ......
        //注釋 10陈瘦, 緩存中沒(méi)有 ViewModel對(duì)象幌甘,用工廠創(chuàng)建,并存入緩存
        viewModel = (mFactory).create(modelClass);
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

上面注釋 6和注釋 7 處ViewModelProvider重載的構(gòu)造器里痊项,分別調(diào)用了Activity的 getViewModelStore()和 getDefaultViewModelProviderFactory()方法獲取或創(chuàng)建了一個(gè) ViewModel的緩存對(duì)象 ViewModelStore以及工廠對(duì)象 Factory锅风,并保存起來(lái)。其中緩存對(duì)象 ViewModelStore內(nèi)部是一個(gè) HashMap鞍泉,以鍵值對(duì)的形式緩存 ViewModel對(duì)象皱埠。

再看上面注釋 9和注釋 10處 get()的重載方法,ViewModelProvider創(chuàng)建 ViewModel對(duì)象前咖驮,首先從緩存 ViewModelStore中取边器,key值是“androidx.lifecycle.ViewModelProvider.DefaultKey” + 類名。當(dāng)緩存中沒(méi)有該對(duì)象時(shí)就用工廠 Factory創(chuàng)建托修,并存入緩存和返回忘巧。

Factory是一個(gè)抽象類,實(shí)現(xiàn)類對(duì)象是從上面注釋 6處通過(guò) Activity的 getDefaultViewModelProviderFactory()方法獲取的睦刃。我們來(lái)看看 ComponentActivity的這個(gè)方法如何創(chuàng)建工廠實(shí)現(xiàn)類的對(duì)象:

   // ComponentActivity.java
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        ......
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    //注釋 11砚嘴, 獲取了 Application對(duì)象,創(chuàng)建SavedStateViewModelFactory對(duì)象
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

    // SavedStateViewModelFactory.java
    public SavedStateViewModelFactory(@NonNull Application application,
                                      @NonNull SavedStateRegistryOwner owner,
                                      @Nullable Bundle defaultArgs) {
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        //注釋 12眯勾, 創(chuàng)建 AndroidViewModelFactory工廠
        mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        ......
       // 注釋 13枣宫,通過(guò) AndroidViewModelFactory創(chuàng)建 ViewModel對(duì)象
        return mFactory.create(modelClass);
        ......
    }

上面注釋 11處,ComponentActivity獲取了 Application對(duì)象吃环,創(chuàng)建了 Factory的子類 SavedStateViewModelFactory的對(duì)象也颤。然后注釋 12處SavedStateViewModelFactory又以 application作為參數(shù)創(chuàng)建了 Factory的另一實(shí)現(xiàn)類 AndroidViewModelFactory的對(duì)象,并用該對(duì)象創(chuàng)建 ViewModel對(duì)象郁轻。

下面重點(diǎn)看看 AndroidViewModelFactory這個(gè)工廠:

    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
        private static ViewModelProvider.AndroidViewModelFactory sInstance;
        @NonNull
        public static ViewModelProvider.AndroidViewModelFactory getInstance(@NonNull Application application) {
            // 注釋14翅娶, 單例模式創(chuàng)建工廠實(shí)例
            if (sInstance == null) {
                sInstance = new ViewModelProvider.AndroidViewModelFactory(application);
            }
            return sInstance;
        }
        private Application mApplication;
        // 傳入 Application對(duì)象
        public AndroidViewModelFactory(@NonNull Application application) {
            mApplication = application;
        }
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                try {
                    //注釋 15, 工廠方法設(shè)計(jì)模式創(chuàng)建 ViewModel對(duì)象好唯,持有 Application的引用
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                }
                ......
            }
            return super.create(modelClass);
        }
    }

上面 AndroidViewModelFactory 這個(gè)工廠竭沫,注釋14處用單例模式創(chuàng)建了該工廠對(duì)象,然后在注釋 15處用工廠方法設(shè)計(jì)模式創(chuàng)建 ViewModel對(duì)象骑篙。有一點(diǎn)值得注意的是蜕提,ViewModel創(chuàng)建時(shí)傳入的是 mApplication參數(shù)。也就是說(shuō) ViewModel對(duì)象最終并不持有 Activity的引用靶端,而是持有了 Application的引用谎势。

經(jīng)過(guò)上面的分析凛膏,我們現(xiàn)在應(yīng)該可以回答上面的兩個(gè)問(wèn)題了:

1、ViewModel對(duì)象使用了 ViewModelProvider獲取而不直接 new脏榆,是因?yàn)?ViewModelProvider有緩存機(jī)制猖毫,內(nèi)部用HashMap以鍵值對(duì)的形式緩存 ViewModel對(duì)象。當(dāng)緩存中沒(méi)有相應(yīng)對(duì)象時(shí)才用工廠去創(chuàng)建须喂。

2吁断、ViewModel對(duì)象并不持有 Activity的引用,而是持有了 Application的引用坞生。這是避免 Activity內(nèi)存泄漏的一種方式仔役。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恨胚,隨后出現(xiàn)的幾起案子骂因,更是在濱河造成了極大的恐慌,老刑警劉巖赃泡,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乘盼,居然都是意外死亡升熊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門绸栅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)级野,“玉大人,你說(shuō)我怎么就攤上這事粹胯”腿幔” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵风纠,是天一觀的道長(zhǎng)况鸣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)竹观,這世上最難降的妖魔是什么镐捧? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮臭增,結(jié)果婚禮上懂酱,老公的妹妹穿的比我還像新娘。我一直安慰自己誊抛,他們只是感情好列牺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著拗窃,像睡著了一般瞎领。 火紅的嫁衣襯著肌膚如雪泌辫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天默刚,我揣著相機(jī)與錄音甥郑,去河邊找鬼。 笑死荤西,一個(gè)胖子當(dāng)著我的面吹牛澜搅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邪锌,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼勉躺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了觅丰?” 一聲冷哼從身側(cè)響起饵溅,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妇萄,沒(méi)想到半個(gè)月后蜕企,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冠句,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年轻掩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懦底。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唇牧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出聚唐,到底是詐尸還是另有隱情丐重,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布杆查,位于F島的核電站扮惦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏根灯。R本人自食惡果不足惜径缅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烙肺。 院中可真熱鬧纳猪,春花似錦、人聲如沸桃笙。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鼠锈,卻和暖如春闪檬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背购笆。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工粗悯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人同欠。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓样傍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親铺遂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衫哥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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