理解Android Architecture Components系列之LiveData(四)

LiveData

LiveData是一種持有可被觀察數(shù)據(jù)的類脱盲。和其他可被觀察的類不同的是磕潮,LiveData是有生命周期感知能力的,這意味著它可以在activities, fragments, 或者 services生命周期是活躍狀態(tài)時更新這些組件。那么什么是活躍狀態(tài)呢?上篇文章中提到的STARTEDRESUMED就是活躍狀態(tài)跨新,只有在這兩個狀態(tài)下LiveData是會通知數(shù)據(jù)變化的。

要想使用LiveData(或者這種有可被觀察數(shù)據(jù)能力的類)就必須配合實(shí)現(xiàn)了LifecycleOwner的對象使用坏逢。在這種情況下域帐,當(dāng)對應(yīng)的生命周期對象DESTORY時赘被,才能移除觀察者。這對Activity或者Fragment來說顯得尤為重要肖揣,因為他們可以在生命周期結(jié)束的時候立刻解除對數(shù)據(jù)的訂閱帘腹,從而避免內(nèi)存泄漏等問題。

使用LiveData的優(yōu)點(diǎn)

  • UI和實(shí)時數(shù)據(jù)保持一致 因為LiveData采用的是觀察者模式许饿,這樣一來就可以在數(shù)據(jù)發(fā)生改變時獲得通知阳欲,更新UI。
  • 避免內(nèi)存泄漏 觀察者被綁定到組件的生命周期上陋率,當(dāng)被綁定的組件銷毀(destory)時球化,觀察者會立刻自動清理自身的數(shù)據(jù)。
  • 不會再產(chǎn)生由于Activity處于stop狀態(tài)而引起的崩潰 例如:當(dāng)Activity處于后臺狀態(tài)時瓦糟,是不會收到LiveData的任何事件的筒愚。
  • 不需要再解決生命周期帶來的問題 LiveData可以感知被綁定的組件的生命周期,只有在活躍狀態(tài)才會通知數(shù)據(jù)變化菩浙。
  • 實(shí)時數(shù)據(jù)刷新 當(dāng)組件處于活躍狀態(tài)或者從不活躍狀態(tài)到活躍狀態(tài)時總是能收到最新的數(shù)據(jù)
  • 解決Configuration Change問題 在屏幕發(fā)生旋轉(zhuǎn)或者被回收再次啟動巢掺,立刻就能收到最新的數(shù)據(jù)。
  • 數(shù)據(jù)共享 如果對應(yīng)的LiveData是單例的話劲蜻,就能在app的組件間分享數(shù)據(jù)陆淀。這部分詳細(xì)的信息可以參考繼承LiveData

使用LiveData

  1. 創(chuàng)建一個持有某種數(shù)據(jù)類型的LiveData (通常是在ViewModel中)
  2. 創(chuàng)建一個定義了onChange()方法的觀察者。這個方法是控制LiveData中數(shù)據(jù)發(fā)生變化時先嬉,采取什么措施 (比如更新界面)轧苫。通常是在UI Controller (Activity/Fragment) 中創(chuàng)建這個觀察者。
  3. 通過 observe()方法連接觀察者和LiveData疫蔓。observe()方法需要攜帶一個LifecycleOwner類含懊。這樣就可以讓觀察者訂閱LiveData中的數(shù)據(jù),實(shí)現(xiàn)實(shí)時更新衅胀。

創(chuàng)建LiveData對象

LiveData是一個數(shù)據(jù)的包裝岔乔。具體的包裝對象可以是任何數(shù)據(jù),包括集合(比如List)滚躯。LiveData通常在ViewModel中創(chuàng)建雏门,然后通過gatter方法獲取。具體可以看一下代碼:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String 暫時就把MutableLiveData看成是LiveData吧哀九,下面的文章有詳細(xì)的解釋
private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

// Rest of the ViewModel...
}

觀察LiveData中的數(shù)據(jù)

通常情況下都是在組件的onCreate()方法中開始觀察數(shù)據(jù)剿配,原因有以下兩點(diǎn):

  • 系統(tǒng)會多次調(diào)用onResume()方法。
  • 確保Activity/Fragment在處于活躍狀態(tài)時立刻可以展示數(shù)據(jù)阅束。

下面的代碼展示了如何觀察LiveData對象:

public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

更新LiveData對象

如果想要在UI Controller中改變LiveData中的值呢呼胚?(比如點(diǎn)擊某個Button把性別從男設(shè)置成女)。LiveData并沒有提供這樣的功能息裸,但是Architecture Component提供了MutableLiveData這樣一個類蝇更,可以通過setValue(T)postValue(T)方法來修改存儲在LiveData中的數(shù)據(jù)沪编。MutableLiveDataLiveData的一個子類,從名稱上也能看出這個類的作用年扩。舉個直觀點(diǎn)的例子:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});

調(diào)用setValue()方法就可以把LiveData中的值改為John Doe蚁廓。同樣,通過這種方法修改LiveData中的值同樣會觸發(fā)所有對這個數(shù)據(jù)感興趣的類厨幻。那么setValue()postValue()有什么不同呢相嵌?區(qū)別就是setValue()只能在主線程中調(diào)用,而postValue()可以在子線程中調(diào)用况脆。

Room和LiveData配合使用

Room可以返回LiveData的數(shù)據(jù)類型饭宾。這樣對數(shù)據(jù)庫中的任何改動都會被傳遞出去。這樣修改完數(shù)據(jù)庫就能獲取最新的數(shù)據(jù)格了,減少了主動獲取數(shù)據(jù)的代碼看铆。詳細(xì)的例子在前面的文章,不記得可以回去翻翻盛末。

繼承LiveData擴(kuò)展功能

LiveData的活躍狀態(tài)包括:STARTED或者RESUMED兩種狀態(tài)弹惦。那么如何在活躍狀態(tài)下把數(shù)據(jù)傳遞出去呢?下面是示例代碼:

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

可以看到onActive()onInactive()就表示了處于活躍和不活躍狀態(tài)的回調(diào)悄但。

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

如果把StockLiveData寫成單例模式棠隐,那么還可以在不同的組件間共享數(shù)據(jù)。代碼如下:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

轉(zhuǎn)換LiveData中的值(Transform LiveData)

這么說很容易和上文改變LiveData中的值搞混算墨。這里的變換是指在LiveData的數(shù)據(jù)被分發(fā)到各個組件之前轉(zhuǎn)換值的內(nèi)容宵荒,各個組件收到的是轉(zhuǎn)換后的值,但是LiveData里面數(shù)據(jù)本身的值并沒有改變净嘀。(和RXJava中map的概念很像)Lifecycle包中提供了Transformations來提供轉(zhuǎn)換的功能。

Transformations.map()

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

把原來是包含User的LiveData轉(zhuǎn)換成包含String的LiveData傳遞出去侠讯。

Transformations.switchMap()

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

和上面的map()方法很像。區(qū)別在于傳遞給switchMap()的函數(shù)必須返回LiveData對象膜眠。
和LiveData一樣炸宵,Transformation也可以在觀察者的整個生命周期中存在捎琐。只有在觀察者處于觀察LiveData狀態(tài)時,Transformation才會運(yùn)算练慕。Transformation是延遲運(yùn)算的(calculated lazily)零截,而生命周期感知的能力確保不會因為延遲發(fā)生任何問題哪工。

如果在ViewModel對象的內(nèi)部需要一個Lifecycle對象撤嫩,那么使用Transformation是一個不錯的方法。舉個例子:假如有個UI組件接受輸入的地址,返回對應(yīng)的郵政編碼。那么可以 實(shí)現(xiàn)一個ViewModel和這個組件綁定:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS (不要這么干)
       return repository.getPostCode(address);
    }
}

看代碼中的注釋,有個// DON'T DO THIS (不要這么干),這是為什么?有一種情況是如果UI組件被回收后又被重新創(chuàng)建们衙,那么又會觸發(fā)一次 repository.getPostCode(address)查詢宗侦,而不是重用上次已經(jīng)獲取到的查詢馋袜。那么應(yīng)該怎樣避免這個問題呢泽台?看一下下面的代碼:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

postalCode變量的修飾符是publicfinal,因為這個變量的是不會改變的怀酷。哎稻爬?不會改變?那我輸入不同的地址還總返回相同郵編蜕依?先打住桅锄,postalCode這個變量存在的作用是把輸入的addressInput轉(zhuǎn)換成郵編,那么只有在輸入變化時才會調(diào)用repository.getPostCode()方法样眠。這就好比你用final來修飾一個數(shù)組友瘤,雖然這個變量不能再指向其他數(shù)組,但是數(shù)組里面的內(nèi)容是可以被修改的檐束。繞來繞去就一點(diǎn):當(dāng)輸入是相同的情況下商佑,用了 switchMap() 可以減少沒有必要的請求。并且同樣厢塘,只有在觀察者處于活躍狀態(tài)時才會運(yùn)算并將結(jié)果通知觀察者。(最近一直有大佬反應(yīng)這段話有問題肌幽,感謝各位大佬指正晚碾,我又看了遍英文文檔,確實(shí)有邏輯不通的地方喂急。原文中的話就是:In this case, the postalCode field is public and final, because the field never changes. The postalCode field is defined as a transformation of the addressInput, which means that the repository.getPostCode() method is called when addressInput changes. This is true if there is an active observer, if there are no active observers at the time repository.getPostCode() is called, no calculations are made until an observer is added.翻譯過來就是通過addressInput做一層緩沖格嘁,只有addressInput發(fā)生改變時才會真的發(fā)生一次查詢,UI組件回收再被重建后重新設(shè)置了addressInput中內(nèi)容廊移,但是addressInput中內(nèi)容和上次(重建前)一樣糕簿,因此不會觸發(fā)真正的查詢探入,在這個意義上來講減少了查詢次數(shù)。所以使用switchMap()的意義就在于對底層返回的LiveData根據(jù)需要變更其中的內(nèi)容懂诗,同時不會影響生命周期控制邏輯蜂嗽。再次感謝各位大佬的較真,你們的指正是我不斷前進(jìn)的動力Q旰恪V簿伞!如果覺得還有有邏輯問題离唐,歡迎溝通2「健!亥鬓!

合并多個LiveData中的數(shù)據(jù)

MediatorLiveData是LiveData的子類完沪,可以通過MediatorLiveData合并多個LiveData來源的數(shù)據(jù)。同樣任意一個來源的LiveData數(shù)據(jù)發(fā)生變化嵌戈,MediatorLiveData都會通知觀察他的對象覆积。說的有點(diǎn)抽象,舉個例子咕别。比如UI接收來自本地數(shù)據(jù)庫和網(wǎng)絡(luò)數(shù)據(jù)技健,并更新相應(yīng)的UI《韫埃可以把下面兩個LiveData加入到MeidatorLiveData中:

  • 關(guān)聯(lián)數(shù)據(jù)庫的LiveData
  • 關(guān)聯(lián)聯(lián)網(wǎng)請求的LiveData

相應(yīng)的UI只需要關(guān)注MediatorLiveData就可以在任意數(shù)據(jù)來源更新時收到通知雌贱。

相關(guān)文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)
理解Android Architecture Components系列之WorkManager(八)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偿短,隨后出現(xiàn)的幾起案子欣孤,更是在濱河造成了極大的恐慌,老刑警劉巖昔逗,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件降传,死亡現(xiàn)場離奇詭異,居然都是意外死亡勾怒,警方通過查閱死者的電腦和手機(jī)婆排,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笔链,“玉大人段只,你說我怎么就攤上這事〖ǎ” “怎么了赞枕?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我炕婶,道長姐赡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任柠掂,我火速辦了婚禮项滑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陪踩。我一直安慰自己杖们,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布肩狂。 她就那樣靜靜地躺著摘完,像睡著了一般。 火紅的嫁衣襯著肌膚如雪傻谁。 梳的紋絲不亂的頭發(fā)上孝治,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音审磁,去河邊找鬼谈飒。 笑死,一個胖子當(dāng)著我的面吹牛态蒂,可吹牛的內(nèi)容都是我干的杭措。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钾恢,長吁一口氣:“原來是場噩夢啊……” “哼手素!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瘩蚪,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泉懦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疹瘦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崩哩,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年言沐,在試婚紗的時候發(fā)現(xiàn)自己被綠了邓嘹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡险胰,死狀恐怖吴超,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸯乃,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站缨睡,受9級特大地震影響鸟悴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奖年,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一细诸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陋守,春花似錦震贵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至中燥,卻和暖如春寇甸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疗涉。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工拿霉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咱扣。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓绽淘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闹伪。 傳聞我的和親對象是個殘疾皇子沪铭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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