(五)LiveData

概述
  • 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对象以从两个源接收更新。

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

推荐阅读更多精彩内容

  • 前言: 这是一篇Android官方关于LiveData介绍的翻译,正如名字所展示的,它是对存活数据的一个封装类,把...
    三秋桂子仪同三司阅读 2,571评论 1 14
  • 在Model - build.gradle添加依赖 LiveData是一个可观察的数据持有者类。与常规可观察性不同...
    贝贝beibei96阅读 1,415评论 0 1
  • 示例应用程序 使用LiveData的优点 使用LiveData对象创建LiveData对象观察LiveData对象...
    yyg阅读 5,857评论 5 7
  •   LiveData是一个可被观察的数据持有者类。与常规的Observable不同,LiveData能意识到应用程...
    鹤鹤阅读 47,030评论 6 42
  • 目标 我儿子积极上进,身心健康,努力学习,考上理想的高中。 尽自己的能力帮助他人,让对方心情轻松愉悦。 自己也身心...
    7f207b2df6aa阅读 396评论 0 2