LiveData学习

Android开发文档翻译

Posted by GL on August 18, 2018

LiveData是一个持有observable data的类。与常规的observable不同,LiveData是生命周期可感知的,这意味着LiveData会关注qitaapp组件(比如activities和fragments和services)的生命周期。LiveData只会在active的生命周期阶段中更新app组件的观察者们。

注意:为了引入LiveData组件到你的Android 项目中,可以看Adding Components to your Project

LiveData认为一个observer(被写为Observer类)在它STARTED或者RESUMED阶段时是处于active阶段的。LiveData之通知或更新激活的observers。注册观察LiveData对象的未激活的观察者不会得到关于更改的通知。

你可以注册一个observer与一个实现了LifecycleOwner接口的object关联。这个关系允许在这个生命周期对象处于destroy阶段时,对应的observer能够被移除。这对activities和fragments是非常有用的,因为他们能安全的观察LiveData对象并且不会担心有关activities和fragments在destroy时立即不被订阅的泄露。

更多的关于怎样使用LiveData的信息,请看Work with LiveData objects

使用LiveData的优势

使用LiveData有如下优势:

确保你的UI与你的数据相匹配

LiveData遵循观察者模式。当生命周期阶段发生改变时,LiveData会通知Observer对象。你可以将你的代码整合到这些观察者对象中更新UI。你的观察者能在它们每次改变时更新UI,而不是每次在app数据改变时更新UI。

没有内存泄漏

观察者们被绑定到生命周期对象上,并且在它们相关联的生命周期destroy时清除它们自身。

在stopped activities时不会crash

如果观察者的生命周期没有被激活(比如一个在back stack中的activity),那么它不会接收到任何LiveData发出的events。

不再需要手动的生命周期处理

UI组件只会观察相关的数据并且不会stop或者resume观察行为。LiveData会自动管理所有这些观察者,因为LiveData可以在观察时意识到相关的生命周期状态变化。

总是最新的数据

如果一个观察者处于生命周期处于未激活时,它会在再次激活时收到新的数据(个人理解有点儿像eventbus的粘性event)。例如,一个在后台的activity会在它重新返回前台之后收到最新的数据。

适当的配置更改

如果一个activity或者fragment由于配置更改被重建(比如设备发生旋转),它会立即接收到最近可获得的数据。

共享资源

你可以扩展一个使用单例模式封装系统services的LiveData对象,这样它们就可以在你的app上被分享。这个LiveData对象只会连接一次系统service,并且之后任何需要资源的观察者可以对这个LiveData对象进行观察。更多的信息,请看Extend LiveData

LiveData对象的使用

使用LiveData对象要遵循下面的步骤:

  1. 创建一个LiveData实例去持有一个确定类型的数据。这个步骤通常在你的ViewModel类中完成。
  2. 创建一个定义了onChanged()方法的Observer对象,它控制了在LiveData对象持有的数据发生改变时应该发生什么样的变化。你通常会创建一个Observer对象在你的UI controller(比如一个activity和fragment)。
  3. 使用observe()方法,将Observer对象绑定到LiveData对象。这个observe()方法要接受一个LifecycleOwner对象。这个observe()方法实现了Observer对象订阅LiveData对象,这样就可以在LiveData对象发生改变时发出通知。通常,你会将Observer对象绑定到UI controller(比如一个activity或者fragment)上。

注意:你可以使用observerForever(Observer)方法注册一个observer,不与LifecycleOwner对象关联。在这种情况下,观察者会一直处于被激活的状态,并且总是会在数据发生改变时被通知。你可以通过调用removeObserver(Observer)方法移除这些观察者。

当你更新存储在LiveData对象中的值时,会触发所有已经注册的并且LifecycleOwner处于激活状态的观察者。

LiveData允许UI controller观察者订阅与更新。当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对象中数据不会被设置。

注意:请确保在ViewModel对象红存储更新UI的LiveData对象,而不是在一个activity或者fragment,因为下面这些原因: (1)为了回避activities和fragments过于臃肿。现在这些UI controllers只负责展示数据而不是保存数据。 (2)为了将LiveData实例从指定的activity或者fragment实例中解耦,并且允许LiveData对象去保存配置更改。

你可以阅读更多的有关ViewModel的好处和用法在ViewModel guide中。

观察LiveData对象

在大多数情况下,关注一个LiveData对象最合适的位置就是在一个app组件的onCreate()方法中。原因如下:

  • 为了确保系统不会从重复的调用一个activity或者fragment的onResume()方法。
  • 为了确保activity或者fragment一旦被激活就展示数据。只要一个app组件在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);
    }
}

在observe()方法被调用后,nameObserver作为参数被传递,onChanged()方法立即被调用并提供一个存储在mCurrentName中最新的值。如果LiveData对象中没有设置mCurrentName值,onChanged()不会被调用。

更新LiveData对象

LiveData没有可获得的public方法去更新存储的值。MultableLiveData类暴露了public的setValue(T)postValue(T)方法,并且如果你需要更改存储在LiveData对象中的值,那么你必须使用这两个方法。通常MultableLiveData在ViewModel中被使用,并且ViewModel只对观察者们暴露了不可变的LiveData对象。

在你设置完观察者关系之后,你可以更新LiveData对象值,正如下面的例子所示,当用户点击了一个按钮,多有的观察者都会受到发送的消息。

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

在这个例子中调用setValue(T)方法,会造成观察者调用它们自己的onChanged()方法(将值设置为John Doe)。虽然这个例子展示了一个按钮被按下,但是setValue()或者postValue()能因为一些原因(包括响应网络请求或者完全加载数据库)被调用去更新mName。在所有情况下,在所有情况下,这个回调函数会setValue()或者postValue()触发观察者并且更新UI。

注意:你必须在主线程中调用setValue(T)方法去更新LiveData对象。如果上述代码执行在工作线程中,你可以使用postValue(T)方法去更新LiveData对象。

配合Room使用LiveData

Room持久性库支持被观察的查询,这些查询返回LiveData对象。Observable要被写为Database Access Object(DAO)。

Room生成了所有的更新LiveData需要的代码,当一个数据库被更新时。当需要的时候,生成的代码在后台运行异步的查询。这种模式对于将数据显示在UI中与存储在数据库中的数据保持同步是很有用的。你可以看更多的有关Room和DAOs的信息在Room persistent library guide

LiveData的拓展

如果观察者的生命周期处于STARTED或者RESUMED阶段时,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);
    }
}

在上面例子中price监听器实现了下面这些重要的方法:

  • onActive()方法在LiveData对象有一个激活的观察者时被调用。这意味着你需要从这个方法开始关注股价的更新。
  • onInactive()方法在LiveData对象没有任何激活的观察者时被调用。因为没有观察者监听,所以没有必要保持连接StockManagerservice。
  • 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.
        });
    }
}
这样做意味着这个观察者被绑定到与所有者相关联的生命周期对象,意思是:

* 如果```Lifecycle```对象不处于一个激活的状态,那么若被观察者的值不改变,则观察者不会被调用。
* 在```Lifecycle```对象在被destroyed之后,观察者会被自动地移除。

```LiveData```对象是生命周期感知的事实意味着你可以在多个activities、fragments和services之间共享它们。为了简化例子,你可以按下面方式实现```LiveData```类作为一个单例:

```java
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.
        });
    }
}

多个fragments和activities能观察MyPriceListener实例。如果它们之中的一个或多个是可见的或激活的,那么LiveData只会连接系统服务。

LiveData的变换

你也许想在分发事件到观察者之前让存储在LiveData对象值改变,或者你也许需要基于另一个值返回一个不同的LiveData实例。Lifecycle包提供了Transformations类,该类包括了支持这些场景的辅助方法。


基于存储在LiveData对象中的值应用一个函数,并且向下传播这个结果值(个人理解与RxJava的map类似)。

```Transformations.switchMap()```

与```map()```方法类似,将一个函数应用于存储在LiveData对象中的值,然后解包并将结果分派到下游(个人理解与RxJava的flatmap类似)。传递进```switchMap()```的这个函数必须返回一个LiveData对象,如下面的例子所示:

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

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

你可以使用转换方法在观察者的生命周期中传递信息。只有观察者正在观察的LiveData对象返回值时,这个转换函数才会被执行。因为这个转换函数是懒加载的,所以生命周期相关的行为在不需要额外的显式调用或依赖关系的情况下隐式地传递下去。

如果你认为你需要一个在ViewModel对象中的Lifecycle对象,那么一个转换函数也许是最好的解决方法。举个例子,假设你有一个用户界面组件,它接受一个地址并返回那个地址的编码。你可以实现这个组件的朴素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);
    }
}