Android LiveData

LiveData Overview

* 使用 LiveData 的优点

* 使用 LiveData 对象
  * 创建 LiveData 对象
  * 观察 LiveData 对象
  * 更新 LiveData 对象
  * LiveData 与 Room 一起使用

* 扩展 LiveData

* 转换 LiveData
  * 创建新的转换

* 合并多个 LiveData 源

* 其他资源

LiveData 是一个可观察的数据持有者类。与常规 observable 不同,LiveData 是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,例如 activity,fragment 或 service。此感知确保 LiveData 仅更新处于活动状态的应用组件观察者。

注意:要将 LiveData 组件导入 Android 项目,请参阅 Adding Components to your Project

如果 Observer 类表示的观察者生命周期处于 STARTEDRESUMED 状态,则 LiveData 会将其视为活动状态。LiveData 仅通知处于活动状态的观察者更新信息。非活动状态的观察者不会收到有关数据更改的通知。

你可以注册与实现了 LifecycleOwner 接口的对象配对的观察者。此关系允许在相应 Lifecycle 对象的状态更改为 DESTROYED 时删除观察者。这对于 activity 和 fragment 特别有用,因为它们可以安全地观察 LiveData 对象而不用担心泄漏 - activity 和 fragment 在其生命周期被销毁时立即取消订阅。

一、使用 LiveData 的优点

使用 LiveData 具有以下优势:

确保你的 UI 符合你的数据状态

LiveData 遵循观察者模式。生命周期状态更改时,LiveData 会通知 Observer 对象。你的观察者可以在每次数据更改时更新 UI。

没有内存泄漏

观察者绑定到 Lifecycle 对象,并在其相关生命周期被销毁后自行清理。

不会因为 activity 停止而发生崩溃

如果观察者的生命周期处于非活动状态(例如,activity 在后台堆栈中),则它不会接收任何 LiveData 事件。

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

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

始终保持最新数据

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

适当的配置更改

如果由于配置更改(例如设备旋转)而重新创建 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 会自动响应更新。

2.1 创建 LiveData 对象

LiveData 是一个包装器,可以与任何数据一起使用,包括实现集合的对象,例如 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 对象中的数据。

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

  • 避免臃肿的 activity 和 fragment。UI 控制器负责显示数据但不保持数据状态。

  • 将 LiveData 实例与特定 activity 或 fragment 实例分离,并允许 LiveData 对象在配置更改后继续存活。

你可以在 ViewModel 指南中阅读有关 ViewModel 类的优点和用法的更多信息。

2.2 观察 LiveData 对象

在大多数情况下,app 组件的 onCreate() 方法是开始观察 LiveData 对象的正确位置,原因如下:

  • 确保系统不会从 activity 或 fragment 的 onResume() 方法进行冗余调用。

  • 确保 activity 或 fragment 在其变为活动状态时立即显示数据。只要应用程序组件处于 STARTED 状态,它就会从它正在观察的 LiveData 对象中接收最新值。只有在设置了要观察的 LiveData 对象时才会出现这种情况。

通常,LiveData 仅在数据更改时才提供更新,并且要求观察者处于活动状态。此行为的一个例外是观察者从非活动状态更改为活动状态时也会收到更新。此外,如果观察者第二次从非活动状态更改为活动状态,则只有在自上次活动状态以来该值发生更改时才会收到更新。

以下示例代码说明了如何开始观察 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() 之后,立即调用 onChanged(),提供存储在 mCurrentName 中的最新值。如果 LiveData 对象未在 mCurrentName 中设置值,则不会调用 onChanged()

2.3 更新 LiveData 对象

LiveData 没有公开的方法来更新存储的数据。MutableLiveData 类公开 setValue(T)postValue(T) 方法,如果需要编辑存储在 LiveData 对象中的值,则必须使用这些方法。通常在 ViewModel 中使用 MutableLiveData,然后 ViewModel 仅向观察者公开不可变的 LiveData 对象。

设置观察者关系后,可以更新 LiveData 对象的值,如以下示例所示,当用户点击按钮时触发所有观察者:

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

在示例中调用 setValue(T) 会导致观察者使用值 John Doe 调用其 onChanged() 方法。该示例显示按下按钮,调用 setValue()postValue() 来更新 mName,更新的原因可以有多种,包括响应网络请求或数据库加载完成;在所有情况下,对 setValue()postValue() 的调用都会触发观察者并更新 UI。

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

2.4 LiveData 与 Room 一起使用

Room 持久性库支持可观察的查询,这些查询返回 LiveData 对象。可观察查询作为数据库访问对象 (DAO) 的一部分写入。

在更新数据库时,Room 会生成更新 LiveData 对象所需的所有代码。生成的代码在需要时在后台线程上异步运行查询。此模式对于使 UI 中显示的数据与存储在数据库中的数据保持同步非常有用。你可以在 Room 持久性库指南中阅读有关 Room 和 DAO 的更多信息。

三、扩展 LiveData

如果观察者的生命周期处于 STARTEDRESUMED 状态,LiveData 会将观察者视为处于活动状态。以下示例代码说明了如何扩展 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);
    }
}

此示例中价格监听器的实现包括以下重要方法:

  • 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() 方法将 fragment(LifecycleOwner 的一个实例)作为第一个参数传递。这样做表示此观察者绑定到与所有者关联的 Lifecycle 对象,这意味着:

  • 如果 Lifecycle 对象未处于活动状态,则即使值发生更改,也不会调用观察者。

  • 销毁 Lifecycle 对象后,会自动删除观察者。

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

多个 fragment 和 activity 可以观察 MyPriceListener 实例。LiveData 仅在一个或多个 fragment 和 activity 可见且处于活动状态时才连接到系统服务。

四、转换 LiveData

你可能希望在将其分配给观察者之前更改存储在 LiveData 对象中的值,或者你可能需要根据另一个实例返回另一个 LiveData 实例的值。Lifecycle 包提供 Transformations 类,其中包括支持这些场景的帮助方法。

对存储在 LiveData 对象中的值应用函数,并往后传递结果。

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

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 字段被定义为 addressInput 的转换。只要你的应用程序具有与 postalCode 字段关联的处于活动状态的观察者,只要 addressInput 更改,就会重新计算并检索字段的值。

此机制允许较低级别的应用程序创建按需延迟计算的 LiveData 对象。ViewModel 对象可以轻松获取对 LiveData 对象的引用,然后在它们之上定义转换规则。

4.1 创建新的转换

有十几种不同的特定转换可能对你的应用有用,但默认情况下不提供。 要实现自己的转换,可以使用 MediatorLiveData 类,该类侦听其他 LiveData 对象并处理它们发出的事件。MediatorLiveData 正确地将其状态传递到源 LiveData 对象。 要了解有关此模式的更多信息,请参阅 Transformations 类的参考文档。

五、合并多个 LiveData 源

MediatorLiveDataLiveData 的子类,允许你合并多个 LiveData 源。只要任何原始 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。

例如,如果你的 UI 中的 LiveData 对象,可以从本地数据库或网络进行更新,那么你可以添加以下来源的 MediatorLiveData 对象:

  • 与存储在数据库中的数据关联的 LiveData 对象。

  • 与从网络访问的数据关联的 LiveData 对象。

你的 activity 只需要观察 MediatorLiveData 对象以从两个源接收更新。有关详细示例,请参阅 Guide to App Architecture 中的 Addendum: exposing network status

六、其他资源

LiveData 用于 Sunflower 演示应用程序。

另请参阅架构组件 BasicSample

有关将 LiveData 与 Snackbar 消息,导航事件和其他事件一起使用的其他信息,请阅读此文章

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

推荐阅读更多精彩内容