“終于懂了“系列:Jetpack AAC完整解析(四)MVVM - Android架構(gòu)探索辫秧!

Jetpack AAC 系列文章:

“終于懂了“系列:Jetpack AAC完整解析(一)Lifecycle 完全掌握晦溪!

“終于懂了“系列:Jetpack AAC完整解析(二)LiveData 完全掌握揽趾!

“終于懂了“系列:Jetpack AAC完整解析(三)ViewModel 完全掌握呛踊!

“終于懂了“系列:Jetpack AAC完整解析(四)MVVM 架構(gòu)探索!

“終于懂了“系列:Jetpack AAC完整解析(五)DataBinding 重新認(rèn)知即纲!

前面三篇介紹了Jetpack 架構(gòu)組件中 最重要 的部分:生命周期組件-Lifecycle具帮、感知生命周期的數(shù)據(jù)組件-LiveData、視圖模型組件-ViewModel低斋。 這篇蜂厅,就來(lái)探索下目前android開發(fā)中 最優(yōu)秀、討論最多的架構(gòu)模式—— MVVM 膊畴。

幾個(gè)月前掘猿,我所在項(xiàng)目完成了 MVVM 的架構(gòu)改造。這篇在開始寫之前巴比,我也閱讀了大量MVVM文章术奖。所以礁遵,這篇盡量講清楚 開發(fā)架構(gòu)模式和MVVM的本質(zhì)轻绞,使得有一種 “哦,原來(lái)如此” 的豁然開朗佣耐。

注意政勃,本篇完全 不會(huì)提 DataBinding、雙向綁定兼砖,文末會(huì)解釋為啥不提奸远。

一、開發(fā)架構(gòu) 是什么讽挟?

我們先來(lái)理解開發(fā)架構(gòu)的本質(zhì)是什么懒叛,維基百科對(duì)軟件架構(gòu)的描述如下:

軟件架構(gòu)是一個(gè)系統(tǒng)的草圖。軟件架構(gòu)描述的對(duì)象是直接構(gòu)成系統(tǒng)的抽象組件耽梅。各個(gè)組件之間的連接則明確和相對(duì)細(xì)致地描述組件之間的通訊薛窥。在實(shí)現(xiàn)階段,這些抽象組件被細(xì)化為實(shí)際的組件,比如具體某個(gè)類或者對(duì)象诅迷。在面向?qū)ο箢I(lǐng)域中佩番,組件之間的連接通常用接口來(lái)實(shí)現(xiàn)。
拆分開來(lái)就是三條:

  1. 針對(duì)的是一個(gè)完整系統(tǒng)罢杉,此系統(tǒng)可以實(shí)現(xiàn)某種功能趟畏。
  2. 系統(tǒng)包含多個(gè)模塊,模塊間有一些關(guān)系和連接滩租。
  3. 架構(gòu)是實(shí)現(xiàn)此系統(tǒng)的實(shí)施描述:模塊責(zé)任赋秀、模塊間的連接。

為啥要做開發(fā)架構(gòu)設(shè)計(jì)呢持际?

  1. 模塊化責(zé)任具體化沃琅,使得每個(gè)模塊專注自己內(nèi)部
  2. 模塊間的關(guān)聯(lián)簡(jiǎn)單化蜘欲,減少耦合益眉。
  3. 易于使用、維護(hù)性好
  4. 提高開發(fā)效率

架構(gòu)模式最終都是 服務(wù)于開發(fā)者姥份。如果代碼職責(zé)和邏輯混亂郭脂,維護(hù)成本就會(huì)相應(yīng)地上升。

宏觀上來(lái)說(shuō)澈歉,開發(fā)架構(gòu)是一種思想展鸡,每個(gè)領(lǐng)域都有一些成熟的架構(gòu)模式,選擇適合自己項(xiàng)目即可埃难。

二莹弊、Android開發(fā)中的架構(gòu)

具體到Android開發(fā)中,開發(fā)架構(gòu)就是描述 視圖層涡尘、邏輯層忍弛、數(shù)據(jù)層 三者之間的關(guān)系和實(shí)施:

  • 視圖層:用戶界面,即界面的展示考抄、以及交互事件的響應(yīng)细疚。
  • 邏輯層:為了實(shí)現(xiàn)系統(tǒng)功能而進(jìn)行的必要邏輯。
  • 數(shù)據(jù)層:數(shù)據(jù)的獲取和存儲(chǔ)川梅,含本地疯兼、server。

正常的開發(fā)流程中贫途,開始寫代碼之前 都會(huì)有架構(gòu)設(shè)計(jì)這一過(guò)程吧彪。這就需要你選擇使用何種架構(gòu)模式了。

我的Android開發(fā)之路完整地經(jīng)過(guò)了 MVC丢早、MVP姨裸、MVVM,相信很多開發(fā)者和我一樣都是這樣一個(gè)過(guò)程,先來(lái)回顧下三者啦扬。

2.1 MVC

MVC中狂,Model-View-Controller,職責(zé)分類如下:

  • Model扑毡,模型層胃榕,即數(shù)據(jù)模型,用于獲取和存儲(chǔ)數(shù)據(jù)瞄摊。
  • View勋又,視圖層,即xml布局
  • Controller换帜,控制層楔壤,負(fù)責(zé)業(yè)務(wù)邏輯。

View層 接收到用戶操作事件惯驼,通知到 Controller 進(jìn)行對(duì)應(yīng)的邏輯處理蹲嚣,然后通知 Model去獲取/更新數(shù)據(jù),Model 再把新的數(shù)據(jù) 通知到 View 更新界面祟牲。這就是一個(gè)完整 MVC 的數(shù)據(jù)流向隙畜。

但在Android中,因?yàn)閤ml布局能力很弱说贝,View的很多操作是在Activity/Fragment中的议惰,而業(yè)務(wù)邏輯同樣也是寫在Activity/Fragment中

MVC

所以乡恕,MVC 的問(wèn)題點(diǎn) 如下:

  1. Activity/Fragment 責(zé)任不明言询,同時(shí)負(fù)責(zé)View、Controller傲宜,就會(huì)導(dǎo)致其中代碼量大运杭,不滿足單一職責(zé)。
  2. Model耦合View蛋哭,View 的修改會(huì)導(dǎo)致 Controller 和 Model 都進(jìn)行改動(dòng)县习,不滿足最少知識(shí)原則涮母。

2.2 MVP

MVP谆趾,Model-View-Presenter,職責(zé)分類如下:

  • Model叛本,模型層沪蓬,即數(shù)據(jù)模型,用于獲取和存儲(chǔ)數(shù)據(jù)来候。
  • View跷叉,視圖層,即Activity/Fragment
  • Presenter,控制層云挟,負(fù)責(zé)業(yè)務(wù)邏輯梆砸。

MVP解決了MVC的問(wèn)題:1.View責(zé)任明確,邏輯不再寫在Activity中园欣,而是在Presenter中帖世;2.Model不再持有View。

View層 接收到用戶操作事件沸枯,通知到Presenter日矫,Presenter進(jìn)行邏輯處理,然后通知Model更新數(shù)據(jù)绑榴,Model 把更新的數(shù)據(jù)給到Presenter哪轿,Presenter再通知到 View 更新界面。

MVP

MVP的實(shí)現(xiàn)思路:

  • UI邏輯抽象成IView接口翔怎,由具體的Activity實(shí)現(xiàn)類來(lái)完成窃诉。且調(diào)用Presenter進(jìn)行邏輯操作。
  • 業(yè)務(wù)邏輯抽象成IPresenter接口赤套,由具體的Presenter實(shí)現(xiàn)類來(lái)完成褐奴。邏輯操作完成后調(diào)用IView接口方法刷新UI。

MVP 本質(zhì)是面向接口編程于毙,實(shí)現(xiàn)了依賴倒置原則敦冬。MVP解決了View層責(zé)任不明的問(wèn)題,但并沒(méi)有解決代碼耦合的問(wèn)題唯沮,View和Presenter之間相互持有脖旱。

所以 MVP 有問(wèn)題點(diǎn) 如下:

  1. 會(huì)引入大量的IView、IPresenter接口介蛉,增加實(shí)現(xiàn)的復(fù)雜度萌庆。
  2. View和Presenter相互持有,形成耦合币旧。

2.3 MVVM

MVVM践险,Model-View-ViewModel,職責(zé)分類如下:

  • Model吹菱,模型層巍虫,即數(shù)據(jù)模型,用于獲取和存儲(chǔ)數(shù)據(jù)鳍刷。
  • View占遥,視圖,即Activity/Fragment
  • ViewModel输瓜,視圖模型瓦胎,負(fù)責(zé)業(yè)務(wù)邏輯芬萍。

注意,MVVM這里的ViewModel就是一個(gè)名稱搔啊,可以理解為MVP中的Presenter柬祠。不等同于上一篇中的 ViewModel組件 ,Jetpack ViewModel組件是 對(duì) MVVM的ViewModel 的具體實(shí)施方案负芋。

MVVM 的本質(zhì)是 數(shù)據(jù)驅(qū)動(dòng)瓶盛,把解耦做的更徹底,viewModel不持有view 示罗。

View 產(chǎn)生事件惩猫,使用 ViewModel進(jìn)行邏輯處理后,通知Model更新數(shù)據(jù)蚜点,Model把更新的數(shù)據(jù)給ViewModel轧房,ViewModel自動(dòng)通知View更新界面竿秆,而不是主動(dòng)調(diào)用View的方法胰坟。

MVVM

MVVM在Android開發(fā)中是如何實(shí)現(xiàn)的呢?接著看~

到這里你會(huì)發(fā)現(xiàn)诈闺,所謂的架構(gòu)模式本質(zhì)上理解很簡(jiǎn)單陪拘。比如MVP厂镇,甚至你都可以忽略這個(gè)名字,理解成 在更高的層面上 面向接口編程左刽,實(shí)現(xiàn)了 依賴倒置 原則捺信,就是這么簡(jiǎn)單。

三欠痴、MVVM 的實(shí)現(xiàn) - Jetpack MVVM

前面提到迄靠,架構(gòu)模式選擇適合自己項(xiàng)目的即可。話雖如此喇辽,但Google官方推薦的架構(gòu)模式 是適合大多數(shù)情況掌挚,是非常值得我們學(xué)習(xí)和實(shí)踐的。

好了菩咨,下面我們就來(lái)詳細(xì)介紹 Jetpack MVVM 架構(gòu)吠式。

3.1 Jetpack MVVM 理解

Jetpack MVVM 是 MVVM 模式在 Android 開發(fā)中的一個(gè)具體實(shí)現(xiàn),是 Android中 Google 官方提供并推薦的 MVVM實(shí)現(xiàn)方式抽米。

不僅通過(guò)數(shù)據(jù)驅(qū)動(dòng)完成徹底解耦特占,還兼顧了 Android 頁(yè)面開發(fā)中其他不可預(yù)期的錯(cuò)誤,例如Lifecycle 能在妥善處理 頁(yè)面生命周期 避免view空指針問(wèn)題缨硝,ViewModel使得UI發(fā)生重建時(shí) 無(wú)需重新向后臺(tái)請(qǐng)求數(shù)據(jù)摩钙,節(jié)省了開銷罢低,讓視圖重建時(shí)更快展示數(shù)據(jù)查辩。

首先胖笛,請(qǐng)查看下圖,該圖顯示了所有模塊應(yīng)如何彼此交互:

Jetpack MVVM標(biāo)準(zhǔn)推薦架構(gòu)

各模塊對(duì)應(yīng)MVVM架構(gòu):

  • View層:Activity/Fragment
  • ViewModel層:Jetpack ViewModel + Jetpack LivaData
  • Model層:Repository倉(cāng)庫(kù)宜岛,包含 本地持久性數(shù)據(jù) 和 服務(wù)端數(shù)據(jù)

View層 包含了我們平時(shí)寫的Activity/Fragment/布局文件等與界面相關(guān)的東西长踊。

ViewModel層 用于持有和UI元素相關(guān)的數(shù)據(jù),以保證這些數(shù)據(jù)在屏幕旋轉(zhuǎn)時(shí)不會(huì)丟失萍倡,并且還要提供接口給View層調(diào)用以及和倉(cāng)庫(kù)層進(jìn)行通信身弊。

倉(cāng)庫(kù)層 要做的主要工作是判斷調(diào)用方請(qǐng)求的數(shù)據(jù)應(yīng)該是從本地?cái)?shù)據(jù)源中獲取還是從網(wǎng)絡(luò)數(shù)據(jù)源中獲取,并將獲取到的數(shù)據(jù)返回給調(diào)用方列敲。本地?cái)?shù)據(jù)源可以使用數(shù)據(jù)庫(kù)阱佛、SharedPreferences等持久化技術(shù)來(lái)實(shí)現(xiàn),而網(wǎng)絡(luò)數(shù)據(jù)源則通常使用Retrofit訪問(wèn)服務(wù)器提供的Webservice接口來(lái)實(shí)現(xiàn)戴而。

另外凑术,圖中所有的箭頭都是單向的,例如View層指向了ViewModel層所意,表示View層會(huì)持有ViewModel層的引用淮逊,但是反過(guò)來(lái)ViewModel層卻不能持有View層的引用。除此之外扶踊,引用也不能跨層持有泄鹏,比如View層不能持有倉(cāng)庫(kù)層的引用,謹(jǐn)記每一層的組件都只能與它相鄰層的組件進(jìn)行交互秧耗。

這種設(shè)計(jì)打造了一致且愉快的用戶體驗(yàn)备籽。無(wú)論用戶上次使用應(yīng)用是在幾分鐘前還是幾天之前,現(xiàn)在回到應(yīng)用時(shí)都會(huì)立即看到應(yīng)用在本地保留的數(shù)據(jù)分井。如果此數(shù)據(jù)已過(guò)期胶台,則應(yīng)用的Repository將開始在后臺(tái)更新數(shù)據(jù)。

3.2 實(shí)施

我們來(lái)舉個(gè)完整的例子 - 在頁(yè)面中顯示用戶信息列表杂抽,來(lái)說(shuō)明 Jetpack MVVM 的具體實(shí)施诈唬。

3.2.1 構(gòu)建界面

首先創(chuàng)建一個(gè)列表頁(yè)面 UserListActivity,并且知道頁(yè)面所需要的數(shù)據(jù)是缩麸,用戶信息列表铸磅。

那么 用戶信息列表 如何獲取呢?根據(jù)上面的架構(gòu)圖杭朱,就是ViewModel了阅仔,所以我們創(chuàng)建 UserListViewModel 繼承自 ViewModel,并且把 用戶信息列表 以 LiveData呈現(xiàn)弧械。

public class UserListViewModel extends ViewModel {
    //用戶信息
    private MutableLiveData<List<User>> userListLiveData;
    //進(jìn)條度的顯示
    private MutableLiveData<Boolean> loadingLiveData;

    public UserListViewModel() {
        userListLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }
    
    public LiveData<List<User>> getUserListLiveData() {
        return userListLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
    ...
}

LiveData 是一種可觀察的數(shù)據(jù)存儲(chǔ)器八酒。應(yīng)用中的其他組件可以使用此存儲(chǔ)器監(jiān)控對(duì)象的更改,而無(wú)需在它們之間創(chuàng)建明確且嚴(yán)格的依賴路徑刃唐。LiveData 組件還遵循應(yīng)用組件(如 Activity羞迷、Fragment 和 Service)的生命周期狀態(tài)界轩,并包括清理邏輯以防止對(duì)象泄漏和過(guò)多的內(nèi)存消耗。

將 UserListViewModel 中的字段類型更改為 MutableLiveData<List<User>>∠挝停現(xiàn)在浊猾,更新數(shù)據(jù)時(shí),系統(tǒng)會(huì)通知 UserListActivity热鞍。此外葫慎,由于此 LiveData 字段具有生命周期感知能力,因此當(dāng)不再需要引用時(shí)薇宠,會(huì)自動(dòng)清理它們偷办。

另外,注意到暴露的獲取LiveData的方法 返回的是LiveData類型澄港,即不可變的爽篷,而不是MutableLiveData,好處是避免數(shù)據(jù)在外部被更改慢睡。(參見(jiàn)LivaData篇文章)

現(xiàn)在逐工,我們修改 UserListActivity 以觀察數(shù)據(jù)并更新界面:

//UserListActivity.java
...
    //觀察ViewModel的數(shù)據(jù),且此數(shù)據(jù) 是 View 直接需要的漂辐,不需要再做邏輯處理
    private void observeLivaData() {
        mUserListViewModel.getUserListLiveData().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                if (users == null) {
                    Toast.makeText(UserListActivity.this, "獲取user失斃岷啊!", Toast.LENGTH_SHORT).show();
                    return;
                }
                //刷新列表
                mUserAdapter.setNewInstance(users);
            }
        });

        mUserListViewModel.getLoadingLiveData().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
                //顯示/隱藏加載進(jìn)度條
                mProgressBar.setVisibility(aBoolean? View.VISIBLE:View.GONE);
            }
        });
    }

每次更新用戶列表信息數(shù)據(jù)時(shí)髓涯,系統(tǒng)都會(huì)調(diào)用 onChanged() 回調(diào)并刷新界面袒啼,而不需要 ViewModel主動(dòng)調(diào)用View層方法刷新,這就是 數(shù)據(jù)驅(qū)動(dòng) 了 —— 數(shù)據(jù)的更改 驅(qū)動(dòng) View 自動(dòng)刷新纬纪。

因?yàn)長(zhǎng)iveData具有生命周期感知能力蚓再,這意味著,除非 Activity 處于活躍狀態(tài)包各,否則它不會(huì)調(diào)用 onChanged() 回調(diào)摘仅。當(dāng)調(diào)用 Activity 的 onDestroy() 方法時(shí),LiveData 還會(huì)自動(dòng)移除觀察者问畅。

另外娃属,我們也沒(méi)有添加任何邏輯來(lái)處理配置更改(例如,用戶旋轉(zhuǎn)設(shè)備的屏幕)护姆。UserListViewModel 會(huì)在配置更改后自動(dòng)恢復(fù)矾端,所以一旦創(chuàng)建新的 Activity,它就會(huì)接收相同的 ViewModel 實(shí)例卵皂,并且會(huì)立即使用當(dāng)前的數(shù)據(jù)調(diào)用回調(diào)秩铆。鑒于 ViewModel 對(duì)象應(yīng)該比它們更新的相應(yīng) View 對(duì)象存在的時(shí)間更長(zhǎng),因此 ViewModel 實(shí)現(xiàn)中不得包含對(duì) View 對(duì)象的直接引用灯变,包括Context殴玛。

3.2.2 獲取數(shù)據(jù)

現(xiàn)在捅膘,我們已使用 LiveData 將 UserListViewModel 連接到UserListActivity,那么如何獲取用戶個(gè)人信息列表數(shù)據(jù)呢族阅?

實(shí)現(xiàn) ViewModel 的第一個(gè)想法可能是 使用Retrofit/Okhttp調(diào)用接口 來(lái)獲取數(shù)據(jù)篓跛,然后將該數(shù)據(jù)設(shè)置給 LiveData 對(duì)象膝捞。這種設(shè)計(jì)行得通坦刀,但如果采用這種設(shè)計(jì),隨著應(yīng)用的擴(kuò)大蔬咬,應(yīng)用會(huì)變得越來(lái)越難以維護(hù)鲤遥。這樣會(huì)使 UserListViewModel 類承擔(dān)太多的責(zé)任,這就違背了單一職責(zé)原則林艘。

ViewModel 會(huì)將數(shù)據(jù)獲取過(guò)程委派給一個(gè)新的模塊盖奈,即Repository

Repository模塊會(huì)處理數(shù)據(jù)操作狐援。它們會(huì)提供一個(gè)干凈的 API钢坦,以便應(yīng)用內(nèi)其余部分也可以輕松獲取該數(shù)據(jù)。數(shù)據(jù)更新時(shí)啥酱,它們知道從何處獲取數(shù)據(jù)以及進(jìn)行哪些 API 調(diào)用爹凹。您可以將Repository視為不同數(shù)據(jù)源(如持久性模型、網(wǎng)絡(luò)服務(wù)和緩存)之間的媒介镶殷。

public class UserRepository {

    private static UserRepository mUserRepository;
    public static UserRepository getUserRepository(){
        if (mUserRepository == null) {
            mUserRepository = new UserRepository();
        }
        return mUserRepository;
    }

    //(假裝)從服務(wù)端獲取
    public void getUsersFromServer(Callback<List<User>> callback){
        new AsyncTask<Void, Void, List<User>>() {
            @Override
            protected void onPostExecute(List<User> users) {
                callback.onSuccess(users);
                //存本地?cái)?shù)據(jù)庫(kù)
                saveUsersToLocal(users);
            }
            @Override
            protected List<User> doInBackground(Void... voids) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //假裝從服務(wù)端獲取的
                List<User> users = new ArrayList<>();
                for (int i = 0; i < 20; i++) {
                    User user = new User("user"+i, i);
                    users.add(user);
                }
                return users;
            }
        }.execute();
    }
    

雖然Repository模塊看起來(lái)不必要禾酱,但它起著一項(xiàng)重要的作用:它會(huì)從應(yīng)用的其余部分中提取數(shù)據(jù)源。現(xiàn)在绘趋,UserListViewModel 是不知道數(shù)據(jù)來(lái)源的颤陶,因此我們可以為ViewModel提供從幾個(gè)不同的數(shù)據(jù)源獲取數(shù)據(jù)。

3.2.3 連接 ViewModel 與存儲(chǔ)區(qū)

我們?cè)赨serListViewModel 提供一個(gè)方法陷遮,用戶Activity獲取用戶信息滓走。此方法就是調(diào)用Repository來(lái)執(zhí)行,并且吧數(shù)據(jù)設(shè)置到LiveData帽馋。

public class UserListViewModel extends ViewModel {
    //用戶信息
    private MutableLiveData<List<User>> userListLiveData;
    //進(jìn)條度的顯示
    private MutableLiveData<Boolean> loadingLiveData;

    public UserListViewModel() {
        userListLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }
    
    /**
     * 獲取用戶列表信息
     * 假裝網(wǎng)絡(luò)請(qǐng)求 2s后 返回用戶信息
     */
    public void getUserInfo() {

        loadingLiveData.setValue(true);

        UserRepository.getUserRepository().getUsersFromServer(new Callback<List<User>>() {
            @Override
            public void onSuccess(List<User> users) {
                loadingLiveData.setValue(false);
                userListLiveData.setValue(users);
            }

            @Override
            public void onFailed(String msg) {
                loadingLiveData.setValue(false);
                userListLiveData.setValue(null);
            }
        });
    }

    //返回LiveData類型
    public LiveData<List<User>> getUserListLiveData() {
        return userListLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
}

在Activity中闲坎,就要獲取UserListViewModel實(shí)例,獲取用戶信息:

//UserListActivity.java
public class UserListActivity extends AppCompatActivity {
    private UserListViewModel mUserListViewModel;
    private ProgressBar mProgressBar;
    private RecyclerView mRvUserList;
    private UserAdapter mUserAdapter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        initView();
        initViewModel();
        getData();
        observeLivaData();
    }
    private void initView() {...}

    private void initViewModel() {
        ViewModelProvider viewModelProvider = new ViewModelProvider(this);
        mUserListViewModel = viewModelProvider.get(UserListViewModel.class);
    }

    /**
     * 獲取數(shù)據(jù)茬斧,調(diào)用ViewModel的方法獲取
     */
    private void getData() {
        mUserListViewModel.getUserInfo();
    }
    
    private void observeLivaData() {...}

3.2.4 緩存數(shù)據(jù)

現(xiàn)在UserRepository 有個(gè)問(wèn)題是腰懂,它從后端獲取數(shù)據(jù)后,不會(huì)將緩存該數(shù)據(jù)项秉。因此绣溜,如果用戶在離開頁(yè)面后再返回,則應(yīng)用必須重新獲取數(shù)據(jù)娄蔼,即使數(shù)據(jù)未發(fā)生更改也是如此怖喻。這就浪費(fèi)了寶貴的網(wǎng)絡(luò)資源底哗,迫使用戶等待新的查詢完成。
所以锚沸,我們向 UserRepository 添加了一個(gè)新的數(shù)據(jù)源跋选,本地緩存。緩存實(shí)現(xiàn) 可以是 數(shù)據(jù)庫(kù)哗蜈、SharedPreferences等持久化技術(shù)前标。(具體實(shí)現(xiàn)就不再寫了)

//UserRepository.java

    //從本地?cái)?shù)據(jù)庫(kù)獲取
    public void getUsersFromLocal(){
        // TODO: 2021/1/24 從本地?cái)?shù)據(jù)庫(kù)獲取
    }

    //存入本地?cái)?shù)據(jù)庫(kù) (從服務(wù)端獲取數(shù)據(jù)后可以調(diào)用)
    private void saveUsersToLocal(List<User> users){
        // TODO: 2021/1/24 存入本地?cái)?shù)據(jù)庫(kù)
    }

到這里,Jetpack MVVM 就介紹完了距潘。

實(shí)際上只要前面介紹的 Lifecycle炼列、LivaData、ViewModel 熟練掌握的話音比,這里是十分好理解的俭尖。

3.3 注意點(diǎn)

  1. 在應(yīng)用的各個(gè)模塊之間設(shè)定明確定義的職責(zé)界限

  2. ViewModel 不能持有 View層引用洞翩,包括Context也不能持有稽犁。

  3. 將一個(gè)數(shù)據(jù)源指定為單一可信來(lái)源。 每當(dāng)需要訪問(wèn)數(shù)據(jù)時(shí)骚亿,都應(yīng)一律源于此單一可信來(lái)源已亥。 例如 UserRepository會(huì)將網(wǎng)絡(luò)服務(wù)響應(yīng)保存在數(shù)據(jù)庫(kù)中。這樣一來(lái)循未,對(duì)數(shù)據(jù)庫(kù)的更改將觸發(fā)對(duì)活躍 LiveData 對(duì)象的回調(diào)陷猫。數(shù)據(jù)庫(kù)會(huì)充當(dāng)單一可信來(lái)源

  4. 保留盡可能多的相關(guān)數(shù)據(jù)和最新數(shù)據(jù)的妖。 這樣绣檬,即使用戶的設(shè)備處于離線模式,他們也可以使用您應(yīng)用的功能嫂粟。請(qǐng)注意娇未,并非所有用戶都能享受到穩(wěn)定的高速連接。

  5. 顯示頁(yè)面狀態(tài)星虹。 例如例子中的加載進(jìn)度條零抬,就是觀察 ViewModel中的MutableLiveData<Boolean> loadingLiveData 進(jìn)行操作的。

3.4 MVP改造MVVM

了解了Jetpack MVVM的實(shí)現(xiàn)宽涌,再來(lái)改造 MVP 是很簡(jiǎn)單的了平夜。

步驟如下:

  1. 去除Presener 對(duì)View、context的引用卸亮。
  2. Presener 替換成ViewModel的實(shí)現(xiàn)忽妒,獲取的數(shù)據(jù)以 LivaData呈現(xiàn)
  3. 刪除定義的IView等接口,Activity/Fragment中 獲取ViewModel實(shí)例段直,調(diào)用其方法獲取數(shù)據(jù)吃溅。
  4. Activity/Fragment 觀察需要的 LivaData 然后刷新UI

這樣就已經(jīng)成為了MVVM鸯檬。當(dāng)然也要檢查下 原MVP的 Model層的實(shí)現(xiàn)决侈,是否滿足上面的要求。

四喧务、總結(jié)

本篇介紹了 架構(gòu)模式的含義赖歌,回顧和比較了Android中的架構(gòu)模式MVC、MVP蹂楣、MVVM俏站,最好在 Jetpack架構(gòu)組件 基礎(chǔ)上 介紹了 MVVM 的詳細(xì)實(shí)現(xiàn)方法讯蒲、注意點(diǎn)痊土,以及MVP的改造。

整篇下來(lái)墨林,基本很簡(jiǎn)單容易理解的赁酝。 例子是很簡(jiǎn)單的,所以在實(shí)際開發(fā)中 需要深入理解 MVVM 數(shù)據(jù)驅(qū)動(dòng)的本質(zhì)旭等,和MVP的區(qū)別酌呆。

有人可能會(huì)有疑惑:怎么完全沒(méi)有提 DataBinding、雙向綁定搔耕?

實(shí)際上隙袁,這也是我之前的疑惑。 沒(méi)有提 是因?yàn)椋?/p>

  1. 我不想讓讀者 一提到 MVVM 就和DataBinding聯(lián)系起來(lái)
  2. 我想讓讀者 抓住 MVVM 數(shù)據(jù)驅(qū)動(dòng) 的本質(zhì)弃榨。
  3. 而DataBinding提供的雙向綁定菩收,是用來(lái)完善Jetpack MVVM 的工具,其本身在業(yè)界又非常具有爭(zhēng)議性鲸睛。
  4. 掌握本篇內(nèi)容娜饵,已經(jīng)是Google推薦的開發(fā)架構(gòu),就已經(jīng)實(shí)現(xiàn) MVVM 模式官辈。在Google官方的 應(yīng)用架構(gòu)指南 中 也同樣絲毫沒(méi)有提到 DataBinding箱舞。

所以,下一篇拳亿,將繼續(xù)介紹 Jetpack AAC 的組件:數(shù)據(jù)綁定組件 DataBinding晴股、數(shù)據(jù)庫(kù)組件 Room,作為 Jetpack MVVM 的完善補(bǔ)充點(diǎn)肺魁。 并且 也將是 Jetpack AAC 完整解析 系列的最后一篇电湘。 敬請(qǐng)期待!

Demo 地址

.

感謝與參考:

ViewModel官方文檔

是讓人耳目一新的 Jetpack MVVM 精講啊胡桨!

Android 開發(fā)中的架構(gòu)模式 -- MVC / MVP / MVVM

.

你的 點(diǎn)贊官帘、評(píng)論,是對(duì)我的巨大鼓勵(lì)昧谊!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刽虹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呢诬,更是在濱河造成了極大的恐慌涌哲,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尚镰,死亡現(xiàn)場(chǎng)離奇詭異阀圾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)狗唉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門初烘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人分俯,你說(shuō)我怎么就攤上這事肾筐。” “怎么了缸剪?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵吗铐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杏节,道長(zhǎng)唬渗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任奋渔,我火速辦了婚禮镊逝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卒稳。我一直安慰自己蹋半,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布充坑。 她就那樣靜靜地躺著减江,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捻爷。 梳的紋絲不亂的頭發(fā)上辈灼,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音也榄,去河邊找鬼巡莹。 笑死司志,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的降宅。 我是一名探鬼主播骂远,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腰根!你這毒婦竟也來(lái)了激才?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤额嘿,失蹤者是張志新(化名)和其女友劉穎瘸恼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體册养,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡东帅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了球拦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠闭。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖刘莹,靈堂內(nèi)的尸體忽然破棺而出阎毅,到底是詐尸還是另有隱情焚刚,我是刑警寧澤点弯,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站矿咕,受9級(jí)特大地震影響抢肛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碳柱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一捡絮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莲镣,春花似錦福稳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至半火,卻和暖如春越妈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钮糖。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工梅掠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓阎抒,卻偏偏與公主長(zhǎng)得像酪我,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子且叁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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