(五)LiveData

weixin_33751566發表於2018-10-22
概述
  • LiveData是一個可觀察的資料持有者類。
  • 與常規observable不同,LiveData是生命週期感知的,這意味著它尊重其他應用程式元件的生命週期,例如活動,片段或服務。
    此感知確保LiveData僅更新處於活動的生命週期狀態的應用程式元件觀察者。

如果Observer類表示的觀察者生命週期處於STARTEDRESUMED狀態,LiveData會認為它處於活動狀態。
LiveData僅通知活動觀察者有關更新的資訊。 註冊觀看LiveData物件的非活動觀察者不會收到有關更改的通知。
您可以註冊與實現LifecycleOwner介面的物件配對的觀察者。 此關係允許在相應Lifecycle物件的狀態更改為DESTROYED時刪除觀察者。 這對於Activity和Fragment特別有用,因為它們可以安全地觀察LiveData物件而不用擔心洩漏 - Activity和Fragment在其生命週期被破壞時立即取消訂閱。

一、使用LiveData的優點

確保UI符合資料狀態

LiveData遵循觀察者模式。 生命週期狀態更改時,LiveData會通知Observer物件。 您可以合併程式碼以更新這些Observer物件中的UI。

沒有記憶體洩漏

觀察者繫結到Lifecycle物件並在其相關生命週期被破壞後自行清理。

由於停止活動而沒有崩潰

如果觀察者的生命週期處於非活動狀態(例如,在後端堆疊中的活動的情況下),則它不會接收任何LiveData事件。

不再需要手動生命週期處理

UI元件只是觀察相關資料,不會停止或恢復觀察。 LiveData自動管理所有這些,因為它在觀察時意識到相關的生命週期狀態變化。

始終保持最新資料

如果生命週期變為非活動狀態,則會在再次變為活動狀態時接收最新資料。 例如,後臺活動在返回前臺後立即收到最新資料。

適當的配置更改

如果由於配置更改(例如裝置輪換)而重新建立Activity或Fragment,則會立即接收最新的可用資料。

共享資源

您可以使用單例模式擴充套件LiveData物件以包裝系統服務,以便可以在應用程式中共享它們。 LiveData物件連線到系統服務一次,然後任何需要該資源的觀察者都可以只觀看LiveData物件。

二、使用LiveData物件

請按照以下步驟使用LiveData物件:

  1. 建立LiveData例項以儲存特定型別的資料。 這通常在您的ViewModel類中完成。
  2. 建立一個Observer物件,該物件定義onChanged()方法,該方法控制LiveData物件保持資料更改時發生的情況。 您通常在UI控制器中建立一個Observer物件,例如Activity或Fragment。
  3. 使用observe()方法將Observer物件附加到LiveData物件。 observe()方法採用LifecycleOwner物件。 這會將Observer物件訂閱到LiveData物件,以便通知它更改。 您通常將Observer物件附加到UI控制器中,例如Activity或Fragment。

注意:您可以使用observeForever(Observer)方法註冊沒有關聯的LifecycleOwner物件的觀察者。 在這種情況下,觀察者被認為始終處於活動狀態,因此始終會收到有關修改的通知。 您可以刪除這些呼叫removeObserver(Observer)方法的觀察者。

更新儲存在LiveData物件中的值時,只要附加的LifecycleOwner處於活動狀態,它就會觸發所有已註冊的觀察者。

LiveData允許UI控制器觀察者訂閱更新。 當LiveData物件儲存的資料發生更改時,UI會自動更新響應。

建立LiveData物件
  • LiveData是一個包裝器,可以與任何資料一起使用,包括實現集合的物件,例如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物件中的資料。

注意:確儲存儲更新ViewModel物件中的UI的LiveData物件,而不是活動或片段,原因如下:

  • 避免臃腫的Activityb和Fragment。 現在,這些UI控制器負責顯示資料但不保持資料狀態
  • 將LiveData例項與特定Activity或Fragment例項分離,並允許LiveData物件在配置更改後繼續存在。
觀察LiveData物件

在大多數情況下,app元件的onCreate()方法是開始觀察LiveData物件的正確位置,原因如下:

  • 確保系統不會從Activity和Fragment的onResume()方法進行冗餘呼叫。
  • 確保活動或片段具有可在其變為活動狀態時立即顯示的資料。 一旦應用程式元件處於STARTED狀態,它就會從它正在觀察的LiveData物件中接收最新值。 只有在設定了要觀察的LiveData物件時才會出現這種情況。

通常,LiveData僅在資料更改時才傳遞更新,並且僅在活動觀察者時傳遞更新。 此行為的一個例外是觀察者在從非活動狀態更改為活動狀態時也會收到更新。 此外,如果觀察者第二次從非活動狀態更改為活動狀態,則只有在自上次活動狀態以來該值發生更改時才會收到更新。
以下示例程式碼說明了如何開始觀察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僅向觀察者公開不可變的LiveData物件。
  • 設定觀察者關係後,可以更新LiveData物件的值,如以下示例所示,當使用者點選按鈕時觸發所有觀察者:
mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});

在示例中呼叫setValue(T)會導致觀察者使用值"John Doe"呼叫其onChanged()方法。 該示例顯示按下按鈕,但可以呼叫setValue()postValue()以更新mName,原因有多種,包括響應網路請求或資料庫負載完成; 在所有情況下,對setValue()postValue()的呼叫都會觸發觀察者並更新UI。

注意:必須呼叫setValue(T)方法才能從主執行緒更新LiveData物件。 如果程式碼在工作執行緒中執行,則可以使用postValue(T)方法來更新LiveData物件。

與Room一起使用LiveData

Room永續性庫支援可觀察的查詢,這些查詢返回LiveData物件。 可觀察查詢作為資料庫訪問物件(DAO)的一部分編寫。

在更新資料庫時,Room會生成更新LiveData物件所需的所有程式碼。 生成的程式碼在需要時在後臺執行緒上非同步執行查詢。 此模式對於使UI中顯示的資料與儲存在資料庫中的資料保持同步非常有用。

三、擴充套件LiveData

如果觀察者的生命週期處於STARTEDRESUMED狀態,LiveData會將觀察者視為處於活動狀態。
以下示例程式碼說明了如何擴充套件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);
    }
}

此示例中價格監聽器的實現包括以下重要方法:

  • 當LiveData物件具有活動觀察者時,將呼叫onActive()方法。 這意味著您需要從此方法開始觀察股票價格更新。
  • 當LiveData物件沒有任何活動觀察者時,將呼叫onInactive()方法。 由於沒有觀察者正在收聽,因此沒有理由保持與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的一個例項)作為第一個引數傳遞。 這樣做表示此觀察者繫結到與所有者關聯的Lifecycle物件,這意味著:

  • 如果Lifecycle物件未處於活動狀態,則即使值發生更改,也不會呼叫觀察者。
  • 銷燬Lifecycle物件後,會自動刪除觀察者。

LiveData物件具有生命週期感知這一事實意味著您可以在多個活動,片段和服務之間共享它們。
為了簡化示例,您可以將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);
    }
}

您可以在片段中使用它,如下所示:

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

多個片段和活動可以觀察MyPriceListener例項。 LiveData僅在系統服務中的一個或多個可見且處於活動狀態時才連線到系統服務。

四、轉換LiveData

您可能希望在將其分配給觀察者之前更改儲存在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) );

您可以使用轉換方法在觀察者的生命週期中傳遞資訊。 除非觀察者正在觀察返回的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欄位是public和final,因為該欄位永遠不會更改。 postalCode欄位被定義為addressInput的轉換,這意味著當addressInput更改時將呼叫repository.getPostCode()方法。 如果存在活動的觀察者,則如果在呼叫repository.getPostCode()時沒有活動的觀察者,則在新增觀察者之前不進行任何計算。

此機制允許較低階別的應用程式建立按需延遲計算的LiveData物件。 ViewModel物件可以輕鬆獲取對LiveData物件的引用,然後在它們之上定義轉換規則。

建立新的轉換

在您的應用中有十幾種不同的特定轉換可能很有用,但預設情況下不提供它們。 要實現自己的轉換,可以使用MediatorLiveData類,該類偵聽其他LiveData物件並處理它們發出的事件。 MediatorLiveData正確地將其狀態傳播到源LiveData物件。

五、合併多個LiveData源

MediatorLiveData是LiveData的子類,允許您合併多個LiveData源。 只要任何原始LiveData源物件發生更改,就會觸發MediatorLiveData物件的觀察者。
例如,如果UI中有可以從本地資料庫或網路更新的LiveData物件,則可以將以下源新增到MediatorLiveData物件:

  • 與儲存在資料庫中的資料關聯的LiveData物件。
  • 與從網路訪問的資料關聯的LiveData物件。

您的活動只需要觀察MediatorLiveData物件以從兩個源接收更新。

相關文章