zoukankan      html  css  js  c++  java
  • 感知生命周期的数据 -- LiveData

    感知生命周期的数据 -- LiveData

    零. 前言

    上篇文章《万物基于Lifecycle》 介绍了整个Lifecycle体系的基石,今天这篇文章咱们来看看Jetpack给我们带来的活着的数据——LiveData

    大纲

    • LiveData 是什么?
    • 为什么要用LiveData?
    • How to use LiveData?
    • LiveData的生命感知能力从何而来,是如何与Lifecycle结合的?

    一. LiveData 是什么?

    ​ LiveData 简单来说,就是普通数据对象的一个包装类,这个包装类中帮助你主动管理了数据的版本号,基于观察者模式,让普通的数据对象能够感知所属宿主(Activity、Fragment)的生命周期。这种感知能力就能够保证只有宿主活跃(Resumed、Started)时,数据的观察者才能受到数据变化的消息。

    上面这短短的一段话,却有着重大的意义,举一个case:

    有一个页面需要加载一个列表,我们需要在后台线程去服务器请求对应的数据,请求数据成功并经过解析后post消息通知UI,UI再渲染请求来的数据。等等!假若此时的网络很慢,而刚好用户此时按home键退出了应用,那么在此时UI是不应该被渲染的,而是应该等用户重新进入应用时才开始渲染。

    从下面的演示代码就诠释了上面的说所的case

    class MainActivity extends AppcompactActivity{
        public void onCreate(Bundle bundle){
      
         Handler handler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                 //无论页面可见不可见,都会去执行页面刷新,IO。更有甚者弹出对话框
            }
          };
         //1.无论当前页面是否可见,这条消息都会被分发。----消耗资源
         //2.无论当前宿主是否还存活,这条消息都会被分发。---内存泄漏
        handler.sendMessage(msg)
    
        
        liveData.observer(this,new Observer<User>){
             void onChanged(User user){
               
             }
         }
        //1.减少资源占用---          页面不可见时不会派发消息
        //2.确保页面始终保持最新状态---页面可见时,会立刻派发最新的一条消息给所有观察者--保证页面最新状态
        //3.不再需要手动处理生命周期---避免NPE
        //4.可以打造一款不用反注册,不会内存泄漏的消息总线---取代eventbus
        liveData.postValue(data);
      }
    }
    

    有人说,我可以在处理消息时,根据当前页面时是否可见来具体处理对应逻辑。是的,没错,确实可以这样,就像下面这样。

     Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
             if (isActivityValid()) {
             	// updateUI...
             } else {
             	// dosomething...
             }
        }
      };
    

    我再拿上面的例子说一下这种问题:

    1. 需要自行判断宿主活跃状态,防止生命周期越界。
    2. 如果Activity不可见,此时不更新UI,那么就需要复写onResume方法去更新UI,手工管理生命周期,增加了代码的复杂性。

    二. 为什么要使用LiveData?

    上面的例子已经很好地诠释了LiveData的强大,当然不仅限于此,它的优势如下:

    1. 确保界面符合数据状态

      LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象。观察者可以在onChanged事件时更新界面,而不是在每次数据发生更改时立即更新界面。

    2. 不会发生内存泄漏

      观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。

    3. 不会因 Activity 停止而导致崩溃

      如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。

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

      界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

    5. 数据始终保持最新状态

      如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

    6. 适当的配置更改

      如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

    7. 共享资源

      可以使用单一实例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象

    8. 支持黏性事件的分发
      即先发送一条数据,后注册一个观察者,默认是能够收到之前发送的那条数据的

    三. How to use LiveData ?

    step1: 添加依赖:

    step2: 在ViewModel中创建 LiveData 实例 (ViewModel组件会在下期讲到,将LiveData存储至viewModel中,是为了符合MVVM架构思想,V层仅负责展示,而VM层负责数据逻辑)

    public class ViewModelTest extends AndroidViewModel {
    	public final  MutableLiveData<String> name = new MutableLiveData<>();
    	...
    }
    

    step3: 在Activity或Fragment中对LiveData进行添加观察者进行监听

    public class ActivityTest extends AppCompatActivity {
    	...
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            viewModel.name.observe(this, new Observer<String>() {
                @Override
                public void onChanged(@Nullable String name) {
                    // dosomething...
                }
            });
        }
    }
    

    我们可以根据LiveData值的变化来做对应的事情,且不用担心生命周期越界的问题。

    LiveData核心方法

    方法名 作用
    observe(LifecycleOwner owner,Observer observer) 注册和宿主生命周期关联的观察者
    observeForever(Observer observer) 注册观察者,不会反注册,需自行维护
    setValue(T data) 发送数据,没有活跃的观察者时不分发。只能在主线程。
    postValue(T data) 和setValue一样。不受线程环境限制,
    onActive 当且仅当有一个活跃的观察者时会触发
    inActive 不存在活跃的观察者时会触发

    LiveData的衍生类及功能

    • MutableLiveData

      该类十分简单,主要开放了LiveData的发消息接口

      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只能接受消息,避免拿到LiveData对象既能发消息也能收消息的混乱使用。

    • MediatorLiveData

      合并多个LiveData, 即一对多统一观察,一个经典的场景是:在向服务器请求数据时,优先展示本地数据库的数据,然后由请求的响应决定是否要更新数据库,如下图所示:

      img

      // ResultType: Type for the Resource data.
      // RequestType: Type for the API response.
      public abstract class NetworkBoundResource<ResultType, RequestType> {
          // MediatorLiveData 数据组合者
          private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
          private Executor executor;
          @MainThread
          protected NetworkBoundResource(Executor mExecutor) {
              this.executor = mExecutor;
              // 首先初始化一个Loading的status 空result
              result.setValue(Resource.loading(null));
              // 然后从数据库中获取持久化数据
              LiveData<ResultType> dbSource = loadFromDb();
              // 数据组合者监听数据库中的数据
              result.addSource(dbSource, data -> {
                  // dbSource第一次回调,用来判断数据有效期,此时取消监听
                  result.removeSource(dbSource);
                  // 业务自行定义是否需要fetch最新的数据
                  if (shouldFetch(data)) {
                      fetchFromNetwork(dbSource);
                  } else {
                      // 数据有效,重新观察一次,观察者会立马收到一次回调(LiveData粘性事件机制)
                      result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
                  }
              });
          }
          
          private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
              LiveData<ApiResponse<RequestType>> apiResponse = createCall();
              // 这里数据虽无效,但是可以先给UI展示
              result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
              result.addSource(apiResponse, response -> {
                  result.removeSource(apiResponse);
                  result.removeSource(dbSource);
      
                  if (response != null) {
                      if (response.isSuccessful()) {
                          executor.execute(() -> {
                              saveCallResult(processResponse(response));
                              executor.execute(() ->
                                   // 这里我们拿到的最新的数据需要主动通知监听者,以拿到从服务端拿到的最新数据
                                      result.addSource(loadFromDb(),
                                              newData -> setValue(Resource.success(newData)))
                              );
                          });
                      } else {
                          onFetchFailed();
                          result.addSource(dbSource,
                                  newData -> setValue(Resource.error(response.errorMessage, newData)));
                      }
                  } else {
                      result.addSource(dbSource,
                              newData -> setValue(Resource.error("Request failed, server didn't 		response", newData)));
                  }
              });
          }
      }
      

      上面的例子MediatorLiveData同时监听了数据库中的LiveData和服务端的LiveData

    • Transformations

      这是一个数据转化工具类,共两个主要方法:

      1. 静态转化 -- map(@NonNull LiveData source, @NonNull final Function<X, Y> mapFunction)

        MutableLiveData<Integer> data = new MutableLiveData<>();
        
        //数据转换
        LiveData<String> transformData = Transformations.map(data, input ->   String.valueOf(input));
        //使用转换后生成的transformData去观察数据
        transformData.observe( this, output -> {
        
        });
        
        //使用原始的livedata发送数据
        data.setValue(10);
        
      2. 动态转化 -- LiveData switchMap(
        @NonNull LiveData source,
        @NonNull final Function<X, LiveData> switchMapFunction)

       MutableLiveData userIdLiveData = ...;
       // 用户数据和用户id紧密相关,当我们改变userId的liveData的同时还会主动通知userLiveData更新
      LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
           repository.getUserById(id));
      
       void setUserId(String userId) {
            this.userIdLiveData.setValue(userId);
       }
      
      // 不要像下面这么做
      private LiveData getUserLiveData(String userId) {
          // DON'T DO THIS
          return repository.getUserById(userId);
      }
      
      

    四. LiveData的实现机制

    livedata2

    LiveData注册观察者触发消息分发流程:

    1. observe 注册时,可以主动跟宿主生命周期绑定,不用反注册:
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
            //1. 首先来个断言,这个方法只能在主线程调用,observeForever也是。
            assertMainThread("observe");
            //2.其次把注册进来的observer包装成 一个具有生命周边边界的观察者
            //它能监听宿主被销毁的事件,从而主动的把自己反注册,避免内存泄漏
            //此时观察者是否处于活跃状态就等于宿主是否可见
            LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
            //3.接着会判断该观察是否已经注册过了,如果是则抛异常,所以要注意,不允许重复注册
            ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
            if (existing != null && !existing.isAttachedTo(owner)) {
                throw new IllegalArgumentException("Cannot add the same observer"
                        + " with different lifecycles");
            }
            //4.这一步才是关键
            //利用Lifecycle,把观察者注册到进去,才能监听到宿主生命周期状态的变化,对不对?
            //根据Lifecycle文章中的分析,一旦一个新的观察者被添加,Lifecycle也会同步它的状态和宿主一致对不对?此时会触发观察者的onStateChanged方法
            owner.getLifecycle().addObserver(wrapper);
    }
    
    1. LifecycleBoundObserver 监听宿主的生命周期(这里我们还记得之前在Lifecycle解析中提到addObserver时也会对observer进行包装,这时一样的),并且宿主不可见时不分发任何数据:
    class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
            LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
                super(observer);
            }
    
            @Override
            boolean shouldBeActive() {
            //使用observer方法注册的观察者都会被包装成LifecycleBoundObserver
            //观察者是否活跃就等于宿主 的状态是否大于等于STARTED,
            //如果页面当前不可见,你发送了一条消息,此时是不会被分发的,可以避免后台任务抢占资源,当页面恢复可见才会分发。
            //注意:如果使用observerForever注册的观察者,
            //会被包装成AlwaysActiveObserver,它的shouldBeActive一致返回true.即便在页面不可见也能收到数据
                return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
            }
    
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                    //在这里如果监听到宿主被销毁了,则主动地把自己从livedata的观察者中移除掉
                if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                    removeObserver(mObserver);
                    return;
                }
                //否则说明宿主的状态发生了变化,此时会判断宿主是否处于活跃状态
                activeStateChanged(shouldBeActive());
            }
        }
    
    1. ObserverWrapper 状态变更后,如果观察者处于活跃状态会触发数据的分发流程:
    abstract class ObserverWrapper{
            final Observer<? super T> mObserver;
            boolean mActive;
            int mLastVersion = START_VERSION//这里就等于-1,没有主动和LiveData的mVersion对齐,为黏性事件埋下了伏笔
            
            void activeStateChanged(boolean newActive) {
                if (newActive == mActive) {
                    return;
                }
                //更改观察者的状态
                mActive = newActive;
                boolean wasInactive = LiveData.this.mActiveCount == 0;
                //如果此时有且只有一个活跃的观察者则触发onActive
                LiveData.this.mActiveCount += mActive ? 1 : -1;
                if (wasInactive && mActive) {
                    onActive();
                }
                //没有任何一个活跃的观察者则触发onInactive
                //利用这个方法被触发的时机,可以做很多事,比如懒加载,资源释放等
                if (LiveData.this.mActiveCount == 0 && !mActive) {
                    onInactive();
                }
                //如果此时观察者处于活跃状态,下面就开始分发数据了
                //请注意,这里传递了this = observer
                if (mActive) {
                    dispatchingValue(this);
                }
            }
    } 
    
    1. dispatchingValue 数据分发流程控制:
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
            if (mDispatchingValue) {
                mDispatchInvalidated = true;
                return;
            }
            mDispatchingValue = true;
            do {
                mDispatchInvalidated = false;
                if (initiator != null) {
                //如果传递的观察者不为空,则把数据分发给他自己。这个流程是新注册观察者的时候会被触发
                    considerNotify(initiator);
                    initiator = null;
                } else {
                    //否则遍历集合中所有已注册的的观察者,逐个调用considerNotify,分发数据
                    for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> 		                       iterator =
                            mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                        considerNotify(iterator.next().getValue());
                        if (mDispatchInvalidated) {
                            break;
                        }
                    }
                }
            } while (mDispatchInvalidated);
            mDispatchingValue = false;
        }
    
    1. considerNotify 数据真正分发的地方,需要满足三个套件:
    private void considerNotify(ObserverWrapper observer) {
            //观察者当前状态不活跃不分发
            if (!observer.mActive) {
                return;
            }
            //观察者所在宿主是否处于活跃状态,否则不分发,并且更改观察者的状态为false
            if (!observer.shouldBeActive()) {
                observer.activeStateChanged(false);
                return;
            }
            //此处判断观察者接收消息的次数是否大于等于 发送消息的次数
            //但是observer被创建之初verison=-1 
            //如果此时LiveData已经发送过数据了。这里就不满足了,就出现黏性事件了,后注册的观察者收到了前面发送的消息。
            if (observer.mLastVersion >= mVersion) {
                return;
            }
            //每分发一次消息,则把观察者和LiveData的version对齐,防止重复发送
            observer.mLastVersion = mVersion;
            //最后的数据传递
            observer.mObserver.onChanged((T) mData);
        }
    

    普通消息分发流程。即调用 postValue,setValue 才会触发消息的分发:

    img

    五. 总结

    Android开发大部分主要的工作就是从服务器获取数据,将其转为渲染为UI展示给用户。本质上我们所做的逻辑都是“数据驱动”,所有的View都是数据的一种状态,数据映射的过程中我们需要去考虑生命周期的限制--即数据的活跃性。LiveData结合Lifecycle帮我们规范了这个流程,完美诠释了observer、lifecycle-aware、data holder 这个铁三角,开发者在遵循这个开发流程的过程中,便完成了UI -> ViewModel -> Data的单项依赖。

  • 相关阅读:
    设计模式--22、状态模式
    设计模式--21、备忘录模式
    设计模式--20、迭代器模式
    关于分布式事务、两阶段提交协议、三阶提交协议
    分布式系统的一致性探讨
    分布式系统的BASE理论
    分布式系统的CAP理论
    Kafka集群环境搭建
    Elasticsearch插件head的安装(有坑)
    centos6 x64安装elasticsearch5.5.2启动报错
  • 原文地址:https://www.cnblogs.com/ztqup666/p/13663856.html
Copyright © 2011-2022 走看看