Jetpack之Lifecycle腻脏、LiveData及ViewModel是如何讓架構(gòu)起飛的

《也談Android應(yīng)用架構(gòu)》中我們對MVC、MVP岔乔、MVVM進(jìn)行了詳盡的分析,但還有一個(gè)問題懸而未決滚躯,那就是生命周期雏门。在Android平臺(tái)上生命周期具有十分重要的意義,因此這也是架構(gòu)必須考慮的因素之一掸掏。生命周期處理不好很容易發(fā)生內(nèi)存泄漏剿配,但對架構(gòu)而言,真正困擾我們的卻不是內(nèi)存泄漏的問題阅束,反而是因生命周期太短呼胚,實(shí)例被銷毀重建,從而產(chǎn)生一系列不必要的行為息裸。這種情況發(fā)生的場景主要在屏幕旋轉(zhuǎn)以及頁面被系統(tǒng)回收時(shí)蝇更。

Activity難免需要依賴網(wǎng)絡(luò)、數(shù)據(jù)庫等數(shù)據(jù)來渲染頁面呼盆,當(dāng)屏幕旋轉(zhuǎn)時(shí)年扩,Activity重建,因而數(shù)據(jù)需要重新加載访圃,但這完全沒有必要厨幻。一種策略是對數(shù)據(jù)進(jìn)行緩存,這是一種可考慮的方案腿时,但它只解決了一半的問題况脆,如果Activity重建發(fā)生在數(shù)據(jù)返回前,此時(shí)根本來不及緩存批糟,下一次請求就迅速地發(fā)生了格了。

在MVP、MVVM架構(gòu)中徽鼎,數(shù)據(jù)由M來提供盛末,但真正和生命周期打交道的是P和VM,我們得從這里著手解決生命周期的問題否淤。再明確地說一遍悄但,我們要解決的問題是不論在數(shù)據(jù)返回前還是返回后,在屏幕旋轉(zhuǎn)這種場景下都不需要多次加載數(shù)據(jù)石抡。這個(gè)問題由兩種狀態(tài)組成:加載中和加載完成后檐嚣,對于前者我們要知道當(dāng)前正在加載數(shù)據(jù),對于后者則只需要把數(shù)據(jù)緩存起來即可汁雷。

對數(shù)據(jù)緩存很簡單净嘀,但加載中的狀態(tài)就要好好斟酌一番了报咳,我們可以輕易地給這個(gè)狀態(tài)加標(biāo)記,但隨著重建這個(gè)標(biāo)記也會(huì)被回收挖藏,由此可以想到兩種應(yīng)對之法暑刃,一是讓P和VM不被回收,這樣就可以進(jìn)行標(biāo)記了膜眠,二是讓當(dāng)前這個(gè)加載不被回收岩臣,也就是其生命周期不和P與VM同步。不讓P和VM回收宵膨,有以下幾種方式:

  • 配置android:configChanges="orientation|keyboardHidden|screenSize"

onRetainCustomNonConfigurationInstance()/getLastCustomNonConfigurationInstance()

  • 繼承Fragment

除了配置configChanges架谎,其余兩種方式都是不錯(cuò)的解決辦法。除此之外還有一種方式可以同時(shí)實(shí)現(xiàn)我們說的兩種應(yīng)對之法辟躏,這就是Loader谷扣。關(guān)于什么是Loader以及Loader如何保持P和VM不被回收,大家可以自行查閱相關(guān)資料捎琐,如何保持一個(gè)加載任務(wù)不被回收会涎,可以參閱architecture-samples
,并切換到分支deprecated-todo-mvp-loaders瑞凑。

我們不打算大刀闊斧地講述每個(gè)方案的細(xì)節(jié)和優(yōu)缺點(diǎn)末秃,因?yàn)殡S著Jetpack誕生,這種復(fù)雜又費(fèi)力的方案系統(tǒng)已經(jīng)幫我們完成了籽御,我們只需要了解系統(tǒng)是如何處理的即可练慕。從書寫代碼變成查看代碼,可以說大大減少了我們對生命周期的“怨恨”技掏,不得不說Google這波操作很圈粉呢铃将。在這里,我們只關(guān)注Lifecycle零截、ViewModel和LiveData三部分麸塞。

Lifecycle

生命周期讓人困擾的很大一部分原因是只有Activity這樣的系統(tǒng)組件才可以感知生命周期的變化,而Lifecycle的出現(xiàn)則把這種感知力放大到了任何類涧衙。Lifecycle的原理很簡單,當(dāng)生命周期變化時(shí)奥此,Activity通知到Lifecycle弧哎,其他類就可以通過Lifecycle感知生命周期的變化了。

Lifecycle的核心就三個(gè)類:Lifecycle稚虎、LifecycleOwnerLifecycleObserver撤嫩。從名字就可以輕易看出這是一個(gè)觀察者模式,Activity作為LifecycleOwner蠢终,把生命周期的變化反映到Lifecycle序攘,Lifecycle再通知給所有的LifecycleObserver即可茴她。這個(gè)概念太簡單了,就不在此贅述源碼了(看了一下沒有什么亮點(diǎn)~)程奠,不過如果你感興趣丈牢,請注意一下ReportFragment這個(gè)類,Activity的生命周期就是通過它來通知Lifecycle的(添加一個(gè)看不見的Fragment瞄沙,這個(gè)操作似乎似曾相識(shí)己沛?)。

Lifecycle只是讓P和VM獲得了生命周期感知能力距境,并沒有解決如何保持的問題申尼,不過它是我們后面內(nèi)容的基礎(chǔ),所以還是很有必要了解一番垫桂。

ViewModel

這個(gè)ViewModel其實(shí)就是MVVM中的VM师幕,但經(jīng)過Google的加工之后具備了很好的生命周期感知能力,這就是我們苦苦追尋的東西呀∥芴玻現(xiàn)在我們就對它抽絲剝繭霹粥,看看系統(tǒng)是如何完成這件事的。

ViewModel的使用非常簡單碱呼,就是一句話:

class LoginActivity : AppCompatActivity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        loginViewModel = ViewModelProvider(this, LoginViewModelFactory()).get(LoginViewModel::class.java)
    }
}

當(dāng)重建發(fā)生時(shí)蒙挑,LoginActivity、ViewModelProvider都是新的實(shí)例愚臀,但是LoginViewModel一定得是原來的實(shí)例忆蚀,這說明它在某處被緩存了起來。先看下ViewModelProvider做了什么吧:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

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) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        // ...
        return (T) viewModel;
    } else {
        // ...   
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

非常簡單姑裂,從Activity獲取到了一個(gè)ViewModelStore馋袜,如果里面包含了LoginViewModel就直接取出來,否則新建一個(gè)并緩存到ViewModelStore里舶斧。那么ViewModelStore是什么欣鳖,它是如何保持下來的?

ViewModelStore里維護(hù)了一個(gè)Map茴厉,存儲(chǔ)ViewModel實(shí)例泽台,僅此而已。AppCompatActivity實(shí)現(xiàn)了ViewModelStoreOwner接口矾缓,里面只有一個(gè)方法getViewModelStore怀酷,它的實(shí)現(xiàn)如下:

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

這里出現(xiàn)了一個(gè)getLastNonConfigurationInstance(),我們在前面提過一個(gè)getLastCustomNonConfigurationInstance()方法嗜闻,那么應(yīng)該也有一個(gè)onRetainNonConfigurationInstance()與之對應(yīng)蜕依,它的實(shí)現(xiàn)如下:

public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        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;
}

一切都很明了了,系統(tǒng)用的是和我們一樣的方法,只是方法名稱稍有區(qū)別而已样眠。ViewModelStore里緩存了ViewModel實(shí)例友瘤,那么在Activity真正銷毀時(shí)肯定需要清空,ViewModel和ViewModelStore都提供了一個(gè)clear()方法檐束,ViewModelStore的clear方法實(shí)現(xiàn)如下:

public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}

它會(huì)調(diào)用其中每個(gè)ViewModel的clear方法使我們有機(jī)會(huì)清除一些數(shù)據(jù)或任務(wù)辫秧,然后就將Map清空了。在Activity中它是這樣被調(diào)用的:

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

isChangingConfigurations()用以標(biāo)識(shí)Activity執(zhí)行onDestory方法后是否準(zhǔn)備重建厢塘,只有不重建時(shí)才會(huì)清空ViewModel茶没,所以只要在clear時(shí)清理數(shù)據(jù)和中斷任務(wù)就好了。

現(xiàn)在我們解決了ViewModel實(shí)例保持的問題晚碾,接下來讓我們再想想應(yīng)該怎么解決數(shù)據(jù)重復(fù)加載的問題抓半。數(shù)據(jù)重復(fù)加載主要是因?yàn)橐粋€(gè)異步任務(wù)被多次調(diào)用,例如請求一個(gè)列表數(shù)據(jù)時(shí)格嘁,如果屏幕發(fā)生旋轉(zhuǎn)笛求,以下方法會(huì)被多次調(diào)用:

fun getUsers(){
    executor.execute {
        val users = model.getUsers()
        handler.post{
            view?.getUsers(users)
        }
    }
}

按照之前的說法,可以給加載任務(wù)加上標(biāo)記糕簿,當(dāng)它正在加載中就等待它加載完成探入,如果已經(jīng)加載完就取緩存的數(shù)據(jù),但是這太復(fù)雜了懂诗,稍有不慎就會(huì)出問題蜂嗽。如何讓事情變得簡單一些,出錯(cuò)率低一些呢殃恒?

要想避免此問題植旧,最好的方式是只調(diào)用一次getUsers()方法,那這個(gè)方法就不能由Activity來調(diào)用了离唐,需要ViewModel自己調(diào)用病附,等它拿到結(jié)果后反過來通知Activity。這不就是MVVM嗎亥鬓?現(xiàn)在我們總算明白為什么被系統(tǒng)實(shí)現(xiàn)的這個(gè)類叫ViewModel了完沪,因?yàn)樗褪菫镸VVM量身定制的。

關(guān)于數(shù)據(jù)反過來通知Activity這件事嵌戈,也不需要擔(dān)心覆积,因?yàn)橄到y(tǒng)照樣幫我們實(shí)現(xiàn)了,這就是LiveData熟呛。

LiveData

可觀察的數(shù)據(jù)并不是只有LiveData技健,但LiveData有自己獨(dú)特的本領(lǐng),它也具備生命周期感知力惰拱。LiveData只有在有效的生命周期范圍內(nèi)通知觀察者,并在生命周期結(jié)束后自動(dòng)移除觀察者,僅這一點(diǎn)就足夠讓它脫穎而出偿短。我們可以從它的observe方法欣孤,了解它處理生命周期的大致流程。

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    owner.getLifecycle().addObserver(wrapper);
}

這里創(chuàng)建了一個(gè)LifecycleBoundObserver來觀察Activity的生命周期昔逗,我們看看它做了哪些工作吧:

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    // ...

    @Override
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        activeStateChanged(shouldBeActive());
    }

    // ...
}

它實(shí)現(xiàn)了LifecycleEventObserver降传,并在DESTROYED狀態(tài)時(shí)移除了觀察者,其后只是調(diào)用了一個(gè)activeStateChanged方法勾怒,這個(gè)方法實(shí)現(xiàn)如下:

void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    // immediately set active state, so we'd never dispatch anything to inactive
    // owner
    mActive = newActive;
    boolean wasInactive = LiveData.this.mActiveCount == 0;
    LiveData.this.mActiveCount += mActive ? 1 : -1;
    if (wasInactive && mActive) {
        onActive();
    }
    if (LiveData.this.mActiveCount == 0 && !mActive) {
        onInactive();
    }
    if (mActive) {
        dispatchingValue(this);
    }
}

這里通過是否active來分發(fā)數(shù)據(jù)婆排,在dispatchingValue中會(huì)通知所有的觀察者。

LiveData實(shí)際上是一個(gè)雙層的觀察者模式笔链,它通過觀察Lifecycle得知是否active段只,在此充當(dāng)?shù)氖怯^察者。當(dāng)它的值發(fā)生變化或者監(jiān)聽到Lifecycle變化時(shí)再通知到它的觀察者鉴扫,在此又充當(dāng)被觀察者赞枕。如此它就具備了我們想要的一切能力坪创。

總結(jié)

現(xiàn)在我們的架構(gòu)“本地化”工作又前進(jìn)了一大步,它終于在生命周期方面也不存在問題了莱预,使用Lifecycle+ViewModel+LiveData組合,解決了架構(gòu)最棘手的問題依沮,也把MVVM推向了另一個(gè)高度涯贞。當(dāng)然這并不代表著MVP就徹底敗下陣來,畢竟生命周期問題只影響了初始化時(shí)的數(shù)據(jù)悉抵,大量場景下還是有無數(shù)的交互行為,需要根據(jù)用戶的操作主動(dòng)加載各種各樣的數(shù)據(jù)傻谁,這種情況下,MVP的直觀性要遠(yuǎn)遠(yuǎn)強(qiáng)于MVVM列粪,這個(gè)特點(diǎn)也可以簡單理解為MVP適合復(fù)雜交互場景审磁,MVVM適合展示型場景。因此我們應(yīng)該根據(jù)具體場景靈活選用MVP和MVVM岂座,甚至在某些情況下可以合二為一。

還是那句話费什,沒有最好的架構(gòu),只有最適合當(dāng)前場景的架構(gòu)。


我是飛機(jī)醬泉懦,如果您喜歡我的文章疹瘦,可以關(guān)注我~

編程之路,道阻且長言沐。唯,路漫漫其修遠(yuǎn)兮险胰,吾將上下而求索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲸阻,一起剝皮案震驚了整個(gè)濱河市缨睡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌细诸,老刑警劉巖陋守,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異猩系,居然都是意外死亡中燥,警方通過查閱死者的電腦和手機(jī)寇甸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門疗涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绽淘,你說我怎么就攤上這事闹伪∽吵兀” “怎么了伦意?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我已骇,道長,這世上最難降的妖魔是什么褪储? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任鲤竹,我火速辦了婚禮,結(jié)果婚禮上辛藻,老公的妹妹穿的比我還像新娘。我一直安慰自己痘拆,他們只是感情好氮墨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著规揪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪字支。 梳的紋絲不亂的頭發(fā)上奕坟,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音刃跛,去河邊找鬼苛萎。 笑死桨昙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的齐苛。 我是一名探鬼主播桂塞,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玛痊!你這毒婦竟也來了狂打?” 一聲冷哼從身側(cè)響起擂煞,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤对省,失蹤者是張志新(化名)和其女友劉穎晾捏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粟瞬,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裙品,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了市怎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡干像,死狀恐怖驰弄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戚篙,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布位喂,位于F島的核電站,受9級特大地震影響塑崖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澜躺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一聋呢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧削锰,春花似錦毕莱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廓八,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剧蹂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工先巴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冒冬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓剂邮,卻偏偏與公主長得像乐埠,于是被迫代替她去往敵國和親抗斤。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345