[翻译] Android Architecture 之 LiveData

LiveData是一个可观察的数据持有类,不像通常的可观察者,LiveData是可感知生命周期的,这意味着LiveData只更新处于活动生命周期状态的组件中的观察者。

LiveData更新的话只会通知处于活动状态下的观察者,对于注册的非活动状态下的观察者,将不会收到更新的通知。

使用LiveData的优点

确保你的用户界面匹配你的数据状态
LiveData遵循观察者模式,当生命周期状态改变的时候,LiveData通知所有的观察者对象,你可以合并代码以更新这些Observer对象中的UI,每当应用程序数据发生更改时,不必每次更新UI,观察者可以在每次得到状态更改时更新UI。

不会导致内存溢出
观察者绑定到生命周期对象,并在其相关的生命周期被销毁的时候自动清理。

不会因停止activities而导致崩溃
如果观察者的生命周期处于非活动状态,例如,在后退栈中的Activity,则不会收到任何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)中。

创建LiveData对象

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对象

在通常情况下,会在onCreate方法中开始观察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);
    }
});

注意:必须在UI线程调用setValue方法来更新LiveData,如果你的代码执行在工作线程,那么使用postValue来更新LiveData对象。

与Room一起使用LiveData

Room持久性库支持可观察查询,并返回LiveData对象,可观察查询是作为数据库访问对象的一部分写入的。
当更新数据库时,会生成所有必要的代码来更新LiveData对象。生成的代码在、需要时在后台线程上异步运行查询。这种模式对于保持用户界面中显示的数据与存储在数据库中的数据同步很有用。

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

上面的实现包含了几个重要的方法:

  • 当LiveData对象具有活跃的观察者时,将调用onActive方法
  • 当LiveData对象没有任何活动观察者时,将调用onInactive()方法。
  • setValue方法用来更新LiveData实例中的值并且会通知其他活跃的观察者数据的更新。

你可以按如下所示使用StockLiveData类:

public class MyFragment extends Fragment{
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {

        });
    }
}

observe()方法将会把fragment当做参数传递进去,因为Fragment是LifecycleOwner的实例,这表示观察者被绑定到生命周期对象,意味着:

  1. 如果Lifecycle对象如果处于非活跃状态,那么即使数据更新Observer也不会被调用的。
  2. 当Lifecycle对象呗销毁之后,观察者也会自动被移除。

事实是,LiveData是可感知生命周期的,意味着你可以在多个activity,fragment之间共享他们,为了保持示例的简介,你可以使用如下方式将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.
        });
    }
}

多个Fragment和Activity可以观察MyPriceListener实例

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

在这个示例中postCode字段是public和final的,因为字段不能被更改,postCode字段被定义为addressInput的转换之后的,意味着addressInput发生更改时将调用repository.getPostCode()方法。
该机制允许较低级别的应用程序创建按需延迟计算的LiveData对象,ViewModel对象可以轻松获得对LiveData对象的引用,然后在其上定义转换规则。

创建新的转换

有十几种不同的特定转换可能在你的应用中很有用,但是他们不是默认提供的。要实现自己的转换,你可以使用MediatorLiveData类,它监听LiveData对象并处理他们发出的事件。MediatorLiveData将其状态正确的传播到源LiveData对象。

合并多个LiveData源

MediatorLiveData是LiveData的子类,允许你合并多个LiveData源,MediatorLiveData对象的观察者会在任何原始LiveData对象更改时触发。
例如,如果你的UI中有一个可从本地数据库或网络更新的LiveData对象,则可以将以下源添加到MediatorLiveData对象:

  • 一个LiveData对象与存储早数据库中的数据关联
  • 一个LiveData对象与从网络加载的数据关联
    你的activity只需要观察MediatorLiveData对象即可从两个源来接收更新。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容