Android應(yīng)用結(jié)構(gòu)之ViewModel

??ViewModel類是用來存儲(chǔ)和管理與UI相關(guān)的數(shù)據(jù)涎显,在設(shè)計(jì)之初就考慮到生命周期的影響徽惋。ViewModel允許數(shù)據(jù)在屏幕旋轉(zhuǎn)等配置變化后存活回怜。

Note: 如何在Android項(xiàng)目中引入 ViewModel , 請(qǐng)參閱 adding components to your project.

??Android framework管理UI控制器(如Activity和Fragment)的生命周期距辆。 framework可能會(huì)決定銷毀或重新創(chuàng)建UI控制器翘紊,以響應(yīng)完全不受控制的某些用戶操作或設(shè)備事件蔽氨。

??如果系統(tǒng)銷毀或重新創(chuàng)建UI控制器,則存儲(chǔ)在其中的所有臨時(shí)的UI相關(guān)數(shù)據(jù)都將丟失帆疟。 舉個(gè)例子孵滞,您的應(yīng)用中的一個(gè)Activity可能包含用戶列表。 當(dāng)因配置更改重新創(chuàng)建Activity時(shí)鸯匹,新Activity必須重新獲取用戶列表。 對(duì)于簡(jiǎn)單數(shù)據(jù)泄伪,活動(dòng)可以使用onSaveInstanceState()方法并從onCreate()中的數(shù)據(jù)包中恢復(fù)其數(shù)據(jù)殴蓬,但是此方法僅適用于可以序列化然后反序列化的少量數(shù)據(jù),而不適用于潛在的大量數(shù)據(jù),例如用戶列表或位圖。

??另一個(gè)問題是UI控制器經(jīng)常需要異步請(qǐng)求染厅,需要一些時(shí)間才能獲取結(jié)果痘绎。 UI控制器需要管理這些請(qǐng)求,確保在系統(tǒng)銷毀自己時(shí)肖粮,清理這些請(qǐng)求以避免潛在的內(nèi)存泄漏孤页。 這種管理需要大量的維護(hù)代碼,并且在因配置更改而重新創(chuàng)建UI控制器的時(shí)涩馆,可能不得不重新發(fā)出已經(jīng)發(fā)出過的請(qǐng)求行施,這樣會(huì)浪費(fèi)許多資源。

??UI控制器(如Activity和Fragment)主要用于顯示UI數(shù)據(jù)魂那,對(duì)用戶操作做出響應(yīng)蛾号,或處理與操作系統(tǒng)之間的通信(如權(quán)限請(qǐng)求)。 再讓UI控制器負(fù)責(zé)從數(shù)據(jù)庫(kù)或網(wǎng)絡(luò)加載數(shù)據(jù)涯雅,會(huì)導(dǎo)致該類過度臃腫鲜结。 給UI控制器分配過多的工作,可能會(huì)導(dǎo)致一個(gè)類去單獨(dú)處理應(yīng)用程序的所有工作活逆,而不是將工作委托給其他類精刷。 給UI控制器分配過多的工作也使得測(cè)試工作變得更加困難。

??將視圖數(shù)據(jù)的所有權(quán)從UI控制器邏輯中分離出來蔗候,會(huì)讓項(xiàng)目更簡(jiǎn)單怒允,更高效。

實(shí)現(xiàn)ViewModel

??Architecture Components為UI控制器提供了ViewModel助手類琴庵,以便給UI準(zhǔn)備數(shù)據(jù)误算。ViewModel對(duì)象在配置更改期間會(huì)自動(dòng)保留,以便它們保存的數(shù)據(jù)可以立即提供給新的Activity或Fragment實(shí)例迷殿。 例如儿礼,您如果需要在應(yīng)用程序中顯示用戶列表,請(qǐng)確保將獲取用戶列表的工作讓ViewModel去做庆寺,而不是Activity或Fragment蚊夫,如以下示例代碼所示:

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() {
        // Do an asyncronous operation to fetch users.
    }
}

然后,您可以在Activity中按如下方式訪問用戶列表:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

??如果重新創(chuàng)建Activity懦尝,它將接收由第一個(gè)Activity創(chuàng)建的那個(gè)MyViewModel實(shí)例知纷。 當(dāng)持有ViewModel的Activity被FINISHED之后,framework將調(diào)用ViewModel對(duì)象的onCleared()方法陵霉,以便清理資源琅轧。

Note:ViewModel絕不能持有任何View,生命周期或?qū)ontext有引用的類踊挠。

??ViewModel的壽命和View乍桂、LifecycleOwners實(shí)例無關(guān)。 這樣做也方便了您編寫覆蓋ViewModel的測(cè)試,因?yàn)樗挥萌タ紤]生命周期睹酌。 ViewModel對(duì)象可以包含LifecycleObservers权谁,例如LiveData對(duì)象。 但是憋沿,ViewModel對(duì)象絕不能去觀察有生命周期感知能力的Observer(如LiveData對(duì)象)旺芽。 如果ViewModel需要Context,例如獲得一個(gè)系統(tǒng)服務(wù)辐啄,那么它可以擴(kuò)展AndroidViewModel類采章,并在構(gòu)造函數(shù)中有一個(gè)接收Application的構(gòu)造函數(shù),而Application是Context的子類则披。

ViewModel的生命周期

??ViewModel生命長(zhǎng)度是在獲取ViewModel時(shí)傳遞給ViewModelProvider的對(duì)象的生命周期決定的共缕。 ViewModel保留在內(nèi)存中,直到說依賴的有生命周期的對(duì)象永久消失:在Activity的情況下士复,當(dāng)它被FINISHED图谷,而在Fragment的情況下,當(dāng)它被DETACHED阱洪。

??圖1展示了一個(gè)活動(dòng)在經(jīng)歷一個(gè)循環(huán)后的各種生命周期狀態(tài)便贵,然后結(jié)束。 該圖還顯示了相關(guān)活動(dòng)生命周期旁邊的ViewModel的生命周期冗荸。 這個(gè)特定的圖表說明了一個(gè)活動(dòng)的狀態(tài)承璃。 相同的基本狀態(tài)適用于片段的生命周期。

圖1

??您通常在系統(tǒng)第一次調(diào)用Activity對(duì)象的onCreate()方法時(shí)初始化ViewModel蚌本。 系統(tǒng)可能會(huì)在Activity的整個(gè)生命周期內(nèi)多次調(diào)用onCreate()盔粹,例如當(dāng)設(shè)備屏幕旋轉(zhuǎn)時(shí)。 ViewModel的生命周期從第一次請(qǐng)求ViewModel開始程癌,直到Activity被FINISHED并銷毀舷嗡。

在Fragment之間共享數(shù)據(jù)

??Activity中Fragment們需要相互通信是很常見的事情。 想象一下嵌莉,常見的Master-Detail模式下进萄,用戶在Master Fragment的列表中選擇一個(gè)項(xiàng)目,另一個(gè)Fragment顯示所選項(xiàng)目的內(nèi)容锐峭。 這實(shí)現(xiàn)起來并不太容易中鼠,因?yàn)檫@兩個(gè)Fragment都需要定義一些接口,而它們的Activity必須將兩者聯(lián)系在一起沿癞。 此外援雇,F(xiàn)ragment必須處理另一個(gè)Fragment尚未創(chuàng)建或可見的情況。

??ViewModel可以解決這個(gè)常見的痛點(diǎn)椎扬。 Fragment可以通過一個(gè)在Activity范圍內(nèi)共享的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 onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

??請(qǐng)注意曙旭,在獲取ViewModel時(shí),兩個(gè)Fragment都要使用getActivity()晶府。這樣,這兩個(gè)Fragment會(huì)收到同一個(gè)SharedViewModel實(shí)例钻趋,該實(shí)例的作用域?yàn)锳ctivity川陆。

這樣做有以下好處:

  • 這個(gè)Activity不需要做任何事情,也不需要知道Fragment之間的交流蛮位。
  • 除了SharedViewModel的接口之外较沪,F(xiàn)ragment不需要了解彼此。 如果其中一個(gè)Fragment消失失仁,另一個(gè)Fragment繼續(xù)照常工作尸曼。
  • 每個(gè)Fragment都有自己的生命周期,不受其他生命周期的影響萄焦。 一個(gè)Fragment替換成另一個(gè)Fragment控轿,UI繼續(xù)工作也沒有任何問題。

用ViewModel替換Loader

??像CursorLoader這樣的Loader類經(jīng)常被用來保持應(yīng)用程序中UI數(shù)據(jù)與數(shù)據(jù)庫(kù)同步拂封。 您可以使用ViewModel和其他幾個(gè)類來替換Loader茬射。 使用ViewModel將UI控制器與數(shù)據(jù)加載操作分開,這意味著您在類之間的強(qiáng)引用減少了冒签。

??Loader常見的用處是觀察數(shù)據(jù)庫(kù)的內(nèi)容在抛。 當(dāng)數(shù)據(jù)庫(kù)中的值發(fā)生更改時(shí),Loader會(huì)自動(dòng)觸發(fā)重新加載數(shù)據(jù)并更新UI:

圖2

??配合使用ViewModel萧恕、Room和LiveData來替換Loader刚梭。 ViewModel確保數(shù)據(jù)在設(shè)備配置更改后仍然存在。 當(dāng)數(shù)據(jù)庫(kù)發(fā)生變化時(shí)票唆,Room會(huì)通知你的LiveData朴读,而LiveData則用修改后的數(shù)據(jù)更新你的UI。

圖3

??這篇博客介紹了如何使用帶有LiveData的ViewModel來替換AsyncTaskLoader惰说。

??隨著你的數(shù)據(jù)變得越來越復(fù)雜磨德,你可能會(huì)選擇一個(gè)單獨(dú)的類來加載數(shù)據(jù)。 ViewModel的目的是封裝UI控制器的數(shù)據(jù)吆视,以使數(shù)據(jù)在配置更改后不受影響典挑。 有關(guān)如何在配置更改時(shí)加載,保持和管理數(shù)據(jù)的更多信息啦吧,請(qǐng)參閱Saving UI State您觉。

??Guide to Android App Architecture 建議構(gòu)建一個(gè)存儲(chǔ)庫(kù)類來處理這些功能。

最后編輯于
?著作權(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)離奇詭異,居然都是意外死亡私沮,警方通過查閱死者的電腦和手機(jī)始赎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仔燕,“玉大人造垛,你說我怎么就攤上這事∥螅” “怎么了五辽?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)外恕。 經(jīng)常有香客問我杆逗,道長(zhǎng),這世上最難降的妖魔是什么吁讨? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任髓迎,我火速辦了婚禮,結(jié)果婚禮上建丧,老公的妹妹穿的比我還像新娘排龄。我一直安慰自己,他們只是感情好翎朱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布橄维。 她就那樣靜靜地躺著,像睡著了一般拴曲。 火紅的嫁衣襯著肌膚如雪争舞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天澈灼,我揣著相機(jī)與錄音竞川,去河邊找鬼。 笑死叁熔,一個(gè)胖子當(dāng)著我的面吹牛委乌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荣回,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼遭贸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了心软?” 一聲冷哼從身側(cè)響起壕吹,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤著蛙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后耳贬,有當(dāng)?shù)厝嗽跇淞掷锇l(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
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摇肌。 院中可真熱鬧擂红,春花似錦、人聲如沸围小。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肯适。三九已至变秦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間框舔,已是汗流浹背蹦玫。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(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)容