Android應用結構之LiveData

??LiveData是一個可被觀察的數據持有者類。與常規的Observable不同,LiveData能意識到應用程序組件的生命周期變化,這意味著它能遵守Activity、Fragment、Service等組件的生命周期。這種意識確保LiveData只更新處于活躍狀態的應用程序組件Observer。

Note: 如何在Android項目中引入LiveData組件, 請參閱 添加項目組件

??如果一個Observer的生命周期處于STARTED或RESUMED狀態,那么LiveData將認為這個Observer處于活躍狀態。 LiveData僅通知活躍的Observer去更新UI。 非活躍狀態的Observer,即使訂閱了LiveData,也不會收到更新的通知。

??結合一個實現了LifecycleOwner接口的對象,你能注冊一個Observer。這種結合關系使得當具有生命周期的對象的狀態變為DESTROYED時,Observer將被取消訂閱。 這對于Activity和Fragment尤其有用,因為它們可以安全地訂閱LiveData對象,而不必擔心內存泄漏 - 當Activity和Fragment生命周期為DESTROYED時,它們立即會被取消訂閱。

??有關如何使用LiveData的更多信息,請參閱 使用LiveData對象

LiveData的優點

在項目中使用LiveData,會有以下優點:

  • 確保UI符合數據狀態
    LiveData遵循觀察者模式。 當生命周期狀態改變時,LiveData會向Observer發出通知。 您可以把更新UI的代碼合并在這些Observer對象中。不必去考慮導致數據變化的各個時機,每次數據有變化,Observer都會去更新UI。
  • 沒有內存泄漏
    Observer會綁定具有生命周期的對象,并在這個綁定的對象被銷毀后自行清理。
  • 不會因停止Activity而發生崩潰
    如果Observer的生命周期處于非活躍狀態,例如在后退堆棧中的Activity,就不會收到任何LiveData事件的通知。
  • 不需要手動處理生命周期
    UI組件只需要去觀察相關數據,不需要手動去停止或恢復觀察。LiveData會進行自動管理這些事情,因為在觀察時,它會感知到相應組件的生命周期變化。
  • 始終保持最新的數據
    如果一個對象的生命周期變到非活躍狀態,它將在再次變為活躍狀態時接收最新的數據。 例如,后臺Activity在返回到前臺后立即收到最新數據。
  • 正確應對配置更改
    如果一個Activity或Fragment由于配置更改(如設備旋轉)而重新創建,它會立即收到最新的可用數據。
  • 共享資源
    您可以使用單例模式擴展LiveData對象并包裝成系統服務,以便在應用程序中進行共享。LiveData對象一旦連接到系統服務,任何需要該資源的Observer都只需觀察這個LiveData對象。 有關更多信息,請參閱擴展LiveData

使用LiveData對象

按照以下步驟使用LiveData對象:

  1. 創建一個LiveData的實例來保存特定類型的數據。 這通常在ViewModel類中完成。
  2. 創建一個定義了onChanged()方法的Observer對象,當LiveData對象保存的數據發生變化時,onChanged()方法可以進行相應的處理。 您通常在UI控制器(如Activity或Fragment)中創建Observer對象。
  3. 使用observe()方法將Observer對象注冊到LiveData對象。 observe()方法還需要一個LifecycleOwner對象作為參數。 Observer對象訂閱了LiveData對象,便會在數據發生變化時發出通知。 您通常需要UI控制器(如Activity或Fragment)中注冊Observer對象。

Note:您可以使用observeForever(Observer)方法注冊一個沒有關聯LifecycleOwner對象的Observer。 在這種情況下,Observer被認為始終處于活動狀態,因此當有數據變化時總是會被通知。 您可以調用removeObserver(Observer)方法移除這些Observer。

??當你更新LiveData對象中存儲的數據時,所有注冊了的Observer,只要所綁定的LifecycleOwner處于活動狀態,就會被觸發通知。
??LiveData允許UI控制器Observer訂閱更新。 當LiveData對象所保存的數據發生變化時,UI會在響應中自動更新。

創建LiveData對象

??LiveData是一個包裝器,可用于任何數據,包括實現Collections的對象,如List。一個 LiveData對象通常存儲在ViewModel對象中,并通過getter方法訪問,如以下示例所示:

public class NameViewModel extends ViewModel {

    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;

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

// Rest of the ViewModel...
}

起初,LiveData對象中的數據未設置。

Note:確保在ViewModel而不是Activity或Fragment中保存用來更新UI的LiveData對象,原因如下:

  • 避免臃腫的Activity和Fragment。 這些UI控制器負責顯示數據而不是保存數據狀態。
  • 將LiveData實例與特定Activity或Fragment實例分離,這將使得LiveData對象在配置更改后仍然存活。

有關ViewModel更多的用處和益處,請參閱Android應用結構之ViewModel

觀察LiveData對象

??在大多數情況下,出于以下原因,應用程序組件的onCreate()方法是開始觀察LiveData對象的最佳位置:

  1. 確保系統不會從Activity或Fragment的onResume()方法中進行多余的調用。
  2. 確保Activity或Fragment一旦變為活動狀態時,就有可展示的數據。 當應用程序組件處于STARTED狀態,它就需從它所觀察的LiveData對象中接收到最新的值。 所以我們需要在一開始就設置好觀察。

??通常情況下,LiveData只在數據有變化時,給活躍的Observer進行通知。 此行為的一個例外是,Observer在從非活躍狀態變為活躍狀態時也會收到通知。 并且,如果Observer第二次從非活躍狀態變為活躍狀態,則只有在自上一次變為活躍狀態以來該數據發生變化時才會接收到更新。

以下示例代碼演示了如何開始觀察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);
    }
}

??傳遞nameObserver作為參數,調用observe()之后,將立即回調onChanged(),提供存儲在mCurrentName中的最新值。 如果LiveData對象mCurrentName的值并未設置,則不調用onChanged()。

更新LiveData對象

??LiveData沒有公用的方法來更新存儲的數據。 MutableLiveData類暴露公用的setValue(T)和postValue(T)方法,如果需要編輯存儲在LiveData對象中的值,必須使用這兩個方法。 通常在ViewModel中使用MutableLiveData,然后ViewModel僅向Observer公開不可變的LiveData對象。

??在建立觀察者關系之后,可以更新LiveData對象的值,如以下示例所示,當用戶點擊按鈕時向所有觀察者發出通知:

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

??在示例中調用setValue(T)會導致Observer使用值"John Doe"調用onChanged()方法。 這個例子展示了點擊按鈕,setValue()或者postValue()被調用來更新mName,原因有多種,包括響應網絡請求或數據庫加載完成; 在所有情況下,調用setValue()或postValue()都會觸發觀察者并更新UI。

Note: 必須要從主線程調用setValue(T) 方法來更新LiveData 對象. 如果代碼在工作線程中執行, 你可以使用postValue(T) 方法來更新LiveData對象.

LiveData配合Room使用

??Room持久性庫支持Observable查詢返回LiveData對象。 Observable查詢成為數據庫訪問對象(DAO)的一項功能。
??當更新數據庫時,會生成所有必要的代碼來更新LiveData對象。 生成的代碼在需要時在后臺線程上異步運行查詢。 這種模式對于保持用戶界面中顯示的數據與存儲在數據庫中的數據同步很有用。 您可以在Room持久性庫指南中閱讀關于Room和DAO的更多信息。

擴展LiveData

??如果Observer的生命周期處于STARTED或RESUMED狀態,則LiveData將認為Observer處于活動狀態。以下示例代碼說明了如何擴展LiveData類:

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

本例中StockLiveData的實現包括以下重要的方法:

  • 當LiveData對象有一個活躍的Observer時,onActive()方法被調用。 這意味著你需要從這個方法開始觀察股票價格的更新。
  • 當LiveData對象沒有任何活躍的Observer時,onInactive()方法被調用。 由于沒有Observer在監聽,所以沒有理由繼續保持與StockManager服務的連接。
  • setValue(T)方法更新LiveData實例的值,并通知活動觀察者有關更改。
    您可以這樣使用StockLiveData類,如下所示:
public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

??observe()方法第一個參數是作為LifecycleOwner實例的Fragment。 這樣做表示此Observer綁定了Lifecycle對象的生命周期,即:

  • 如果Lifecycle對象不處于活動狀態,則即使值發生更改,也不會調用Observer。
  • Lifecycle對象被銷毀后,Observer被自動刪除。
    LiveData對象具有感知生命周期的能力意味著您可以在多個Activity,Fragment和service之間共享它們。 為了保持簡潔,你可以使用單例模式實現LiveData,如下所示:
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);
    }
}

你可以在Fragment中使用它,如下所示:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}

??多個Activity和Fragment可以觀察到MyPriceListener實例。 LiveData只在他們至少一個處于可見和活躍狀態時才連接到系統服務。

轉換LiveData

??您可能希望先轉換存儲在LiveData對象中的值,然后再將其分派給Observer,或者您可能需要根據一個LiveData實例的值返回不同的LiveData實例。Lifecycle包提供了 Transformations類,提供了支持這些使用場景的方法。

Transformations.map()
??使用一個函數來轉換存儲在LiveData對象中的值,并向下傳遞轉換后的值。

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

Transformations.switchMap()
??與map()類似,使用一個函數來轉換存儲在LiveData對象中的值,并向下傳遞結果。 傳遞給switchMap()的函數必須返回一個LiveData對象,如下例所示:

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

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

??您可以使用轉換方法在Observer的生命周期中傳遞信息。 除非Observer正在觀看返回的LiveData對象,否則不會計算轉換。 由于轉換是延遲計算的,所以與生命周期相關的行為隱式傳遞,而不需要額外的顯式調用或依賴關系。
??如果您認為在ViewModel對象中需要Lifecycle對象,則轉換可能是更好的解決方案。 例如,假設您有一個接受地址并返回該地址的郵政編碼的UI組件。 您可以為此組件實現樸素的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);
    }
}

??UI組件隨后需要從以前的LiveData對象注銷,并在每次調用getPostalCode()時注冊到新實例。 另外,如果UI組件被重新創建,它會觸發對repository.getPostCode()方法的另一個調用,而不是使用前一個調用的結果。
??相反,您可以實現郵政編碼查找作為地址輸入的轉換,如以下示例所示:

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字段是公開的和最終的,因為該字段永遠不會改變。 postalCode字段定義為addressInput的轉換,這意味著addressInput發生更改時,如果有一個活躍的Observer,將調用repository.getPostCode()方法,如果在repository.getPostCode()被調用時沒有活躍的Observer,直到添加一個觀察者才會進行計算。
??此機制允許較低級別的應用程序創建按需延遲計算的LiveData對象。 ViewModel對象可以很容易地獲得對LiveData對象的引用,然后在其上定義轉換規則。

創建新的Transformations

??我們有十幾個不同的具體Transformations,它們可能在你的應用程序中很有用,但是它們并不是默認提供的。 要實現自己的轉換,您可以使用MediatorLiveData類,該類監聽其他LiveData對象并處理它們發出的事件。 MediatorLiveData將其狀態正確地傳播到源LiveData對象。 要了解有關此模式的更多信息,請參閱Transformations的參考文檔。

合并多個LiveData源

??MediatorLiveData是LiveData的一個子類,幫助您合并多個LiveData源。 在任何原始LiveData源對象改變后,MediatorLiveData對象的Observer會被觸發。
??例如,如果在UI中有一個從本地數據庫或網絡獲取更新的LiveData對象,則可以將以下數據源添加到MediatorLiveData對象:

  • 與存儲在數據庫中的數據關聯的LiveData對象。
  • 與從網絡訪問的數據關聯的LiveData對象。
    您的Activity只需觀察MediatorLiveData對象即可接收來自兩個數據源的更新。 有關詳細示例,請參閱應用程序體系結構指南的附錄:Guide to App Architecture
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370