LiveData概观

LiveData是一个可观测的数据保持类。与常规观察不同,LiveData是能感知生命周期的,这意味着它尊重其他应用程序组件(如activity、fragment或service)的生命周期。这种意识确保LiveData只更新处于活跃生命周期状态的应用程序组件观察者。
依赖方式:

dependencies {
    def lifecycle_version = "1.1.1"
    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternately - if using Java8, use the following instead of compiler
    implementation "android.arch.lifecycle:common-java8:$lifecycle_version"
}

kotlin的依赖方式请见:kotlin依赖方式
LiveData关注的观察者是处于活跃状态的,对于观察了数据但是没有处于活跃状态的组件,是不会收到数据更新的。
你可以在实现了LifecycleOwner接口的类中注册一个观察者,这种关系允许你在activity/fragment的生命周期走到Destroy时把观察者移除。
这对于activity和fragment特别有用,因为它们可以安全地观察LiveData对象,并且不担心泄漏,activity和fragment在它们的生命周期被破坏时立即被取消订阅。
空说无凭,我们直接看看源码是怎么回事。

 @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

从以上可以看出,当我们调用observe方法时,会传入一个LifecycleOwner对象,在前一篇文章Lifecycles中我们了解到,activity/fragment在Support Library 26.1.0以及以后的版本已经实现了LifecycleOwner接口,所以这里其实就相当于activity的上下文引用。注意观察第二行代码,当我们获取到当前组件处于destroy状态时,就结束了方法。
我们先不管中间的代码,我们看最后一行,是不是感觉很熟悉,我们的LiveData里的确有一个观察者LifecycleBoundObserver,来观察了activity等组件的生命周期。
我们一步步往下走,这个LifecycleBoundObserver里面又是啥样呢?还是一样,我们从源码一步步分析。

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
            super(observer);
            mOwner = owner;
        }

        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

我们看到LifecycleBoundObserver继承了ObserverWrapper并且实现了GenericLifecycleObserver接口。到这里,我们心里想的是不是每一个强大的工具或者强大的类的产生都不容易,设计到方方面面。废话不多说,我们继续看看google里大神的代码。
我们首先看shouldBeActive()这个方法,它返回的是当前组件是否是活跃状态。
在这里要多说一句,什么是Lifecycle的活跃状态呢?
我们看代码mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED)这个方法告诉我们活跃状态在Lifecycle中是状态至少是STARTED。在Lifecycle中有两个枚举STATE / EVENT。其中event是对应于activity等的生命周期,而state是Lifecycle自己的状态,我们可以看看源码

    /**state你可以看作一个个节点,event就在这些节点的边缘。
     * Lifecycle states. You can consider the states as the nodes in a graph and
     * {@link Event}s as the edges between these nodes.
     */
public enum State {
        /**在这个状态,lifecycle不会发布任何事件。该状态在activity destroy之前发生
         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
         */
        DESTROYED,

        /**
         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
         * the state when it is constructed but has not received
         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
         */
        INITIALIZED,

        /**
         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
         * </ul>
         */
        CREATED,

        /**
         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onStart() onStart} call;
         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
         * </ul>
         */
        STARTED,

        /**
         * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached after {@link android.app.Activity#onResume() onResume} is called.
         */
        RESUMED;

        /**
         * Compares if this State is greater or equal to the given {@code state}.
         *
         * @param state State to compare with
         * @return true if this State is greater or equal to the given {@code state}
         */
        public boolean isAtLeast(@NonNull State state) {
            return compareTo(state) >= 0;
        }

可以看到其中源码的解释,结合isAtLeast(@NonNull State state)方法,你就会很明白,什么是lifecycle中的活跃状态,就是在状态STARTED及之后的状态,我们称为活跃状态。
然后我们在往下分析在
onStateChanged(LifecycleOwner source, Lifecycle.Event event)
方法里看到,当我们获取到Lifecycle的状态处于DESTROYED时就会移除数据观察者。在activeStateChanged(boolean newActive)方法中,当处于活跃状态,数据变化就会push到观察者,从而实时更新UI。
源码大体上就是这样,那我们接下来就关注下LiveData的优点,和怎么使用吧。

使用LiveData的优点

  • 确保UI与数据状态匹配
    LiveData遵循观察者模式。当生命周期状态改变时,你可以在观察者中更新UI,而不是直接在生命周期方法中操作。
  • 没有内存泄漏
    观察者绑定到生命周期对象,并在其关联的生命周期被销毁后自行清理。
  • activity停止后不会造成crash
    观察者的生命周期是不活跃的,例如在后台运行的情况下,它不接收任何LiveData事件。
  • 不再人工生命周期处理
    UI组件只是观察相关数据,不停止或恢复观察。LiveData自动管理所有这一切,因为它会感知、观察到相关的生命周期状态变化。
  • 始终保持最新数据
    因为采用观察者模式,所以我们能实时的知道数据的变化。
    如果生命周期变得不活跃,则在再次激活时接收最新数据。例如,在后台返回到前台后立即接收最新数据。
  • 正确的配置改变
    如果由于配置改变(如设备旋转)而重新创建activity或fragment,则它立即接收最新的可用数据。
  • 资源共享
    你可以使用Sigelon模式扩展LiveData对象来包装系统服务,以便它们可以在你的应用程序中共享。LiveData对象连接到系统服务一次,然后需要该资源的任何观察者都可以只观察LiveData对象。

LiveData使用

LiveData遵循观察者模式。当生命周期状态改变时,LiveData通知观察者对象。这样很容易对代码进行维护,诸多好处这里不在赘述。

  • 1.创建一个LiveData实例来保存某种类型的数据。这通常是在View模型类中完成的(这个类实际上就是我们说的ViewModel)。
  • 2.创建一个Observer观察者对象,该对象定义onChanged()方法,该方法会响应LiveData对象保存的数据更改或变化。通常在UI控制器中创建一个观察对象,例如activity或fragment。
  • 3.使用observe()观察者方法将观察者对象附加到LiveData对象。observe()方法会持有一个LifecycleOwner对象。通常将观察者对象附加在UI控制器中,例如activity或fragment。
    当更新存储在LiveData对象中的值时,只要附加的LifecycleOwner处于活动状态,它就会触发所有注册的观察者。LiveData允许UI控制器里的观察者订阅更新。当LiveData对象保存的数据发生变化时,UI会自动响应更新。
    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...
}

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

  • 避免臃肿的fragment和activity。现在这些UI控制器负责显示数据,但不保存数据状态。
  • 将LiveData实例与特定fragment和activity实例解耦,并允许LiveData对象在配置更改中生存。

另外,数据的观察,最好是在onCreate()方法中进行,因为这样可以避免多次调用,以确保LifecycleOwner在其到达活跃状态后立即显示数据


下面我们看看数据是如何被观察的,我们看代码。

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

当我们调用observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)方法,我们的观察者回掉方法onChanged(@Nullable final String newName)就会收到数据的变化。
那么我们是怎么发布或者更新数据的呢?
LiveData 没有公开可用的方法来更新存储的数据。所以我们会使用继承了LiveData 的子类MutableLiveData类的setValue(T)和postValue(T)方法,如果需要编辑LiveData对象中存储的值,则必须使用这些方法。

/**
 * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
 *
 * @param <T> The type of data hold by this instance
 */
public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

在建立了观察者关系之后,然后可以更新LiveData对象的值,如下面的示例所示,当用户点击按钮时触发所有观察者:

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

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

Transform LiveData

在将LiveData对象分配给观察者之前,你可能需要对存储在LiveData对象中的值进行更改,或者您可能需要基于另一个LiveData对象的值返回不同的LiveData实例。生命周期包提供Transformations
转换类,其中包含支持这些转换的方法。

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

通过转换,我们把user转化成了name和lastName传递下去。
是不是感觉这部分的概念有点熟悉?没错,这里的map()和我们RxJava中的map()操作符很相似,和我们的java中的Stream里的高阶函数map()也很相似。其实他们的转换理念或者说核心思想都一样,都是把对象变成流来操作。

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

和上面的map()方法很像。区别在于传递给switchMap()的函数必须返回LiveData对象。
和LiveData一样,Transformation也可以在观察者的整个生命周期中存在。只有在观察者处于观察LiveData状态时,Transformation才会运算。Transformation是延迟运算的(calculated lazily),而生命周期感知的能力确保不会因为延迟发生任何问题。
这部分代码看不明白?迷迷糊糊的?那我们还是使用终极手段——查看源码中的switchMap()是怎么说的。

/**
     * Creates a LiveData, let's name it {@code swLiveData}, which follows next flow:
     * it reacts on changes of {@code trigger} LiveData, applies the given function to new value of
     * {@code trigger} LiveData and sets resulting LiveData as a "backing" LiveData
     * to {@code swLiveData}.
     * "Backing" LiveData means, that all events emitted by it will retransmitted
     * by {@code swLiveData}.
     * <p>
     * If the given function returns null, then {@code swLiveData} is not "backed" by any other
     * LiveData.
     *
     * <p>
     * The given function {@code func} will be executed on the main thread.
     *
     * <p>
     * Consider the case where you have a LiveData containing a user id. Every time there's a new
     * user id emitted, you want to trigger a request to get the user object corresponding to that
     * id, from a repository that also returns a LiveData.
     * <p>
     * The {@code userIdLiveData} is the trigger and the LiveData returned by the {@code
     * repository.getUserById} is the "backing" LiveData.
     * <p>
     * In a scenario where the repository contains User(1, "Jane") and User(2, "John"), when the
     * userIdLiveData value is set to "1", the {@code switchMap} will call {@code getUser(1)},
     * that will return a LiveData containing the value User(1, "Jane"). So now, the userLiveData
     * will emit User(1, "Jane"). When the user in the repository gets updated to User(1, "Sarah"),
     * the {@code userLiveData} gets automatically notified and will emit User(1, "Sarah").
     * <p>
     * When the {@code setUserId} method is called with userId = "2", the value of the {@code
     * userIdLiveData} changes and automatically triggers a request for getting the user with id
     * "2" from the repository. So, the {@code userLiveData} emits User(2, "John"). The LiveData
     * returned by {@code repository.getUserById(1)} is removed as a source.
     *
     * <pre>
     * MutableLiveData<String> userIdLiveData = ...;
     * LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, id ->
     *     repository.getUserById(id));
     *
     * void setUserId(String userId) {
     *      this.userIdLiveData.setValue(userId);
     * }
     * </pre>
     *
     * @param trigger a {@code LiveData} to listen to
     * @param func    a function which creates "backing" LiveData
     * @param <X>     a type of {@code source} LiveData
     * @param <Y>     a type of resulting LiveData
     */
    @MainThread
    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
            @NonNull final Function<X, LiveData<Y>> func) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(trigger, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = func.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }

嗯。。。好像有点长哈,太长不看。那我就来帮大家翻译翻译。
假如我们有一个User实体类,而在我们的数据仓库中(我们暂时不关心仓库的概念),有这样两个数据,User(1, "Jane")User(2, "John")
我们也有了userIdLiveData,其中我们能拿到需要的用户id。每次发出新的用户id,您都希望触发一个请求,从还返回LiveData的存储库中获取与该id对应的用户对象。(也就是这样一种场景,我们要从api中获取我需要的用户id,然后根据id获取到我需要的用户)
在存储库(假设在网络)包含User(1,“Jane”)和User(2,“John”)的情况下,当userIdLiveData值被设置为“1”时,switchMap将调用getUser(1),它将返回包含值User(1,“Jane”)的LiveData,userLiveData将发出用户(1,“jane”)。当存储库中的用户更新到User(1,“Sarah”)时,userLiveData将自动得到通知,并将发出User(1“Sarah”)。
当使用userId="2"调用setUserId方法时,userIdLiveData的值发生变化,并自动触发从存储库中获取id="2"的用户的请求。因此,userIdLiveData发出用户(2,“john”)。
下面贴上例子:
首先建立我们的ViewModel

public class StudyViewModel extends ViewModel {
 
    private InitDataRepository initDataRepository = new InitDataRepository();
    private LiveData<User>  userLiveData;
    private MutableLiveData<Integer> userId;
    //获取user的方法
    public LiveData<User> getUserLiveData() {
        userLiveData=Transformations.switchMap(userId,
                new Function<Integer, LiveData<User>>() {
                    @Override
                    public MutableLiveData<User> apply(Integer input) {
                        return initDataRepository.getUserById(input);
                    }
                });
        return userLiveData;
    }
    //这里模拟我们动态的设置需要的 user id
    public void setUserId(int require) {
        userId = initDataRepository.getUserId(require);

    }
}

我们把数据的获取方式独立出来,这就是前面多次提到的Repository概念。这个概念的意思就是把获取数据的这部分功能通过一个新的Repository类代理执行。这个Repository类的作用就是获取并提供各种来源的数据(数据库中的数据,网络数据,缓存数据等),并且在来源数据更新时通知数据的获取方ViewModel,这样就大大减少了ViewModel的代码量。

public class InitDataRepository {

    private MutableLiveData<User> users=new MutableLiveData<>();
    private MutableLiveData<Integer> userId=new MutableLiveData<>();

    public MutableLiveData<User> getUserById(int id){
        //我们这里假设通过网络请求我们拿到需要的User
        User user=new User(id,"user_name"+id);
        users.setValue(user);
        return users;
    }

    public MutableLiveData<Integer> getUserId(int require) {
        //我们这里假设通过网络请求,我们拿到id=require;
        userId.setValue(require);
        return userId;
    }

我们在activity中观察我们的数据,当我们点击一次就更新一次UI

public class MainActivity extends AppCompatActivity {

    private StudyViewModel studyViewModel;
    private TextView text;
    private int count=0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text=findViewById(R.id.text);
        studyViewModel = ViewModelProviders.of(this)
                .get(StudyViewModel.class);
        /**
         * 观察数据的变化,及时更新
         */
         //注意,在本例子中必须要先调用这个方法,不然getUserLiveData()会空指针
        studyViewModel.setUserId(count);
        studyViewModel.getUserLiveData().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                text.setText(user.toString());
            }
        });
    }
    public void click(View view) {
        count++;
        studyViewModel.setUserId(count);
    }

合并多个LiveData中的数据

MediatorLiveData
是LiveData的子类,可以通过MediatorLiveData合并多个LiveData来源的数据。同样任意一个来源的LiveData数据发生变化,MediatorLiveData都会通知观察他的对象。说的有点抽象,举个例子。比如UI接收来自本地数据库和网络数据,并更新相应的UI。可以把下面两个LiveData加入到MeidatorLiveData中:

  • 关联数据库的LiveData
  • 关联联网请求的LiveData

相应的UI只需要关注MediatorLiveData就可以在任意数据来源更新时收到通知。

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

推荐阅读更多精彩内容