Jetpack —— LiveData,可观察的数据持有者

LiveData 是一个可观察的数据持有类,它和常规的 Observable 不同,LiveData 可以感知生命周期,这就意味着它会涉及 APP 其他组件的生命周期,比如 Activity、Fragment 或者 Service。这种涉及可以确保 LiveData 只会更新处于活动生命周期状态的 APP 组件 Observers。

LiveData 认为,如果观察者的生命周期处于 STARTEDRESUME 状态,则该观察者 (以 Observer 类呈现) 处于活动状态。LiveData 只通知活动状态的 observers 进行更新。注册到 LiveData 的非活动状态的 observers 不会接收到改变通知。

你可以注册一个观察者,该观察者需要和实现了 LifecycleOwner 接口的对象配对。这种关系允许在 Lifecycle 对象进入 DESTROYED 的时候删除相应的 Observer。这对 Activity 和 Fragment 很有帮助,因为可以安全的观察 LiveData 对象,并且无须担心内存泄漏,因为 Activity 和 Fragment 在销毁的时候,它们会立即被取消订阅。

想要在项目中引入 LiveData,需要引入依赖:

allprojects {
    repositories {
        google()
        jcenter()
    }
}

LiveData 的好处

使用 LiveData 有以下好处:

  • 确保您的 UI 与您的数据状态相匹配

    LiveData 遵循观察者模式。当生命周期状态改变时,LiveData 通知 Observer 对象。你可以巩固你的代码在这些 observer 对象中更新 UI。Observer 在每次有变化是更新 UI,而不只是在每次 app 数据变化时更新 UI。

  • 没有内存泄漏

    Observer 绑定到 Lifecycle 对象,并在其关联的生命周期销毁后进行清理。

  • 没有因为 activities 停止导致的崩溃

    如果 Observer 的生命周期处于非活动状态,例如在后台堆栈中的活动的情况下,则它不会接收任何 LiveData 事件。

  • 无需手动的生命周期处理

    UI 组件只是观察相关数据,不会停止或恢复观察。LiveData 会自动管理所有这些,因为它在观察时了解相关的生命周期状态更改。

  • 始终是最新的数据

    如果生命周期变为非活动状态,它将在再次处于活动状态时接收最新数据。例如,后台的活动在返回到前台后立即接收最新数据。

  • 正确的配置更改

    如果由于配置更改,比如设备旋转,而重新创建 activity 或 fragment,它将立即接收最新的可用数据。

  • 共享资源

    您可以使用单例模式扩展 LiveData 对象来包装系统服务,以便在 app 中共享这些服务。LiveData 对象连接到系统服务一次,然后任何需要资源的观察者都可以只监听 LiveData 对象。

使用 LiveData 对象

跟随以下几步来使用 LiveData 对象:

  1. 创建一个 LiveData 的实例来持有某个类型的数据。这通常是在你的 ViewModel 类中做的。
  2. 创建一个定义了 onChanged() 方法的 Observer 对象,它会在 LiveData 对象持有的数据发生改变时进行控制处理。你通常要在 UI controller 中创建 Observer 对象,比如在 activity 或者 fragment。
  3. 使用 [observe()](https://developer.android.com/reference/android/arch/lifecycle/LiveData.html#observe(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.Observer)) 方法来把 Observer 对象 attach 到 LiveData 对象上。observe() 方法接收一个 LifecycleOwner 对象。这将 Observer 对象订阅到 LiveData 对象上,以便它收到改变的通知。你通常会 attach Observer 到 UI Controller 上,比如 activity 或者 fragment。

你可以使用 observeForever(Observer) 方法以不关联一个 LifecycleOwner 对象的方式来注册一个 observer。在这种情况下,observer 被认为始终处于活动状态,因此始终会收到有关改变的通知。你可以使用 removeObserver(Observer) 方法来移除这些 observer 的调用。

当你更新存储在 LiveData 对象的值的时候,它会触发所有注册的 observers 只要关联的 LifecycleOwner的状态是 active 的。

LiveData 允许 UI Controller 的 observer 去订阅更新。当持有数据的 LiveData 对象更新时,UI 会在响应中自动更新。

创建 LiveData 对象

LiveData 是一个可用于任何数据的一个包装,包括实现了 Collections 的对象,比如 List。一个 LiveData 对象通常存储在 ViewModel 对象中,并且通过 getter 方法访问,就像下面演示的那样:

public class NameViewModel extends ViewModel {

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

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

// Rest of the ViewModel...
}

最初,LiveData 对象中的数据没有被设置。

注意:确保存储的更新 UI 的 LiveData 对象是在 ViewModel 对象中,而不是 activity 或者 fragment,原因如下:

  • 避免臃肿的 activities 和 fragments。现在这些 UI Controllers 是负责展示数据,而不持有数据的状态。
  • 要从特定的 activity 或者 fragment 实例中解耦 LiveData 实例,并且允许 LiveData 对象在配置更改后继续生存。

你可以在 ViewModel 指南 中了解更多关于 ViewModel 类型使用的好处。

观察 LiveData 对象

在大多情况下,一个 app 组件的 onCreate() 方法是开始观察 LiveData 对象比较正确的地方,愿意如下:

  • 可以确保系统不会在 activity 或者 fragment 的 onResume() 有过多的调用。
  • 可以确保 activity 或者 fragment 状态变为 active 时有数据可以尽快显示出来。一旦一个 app 组件处于 STARTED,它会从它观察中的 LiveData 对象接收到最近的数据。这只会在观察了 LiveData时才会发生这种情况。

通常情况下,LiveData 仅在数据更改时提供更新,并且仅向 active 状态的 observer 提供更新。这个行为的一个例外是,observer 在从 inactive 状态更改为 active 状态时也会收到更新。此外,observer 第二次从 inactive 到 active 状态,只有自从上次变成 active 为止数据改变了,它才会接收到更新。

下面的例子说明了如何开始观察一个 LiveData 对象:

public class NameActivity extends AppCompatActivity {

    private NameViewModel model;

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

        // Other code to setup the activity...

        // Get the ViewModel.
        model = 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.
                nameTextView.setText(newName);
            }
        };

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

nameObserver 作为参数传入的 [observe()](https://developer.android.com/reference/android/arch/lifecycle/LiveData.html#observe(android.arch.lifecycle.LifecycleOwner,
android.arch.lifecycle.Observer)) 被调用之后,onChanged() 立刻就会被调用并提供存储在 mCurrentName 中最近的值。如果 LiveData 对象 mCurrentName 中没有被设置值,则 onChanged() 不会被调用。

更新 LiveData 对象

LiveData 没有公开可用的方法用来存储更新存储的数据。MutableLiveData 类暴露公开了 setValue(T)postValue(T) 方法,如果你需要编辑存储在 LiveData 对象中的值,你必须要使用它们。通常,MutableLiveData 被在 ViewModel 中被使用,ViewModel 只暴露不可编辑的 LiveData 对象给 observers。

当你设置了 observer 关系之后,你可以更新 LiveData 对象中的值,就如下面例子展示的,当用户按下按钮时触发所有 observers:

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

在例子中调用 setValue(T),observers 调用它们的 onChanged() 方法,值为 John Doe。例子展示了一个按钮被按下,但由于各种原因,可以调用 setValue()postValue() 来更新 mName,包括在网络请求的 response 中,或者数据库加载完成;在所有情况中,调用 setValue()postValue() 会触发 observers 并更新 UI。

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

结合 Room 使用 LiveData

持久化库 Room 支持 observable 查询,它返回 LiveData 对象。Observable 查询被作为数据访问对象(DAO)的一部分被编写。

Room 生成所有必要的代码来在数据库更新时更新 LiveData 对象。如果需要,被生成的代码在后台线程运行异步查询。这个模式可用于使 UI 中显示的数据与存储在数据库中的数据保持同步。你可以在 Room 持久化库指南了解更多关于 Room 和 DAOs。

扩展 LiveData

如果 observer 的生命周期是 STARTED 或者 RESUMED 状态,LiveData 则认为 observer 处于 active 状态,下面的代码展示了如何扩展继承 LiveData 类:

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

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

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

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

在上述例子的 price listener 的实现中包含了以下几个重要的方法:

  • onActive() 方法在 LiveData 对象有一个 active observer 的时候被调用。这意味着你需要在这个方法中开始观察 stock price 的更新。
  • onInactive() 方法在 LiveData 对象没有任何一个 active observer 的时候被调用。因为没有 observers 正在监听,所以没有理由与 StockManager 服务保持连接。
  • setValue(T) 方法更新 LiveData 实例的值,并且通知任何 active 状态的 observers 关于这次改变。

你可以如下使用 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()](https://developer.android.com/reference/android/arch/lifecycle/LiveData.html#observe(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.Observer)) 方法传入 fragment 作为第一个参数,它是 LifecycleOwner 的一个实例。这样做表示此 observer 绑定到与所有者关联的 Lifecycle 对象,意味着:

  • 如果 Lifecycle 对象不处于 active 状态了,那么就算值改变了 observer 也不会回调。
  • Lifecycle 对象被销毁了,observer 会自动被移除。

LiveData 对象是生命周期感知的事实,这意味着你可以在多个 activity,fragment,service 之间共享它们。为了保持例子简单,你可以把 LiveData 类作为一个单例实现,如下:

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

    private SimplePriceListener listener = 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) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

然后,你可以在 fragment 中使用它,如下:

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

多个 fragments 和 activities 可以观察这个 MyPriceListener 实例。LiveData 只会在它们中的一个或多个是可见和 active 的才会连接到系统服务。

转换 LiveData

储存在 LiveData 的数据在派发给 observers 之前,你可能需要做一些改变,或者你可能需要基于另一个值返回一个不同的 LiveData 实例。Lifecycle 包提供了 Transformations 类,它包含了一些帮助的方法来支持这些场景。

[Transformations.map()](https://developer.android.com/reference/android/arch/lifecycle/Transformations.html#map(android.arch.lifecycle.LiveData, android.arch.core.util.Function))

对存储在 LiveData 对象中的值执行函数,并将结果发送到下游。

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

[Transformations.switchMap()](https://developer.android.google.cn/reference/androidx/lifecycle/Transformations.html#switchMap(android.arch.lifecycle.LiveData, android.arch.core.util.Function>))

类似于 map(),对存储在 LiveData 对象中的值执行函数,并在下游展开和调度结果。传入 switchMap() 的函数必须返回一个 LiveData 对象,如下面例子所示:

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

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

你可以使用转换方法在 observer 的整个生命周期中传输信息。transformations 不会被计算,除非一个 observer 正在监听返回的 LiveData 对象。因为 transformations 是懒计算的,与生命周期相关的行为被隐式传递,而不需要额外的显式调用或依赖。

如果你觉得你在 ViewModel 对象中需要一个 Lifecycle 对象,transformation 是一个更好的解决方案。举个例子,假设你有一个 UI 组件,它接收一个 address 并返回一个 该 address 的 postal code。你可以针对该组件实现单纯的 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);
    }
}

那么当每次调用 getPostalCode()时,UI 组件需要从上一个 LiveData 对象反注册,并注册到新的实例。另外,如果 UI component 被重建,它会触发另一个 repository.getPostCode() 调用而不是使用之前调用的结果。

对此你实现 postal code 查找作为 address 输入的转换,如下所示:

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 field 是 publicfinal 的,因为这个 field 永远不会改变。postalCode 这个 field 被定义为 addressInput 的一个转换,这意味着 repository.getPostCode()addressInput 改变时被调用。在 repository.getPostCode() 被调用的时候,如果有一个 active 的 observer 则是真的,如果没有 active 的 observer,在添加 observer 之前,不进行任何计算。

这个机制允许低级别的 app 去创建按需懒计算的 LiveData 对象。一个 ViewModel 对象可以轻松地获取 LiveData 对象的引用,然后在其基础上定义转换规则。

创建新的转换

在你的应用中可能有十几个不同且有用的特定转换,但默认情况下是不提供的。要实现你自己的 transformation 你可以使用 MediatorLiveData 类,它监听其它 LiveData 对象并处理它们发出的事件。MediatorLiveData 正确地将其状态传送到源 LiveData 对象。学习关于这个模式的详情,查看 Transformations 类的参考文档。

合并多个 LiveData 源

MediatorLiveDataLiveData 的一个子类,它允许你去合并多个 LiveData 源。 每当任何原始 LiveData 源对象更改时,MediatorLiveData 对象的 observers 会被触发。

举个例子,如果你在你的 UI 有一个 LiveData 对象,它可以从本地数据库和网络中更新,然后你可以增加下面几个源到 MediatorLiveData 对象:

  • 一个 LiveData 对象,与存储在数据库中的数据相关联。
  • 一个 LiveData 对象,于访问网络的数据相关联。

你的 activity 只需要去观察 MediatorLiveData 对象来从上面两种源中接收更新。有详细示例,见 App Architecture 指南附录: 公开网络状态 栏。

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1