1. 程式人生 > >Android LiveData詳解

Android LiveData詳解

官方文件翻譯

1.LiveData概述

LiveData是一個observable資料持有類。與常規observable不同,LiveData是生命週期感知的,這意味著它跟隨其他應用程式元件(如activities, fragments, or services)的生命週期。這種感知能力確保LiveData只更新處於活躍生命週期狀態的應用程式元件。

注意:在Android工程中匯入LiveData元件,請看Adding Components to your Project

LiveData與一個Observer關聯,如果觀察者的生命週期處於

STARTEDRESUMED狀態,則表示觀察者處於活動狀態。LiveData只通知活躍的觀察者做更新。註冊到LiveData物件中的不活躍的觀察者則得不到資料更新的通知。

您可以註冊一個observer並與實現了LifecycleOwner介面的物件配對。這種關係允許當相應的Lifecycle物件的狀態改變為DESTROYED時,觀察者被移除。這對於activities and fragments尤其有用,因為它們可以安全地觀察LiveData物件,而不用擔心洩漏——當activities 和 fragments的生命週期被銷燬時,它們會立即取消訂閱。

有關如何使用LiveData的更多資訊,請參見

使用LiveData物件

1.1 使用LiveData的優點

使用LiveData具有以下優點:

  1. 確保UI與資料狀態匹配

LiveData遵循觀察者模式。當生命週期狀態改變時,LiveData通知Observer物件。您可以合併程式碼來更新這些觀察者物件中的UI。觀察者可以在資料每次有更新時更新UI。

  1. 沒有記憶體洩漏

觀察者繫結到生命週期物件,並在其關聯的生命週期是destroyed時自行清理。

  1. 停止activities造成的crash問題

如果觀察者的生命週期是不活動的,比如在堆疊下面的activity,那麼它接收不到任何LiveData事件。

  1. 不再手動管理生命週期

UI元件僅僅觀察相關資料,不停止或恢復觀察。LiveData自動管理所有這一切,因為在觀察的時候它能感知到相關的生命週期狀態變化。

  1. 始終保持最新資料

如果生命週期變得不活動,則在再次啟用時接收最新資料。例如,後臺中的activity在返回到前臺後立即接收最新資料。

  1. 及時響應配置改變

如果由於配置改變(如裝置旋轉)而重新創activity或fragment,則它立即接收最新的可用資料。

  1. 資源共享

您可以使用Sigelon模式整合LiveData物件來包裝系統服務,以便它們可以在您的應用程式中共享。一旦LiveData物件連線到系統服務,然後需要用到該資源的任何observer都可以觀察到LiveData物件。有關更多資訊,請參見擴充套件LiveData

1.2 使用LiveData物件

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

建立一個LiveData例項來儲存某種型別的資料。這通常是在ViewModel類中完成的。

建立一個Observer物件,該物件定義onChanged()方法,該方法響應LiveData物件中資料更改時發生的變化。通常在UI控制器中建立一個Observer物件,例如activity或fragment。

使用observe()方法將觀察者物件與LiveData物件關聯到一塊。observe()方法使用LifecycleOwner物件。這將Observer物件向LiveData物件訂閱,以便通知其更改。通常在UI控制器中新增Observer物件,例如activity 或者 fragment。

注意:您可以使用observeForever(Observer)方法註冊一個沒有關聯LifecycleOwner物件的觀察者。在這種情況下,觀察者被認為總是活躍的,因此總是被通知更新。您可以通過呼叫removeObserver(Observer)方法刪除這些觀察者。

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

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

1.2.1 建立LiveData物件

LiveData是一種可以與任何資料一起使用的包裝器,包括實現Collections的物件,如ListLiveData物件通常儲存在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物件中的資料沒有設定。

注意:請確保將更新UI的LiveData物件儲存在ViewModel物件中,而不是activity 或者 fragment中,原因如下:

  • 避免臃腫的activities和fragments。現在這些UI控制器負責顯示資料,但不儲存資料狀態。

  • 將LiveData例項與特定activity 或者 fragment例項解耦, 並允許LiveData物件在配置更改時存活。

您可以ViewModel嚮導中閱讀更多關於ViewModel類的優點和用法。

1.2.2 觀察LiveData物件

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

以確保系統不從activity或fragment的onResume()方法中進行多餘呼叫。

以確保activity或fragment具有可在其活躍後立即顯示的資料。一旦應用程式元件處於STARTED狀態,它就會從正在觀察的LiveData物件接收最新的值。只有在LiveData物件被設定為可觀察狀態時才會發生。

通常,LiveData只在資料更改時才提供更新,並且只對活動的observers提供更新。這種行為的一個例外是,觀察者在從非活動狀態轉變為活動狀態時也會收到更新。此外,如果觀察者第二次從非活動狀態改變為活動狀態,它僅接收到一個更新,如果自上一次變為活動狀態的值發生了改變。

下面的示例程式碼說明如何開始觀察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()。

1.2.3 更新LiveData物件

LiveData沒有公開可用的方法來更新儲存的資料。MutableLiveData類公開了setValue(T)postValue(T)方法,如果需要編輯LiveData物件中儲存的值,則必須使用這些方法。通常在ViewModel中使用MutableLiveData,然後ViewModel只向observers公開不可變的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物件。

1.2.4 在Room中使用LiveData

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

當資料庫被更新時,Room生成所有必要的程式碼來更新LiveData物件。生成的程式碼在需要時非同步地在後臺執行緒上執行查詢。此模式對於保持UI中顯示的資料與儲存在資料庫中的資料同步是有用的。您可以閱讀更多關於Room和DAOs在Room持久庫指南。

1.3 繼承LiveData

如果觀察者的生命週期處於STARTED或RESUMED狀態,則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);
    }
}

本例中價格listener的實現包括以下重要方法:

當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例項的fragment作為第一個引數傳遞。這樣做意味著這個觀察者被繫結到與所有者相關聯的Lifecycle物件,意思是:

  • 如果生命週期物件不處於活動狀態,則即使值改變,也不會呼叫observer。

  • 在生命週期物件被銷燬後,觀察者將被自動移除。

LiveData物件是生命週期感知的,這意味著您可以在多個activities, fragments, 和 services之間共享它們。為了保持示例簡單,可以將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(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}

多個fragments和activities可以觀察MyPriceListener例項。LiveData只連線系統服務,如果其中一個或多個系統可見和啟用。

1.4 LiveData變換

在將LiveData物件分發給觀察者之前,您可能需要對儲存在LiveData物件中的值進行更改,或者您可能需要基於另一個LiveData物件的值返回不同的LiveData例項。Lifecycle包提供轉換類,其中包含支援這些方案的幫助方法。

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

可以使用transformation方法在觀察者的生命週期中傳遞資訊。除非觀察者正在觀看返回的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()方法的另一個呼叫,而不是使用前一個呼叫的結果。

相反,您可以實現postalcode 查詢作為地址輸入的轉換,如下面的示例所示:

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的轉換,這意味著在addressInput更改時呼叫repository.getPostCode()方法。如果存在活動觀察者,則這是正確的,如果在呼叫repository.getPostCode()時沒有活動觀察者,則在新增觀察者之前不進行任何計算。

該機制允許應用程式的較低級別建立按需求計算的LiveData物件。ViewModel物件可以很容易地獲得對LiveData物件的引用,然後在它們上面定義轉換規則。

1.4.1 建立新的轉換

在你的應用程式中有十幾種不同的特定轉換,但預設情況下它們不被提供。為了實現您自己的轉換,您可以使用MediatorLiveData類,該類偵聽其他LiveData物件並處理它們發出的事件。MediatorLiveData正確地將其狀態傳播到Source LiveData物件。若要了解此模式的更多資訊,請參見Transformations的參考文件。

1.5 合併多個LiveData資料來源

MediatorLiveData是LiveData的子類,允許您合併多個LiveData源。然後,每當原始LiveData源物件改變時,就會觸發MediatorLiveData物件的觀察者。

例如,如果在UI中有一個可以從本地資料庫或網路更新的LiveData物件,則可以向MediatorLiveData物件新增以下源:

  • 與資料庫中儲存的資料相關聯的LiveData物件。

  • 與從網路訪問的資料相關聯的LiveData物件。

您的activity只需要觀察MediatorLiveData物件以從兩個源接收更新。有關詳細示例,請參閱附錄:App體系結構指南公開網路狀態部分

1.6 額外資源

LiveData是一個Android Jetpack架構元件。在Sunflower demo應用程式中使用它。

有關使用LiveDataSnackbar訊息、navigation事件和其他事件有關的資訊,請閱讀此帖子