LiveData

示例应用程序

LiveData是一个可观察的数据持有者类。与常规的可观察性不同,LiveData是生命周期感知的,意味着它遵循其他应用程序组件(例如活动,片段或服务)的生命周期。这种意识确保LiveData只更新处于活动生命周期状态的应用程序组件观察者。

注意:要将LiveData组件导入到Android项目中,请参阅向项目添加组件

如果生命周期处于STARTEDRESUMED 状态,LiveData认为观察者(Observer类表示)处于活动状态。LiveData仅通知活跃的观察员作出更新。注册观察LiveData对象的非活动观察者不会收到有关更改的通知。

您可以给实现LifecycleOwner 接口的对象注册配对的观察者。这种关系允许观察者在相应被观察对象的Lifecycle状态改变为DESTROYED时被移除。这对于activityfragmetn特别有用,因为它们可以安全地观察LiveData对象,而不用担心泄漏 。 activityfragment在其生命周期被销毁时会立即取消对LiveData的订阅。

有关如何使用LiveData的更多信息,请参阅使用LiveData对象

1. 使用LiveData的优点

使用LiveData提供以下优点:

  • 保证用户界面与数据状态匹配
    LiveData遵循观察者模式。在生命周期状态更改时LiveData会通知Observer对象。您可以升级代码在observer对象中更新UI,不必每次数据改变时手动更新UI,观察者可以在每次更改时更新UI。

  • 没有内存泄漏
    观察者与Lifecycle对象绑定,并在具有生命周期的对象被destroyed后自行清理。

  • 停止activities不会导致崩溃
    如果观察者的生命周期处于非活动状态,例如在后退堆栈中的活动,则不会收到任何LiveData事件。

  • 无需手动处理生命周期
    UI组件只是观察相关数据,不会停止或恢复观察。LiveData自动管理所有这些操作,因为在观察时它实时响应相关的生命周期状态的变化。

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

  • 正确的配置更改
    如果由于配置更改(如设备旋转)而重新创建活动或片段,则会立即收到最新的可用数据。

  • 共享资源
    可以使用单例模式来扩展LiveData对象来包装系统服务,以便它们可以在应用程序中共享。LiveData对象一旦连接到系统服务,然后其他任何需要系统资源的观察者只需要观看该LiveData 对象就可以。
    有关更多信息,请参阅扩展LiveData


2. 使用LiveData对象

按照以下步骤操作 LiveData对象:

  • 1.创建一个LiveData实例来保存某种类型的数据。这通常在你的ViewModel类中完成。
  • 2.创建一个定义了onChanged() 方法的Observer 对象,该方法在LiveData对象所保存的数据更改时作出相应的变化。您通常需要在UI控制器(如activity or fragment)中创建一个Observer对象。
  • 3.使用Observer方法将observe()对象附加到LiveData对象中。该observe()方法持有一个 LifecycleOwne对象。Observer订阅到LiveData,以便通知更改。通常将Observer对象添加到如活动或片段等UI控制器中。

注意: 您可以使用observeForever(Observer) 方法注册一个observer在不使用LifecycleOwner的情况下。在这种情况下,观察者被认为始终处于活动状态,因此总是通知修改。你可以删除这些观察者调用removeObserver(Observer) 方法。

当您在LiveData对象更新存储中的值时,只要被添加的LifecycleOwner处于活动状态,就会触发所有注册的观察者
LiveData允许UI控制器观察者订阅更新。当LiveData对象所持有的数据发生变化时,UI会自动更新。

2.1创建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对象中的数据未设置。

注意:由于以下原因,请确保在更新UI的ViewModel对象中存储LiveData,而不是在 activity或者 fragment 中:
1.避免臃肿的activityfragment。UI控制器负责显示数据但不保存数据状态。
2.将LiveData实例从特定activityfragment实例中分离出来,并允许LiveData对象在配置更改后继续运行。

您可以阅读更多关于ViewModel类的好处和用法在ViewModel指南中

2.2观察LiveData对象

在大多数情况下,出于以下原因应用程序组件的onCreate()方法是开始观察LiveData对象的正确位置:

  • 确保系统不会从活动或片段的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值则不会调用。

2.3更新LiveData对象

LiveData没有公开的方法来更新存储的数据。但是MutableLiveData类公开了 setValue(T)postValue(T) 方法,如果你需要编辑一个存储在LiveData对象中的值,你必须使用上述两个方法。通常MutableLiveDataViewModel中使用 ,然后 ViewModel只暴露不可变的LiveData对象给观察者的。
建立观察者关系之后,可以更新LiveData对象的值,如以下示例所示,当用户点击按钮时触发所有观察者:

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

示列中调用setValue(T)的结果是,观察者调用携带了John DoeonChanged() 方法。这个例子表示,一个可点击按钮,但是因为各种原因setValue()或者postValue()可能被调用来更新mName,包括响应网络请求或数据库加载完成的情况。在所有情况下,调用setValue()postValue()都会触发observers并更新UI。

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

2.4与Room一起使用LiveData

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

当更新数据库时,Room会生成所有必要的代码来更新LiveData对象。生成的代码在需要的时候在后台线程上进行异步查询。这种模式对于保持用户界面中显示的数据与存储在数据库中的数据同步很有用。您可以在Room持久性库指南中阅读关于RoomDAO的更多信息


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

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

  • onActive() 方法在LiveData对象具有活的观察者时被调用。这意味着你需要从这个方法开始观察股票价格的更新。
  • onInactive()LiveData对象没有任何活动的观察者时调用该方法。由于没有观察员在监听,所以没有理由保持与StockManager服务的连接。
  • setValue(T) 方法更新LiveData实例的值,并通知所有活跃的观察者有关更改。
    你可以使用这个类如下:
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()方法将fragmetn作为第一个参数。该fragmentLifecycleOwner的实现。这样做意味着这个观察者被绑定到Lifecycle所有者对象上,这意味着:

  • 如果Lifecycle对象不处于活动状态,则即使值发生更改,也不会调用观察者。
  • Lifecycle的对象被destroyed之后,观察者被自动删除。

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(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}

多个fragmentsactivites可以观察MyPriceListener实例,而LiveData只能在它们其中一个或多个可见和活动时,才能连接到系统服务。


4.转换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字段是publicfinal,因为字段永远不会改变。postalCode字段被定义为addressInput的变换,这意味着repository.getPostCode()方法在addressInput变化时被调用。如果存在活跃的观察者,那么这是正确的,如果在repository.getPostCode()被调用时没有活动的观察者,则在添加观察者之前不进行计算。

这种机制允许较低级别的应用程序按需计算延迟创建LiveData对象。ViewModel对象可以很容易地获得LiveData对象的引用,然后在其上定义转换规则。

4.1创建新的转换

有十几个不同的具体转换可能在您的应用程序中很有用,但它们不是默认提供的。为了实现你自己的转换,你可以使用这个MediatorLiveData 类来监听其他LiveData对象,并处理它们发出的事件。MediatorLiveData正确地将其状态传播到源LiveData对象。
要了解更多关于这种模式的信息,请参阅Transformations 的参考文档。


5.合并多个LiveData源

MediatorLiveDataLiveData的子类,它允许您合并多个LiveData源。 每当有任何原始的LiveData源对象改变,就会触发MediatorLiveData对象的观察者。

例如,如果您的UI中有一个可以从本地数据库或网络更新的LiveData对象,则可以将以下源添加到MediatorLiveData 对象:

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

您的acticity只需要观察从两个以上两个源接收更新的MediatorLiveData对象。有关详细示例,请参阅应用程序体系结构指南附录:展示网络状态 部分。

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

推荐阅读更多精彩内容