Android模块开发框架 LiveData+ViewModel

栏目: IOS · Android · 发布时间: 5年前

  • LiveData+ViewModel是Android Architecture Component开发组件的一部分,主要的目的是为了解决android开发过程中的因为Activity及Fragment生命周期引起的一些常见问题,譬如:内存泄露,异步任务引起空指针,横竖屏切换界面刷新问题,当然它的作用远不止于此,比如:LiveData的观察者模型可以保障界面在第一时间更新到最新的数据(当然你的LifecycleOwner必须是Alive状态),解决了多端写入数据的同步问题;使用LiveData实现View和VM的自动绑定(通常这个绑定的数据流向是单向的,VM->View).另外值得一提的是,AAC框架内部维护了一个ViewModel的内存缓存池,并且会监听Activity或Fragment的生命周期,在destory的时候自动清空缓存.因此,对于开发者而言,只需要聚焦在业务开发,几乎不用对接生命周期接口.
  • 感兴趣的同学可以去看看官方的详细文档和Demo
    • 官当文档:链接
    • 官方Demo:链接
  • mvvm相比mvp最大的区别就是实现了v和vm(p)的自动绑定,mvp中的v和p之间存在较多的接口依赖,不利于扩展及测试,mvvm通常存在一个Binding中介层,通过注解+apt(或反射)的方式,解除v和vm直接的接口依赖,当然mvvm相比于mvp的进步不仅仅是代码解耦,也是从"面向功能接口编程"到"响应式编程"的思想转变,一切皆是数据(指令)流(ui<->数据<->model)
  • 官方推荐使用MVVM框架,结合DataBinding依赖注入框架实现View和VM的双向绑定,考虑到使用DataBinding依赖于xml布局配置,且有较大的理解成本,我们这次没有采用严格意义上的MVVM框架,而是选择折中方案:
    • VM->View:通过LiveData实现数据的单向流动
    • View->VM:依然采用传统的接口实现,但是所有的执行结果都依赖LiveData回传给View
Android模块开发框架 LiveData+ViewModel
  • 一个框架的好坏,通常会有以下几个衡量指标:
    • 是否可以解决当前的业务问题
    • 是否具备好的可扩展性
    • 是否具备好的可测试性
    • 是否遵循模块化设计原则
    • 逻辑,界面,数据是否分离
  • 当然,还有更多的衡量指标,我们在这里不一一列举,上图所示的是一个圆环依赖结构,从内到外分别是:业务数据->业务逻辑层->接口适配层->界面,遵循"依赖倒置原则",内部圆圈不能依赖外部圆圈
Android模块开发框架 LiveData+ViewModel
  • 遵从单向依赖原则,我们的模块内部也划分了一下三个层级,从下往上分别是:
    • 数据层:
      • 主要用来提供界面展示及交互所需要数据,通常会定义获取数据的策略接口,选择不同的实现(DB,内存,网络等)
      • 不依赖其他层级,被逻辑层依赖
    • 逻辑层(领域层)
      • 这一层跟业务强相关,包含复杂的业务逻辑,譬如:获取数据,提交数据,数据存储策略的选择及数据融合等
      • 依赖数据层,被展示层依赖
    • 展示层(表现层)
      • 这一层的主要工作有以下几个:
        • 构建用户可见的界面
        • 为界面展示提供必要的数据
        • 接收并处理用户交互事件
      • 复杂的业务逻辑都委派给逻辑层(领域层)来处理,这里的ViewModel可以理解成一个接口适配器,只负责建立与View之间的通信渠道,然后传递数据或接受指令,自身并不处理复杂的业务逻辑
      • 依赖逻辑层(领域层)
Android模块开发框架 LiveData+ViewModel
  • View与VM之间的通信有两种
    • View->VM,通常是用户交互行为产生的一些指令(可能携带一些数据,譬如:用户登录行为会携带账号密码)
    • VM->View,通常是界面展示所需要的数据(也可能是状态,譬如:加载数据失败,展示一个Toast提示等)
  • 我们来举一个简单的案例,一个列表界面,需要刷新数据并展示,会有以下几个必要步骤:
    • 首先,View持有一个ViewModel实例(自己实例化,或则外部传参都可以)
    • 通过ViewModel获取一个LiveData对象(同一类LiveData在ViewModel内只能有一个实例),并开始观察这个LiveData对象(俗称subscribe)
    • ViewModel接收到"刷新数据"的指令,委派给具体的UseCase来执行
    • UseCase从数据源获取到数据,写入到LiveData
    • LiveData通知所有观察者(当然,会先判断observer依附的LifecycleOwner是否alive),其中就包括View
    • View从LIveData中获取到最新的完整的数据列表,刷新展示界面
Android模块开发框架 LiveData+ViewModel
  • 前面已经提到了,usecase主要用来处理复杂的业务逻辑,减轻ViewModel负担
  • BaseUseCase可以看做是一个模板方法类(当然这个模板不一定适用所有业务场景),内部会做一些"线程调度""LiveData赋值"等业务无相关的操作,具体的业务逻辑交给子类实现
  • 这里有一个Either<Failure, T>返回值,这个是 java 8函数式编程的一个特性,类似于 c语言 里的union(共同体),主要用来 以类型安全的方式返回两个(或多个)值,感兴趣的同学可以自行google
Android模块开发框架 LiveData+ViewModel
  • 定义一个获取(读/写)数据的策略接口,实现不同的数据读写策略,也可以是多个策略的组合使用,根据具体的业务场景来决定,最大的好处就是可扩展性好,逻辑层(领域层)不用关心数据具体从哪里来
  • 模块划分有两种典型的思路,"按功能用途分模块","按业务特性分模块",前者的一个常规做法就是按照Model,View,Present(Controler)等角色对文件进行分组,这样做最大的弊端就是不利于业务拆分及多人协作编程,所以,我们推荐按照"业务特性分模块",譬如:主界面,详情页,登录页等都是一个相对独立的模块
  • 然后,如何界定一个独立的子模块,需要满足下面几个条件:
    • 相对独立的界面展示(android里的一个Activity或一个Fragment)
    • 相对独立的数据来源(你的界面渲染所需要的数据,可以通过独立的数据仓库获取,譬如:独立的服务端api接口,独立的数据表)
    • 用户交互产生的影响尽可能的收敛在界面内(譬如:下拉刷新产生的数据只用来渲染当前页面)
    • 具备一个闭环的生命周期(模块使用的内存是可回收的,不建议用单例来实现跨模块内存共享)
  • 简单概括就是:如果一个模块在脱离其他模块的情况下,依然能以缺省的方式独立运行,那么它就是一个相对独立的模块
  • 我们以一个列表界面为例子,运行效果:
Android模块开发框架 LiveData+ViewModel
  • 按照以下步骤开发
    • Step1 数据层:数据仓库实现
      • 定义数据Bean
      public class HotContentItem {
      
          public String id;
          public String name;
          public String desc;
          public long timeStamp;
      }
      复制代码
      • 数据仓库策略实现(数据是本地mock的)
      public class HotContentNetRepository {
      
          //mock数据
          public Either<? extends Failure, List<HotContentItem>> refreshNew() {
              try {
                  Thread.sleep(1000L);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              Either<? extends Failure, List<HotContentItem>> result;
              Random random = new Random();
              boolean success = random.nextInt(10) > 3;
              if (success) {
                  result = Either.right((mockItemList(0)));
              } else if (random.nextInt(10) > 3) {
                  result = Either.right(Collections.<HotContentItem>emptyList());
              } else {
                  result = Either.left(new NetworkFailure());
              }
              return result;
          }
      }
      复制代码
    • Step2 逻辑层:数据仓库选择及使用
      • 省略列这一步,按照业务需求实现不同的数据仓库组合使用
    • Step3 逻辑层:实现UseCase(示例代码:刷新数据)
    public class HotContentRefreshNew extends BaseUseCase<List<HotContentItem>, Void> {
    
        private HotContentNetRepository mNetRepository;
    
        public HotContentRefreshNew(
            MutableLiveData<List<HotContentItem>> data,
            MutableLiveData<Failure> failure) {
            super(data, failure);
            mNetRepository = new HotContentNetRepository();
        }
    
        @Override
        protected Either<? extends Failure, List<HotContentItem>> loadData(Void aVoid) {
            //从网络获取数据
            Either<? extends Failure, List<HotContentItem>> result = mNetRepository.refreshNew();
            if (result.isRight() && CollectionUtil.isEmpty(result.right())) {
                Failure failure = new RefreshNewFailure(RefreshNewFailure.CODE_DATA_EMPTY, "Data is empty!");
                result = Either.left(failure);
            }
            return result;
        }
    
        @Override
        protected Failure processFailure(Failure failure) {
            ...
        }
    }
    复制代码
    • Step4 展示层:UI框架选择
      • 示例界面是作为一个TabLayout的一个Page页,因此这里选择"具备生命周期View"作为的UI框架,这是个自定的View,实现了LifecycleOwner接口(参考了LifecycleActivity和LifecycleFragment的实现逻辑)
      public abstract class BaseLifecycleView extends FrameLayout implements LifecycleOwner {
      
          private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
          private ViewModelStore mViewModelStore = new ViewModelStore();
      
          public BaseLifecycleView(@NonNull Context context) {
              super(context);
          }
      
          protected abstract void onCreate();
      
          protected abstract void onDestroy();
      
          @Override
          public Lifecycle getLifecycle() {
              return mRegistry;
          }
      
          @Override
          @CallSuper
          protected void onAttachedToWindow() {
              super.onAttachedToWindow();
              mRegistry.handleLifecycleEvent(Event.ON_CREATE);
              onCreate();
              if (getVisibility() == View.VISIBLE) {
                  mRegistry.handleLifecycleEvent(Event.ON_START);
              }
          }
      
          @Override
          @CallSuper
          protected void onDetachedFromWindow() {
              super.onDetachedFromWindow();
              mRegistry.handleLifecycleEvent(Event.ON_DESTROY);
              mViewModelStore.clear();
              onDestroy();
          }
      
          @Override
          @CallSuper
          protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
              super.onVisibilityChanged(changedView, visibility);
              Event event = visibility == View.VISIBLE ? Event.ON_RESUME : Event.ON_PAUSE;
              mRegistry.handleLifecycleEvent(event);
          }
      
          @Override
          @CallSuper
          public void onStartTemporaryDetach() {
              super.onStartTemporaryDetach();
              State state = mRegistry.getCurrentState();
              if (state == State.RESUMED) {
                  mRegistry.handleLifecycleEvent(Event.ON_STOP);
              }
          }
      
          @Override
          @CallSuper
          public void onFinishTemporaryDetach() {
              super.onFinishTemporaryDetach();
              State state = mRegistry.getCurrentState();
              if (state == State.CREATED) {
                  mRegistry.handleLifecycleEvent(Event.ON_START);
              }
          }
      
          protected <T extends ViewModel> T getViewModel(@NonNull ViewModelProvider.NewInstanceFactory modelFactory,
                                                         @NonNull Class<T> modelClass) {
              return new ViewModelProvider(mViewModelStore, modelFactory).get(modelClass);
          }
      
      }
      复制代码
    • Step5 展示层:定义自己的LiveData和ViewModel
    public class HotContentViewModel extends BaseViewModel<List<HotContentItem>> {
    
        private HotContentRefreshNew mRefreshNew;
    
        public HotContentViewModel() {
            refreshNew();
        }
    
        public void refreshNew() {
            AssertUtil.mustInUiThread();
            if (mRefreshNew == null) {
                mRefreshNew = new HotContentRefreshNew(getMutableLiveData(), getMutableFailure());
            }
            //通过usecase执行具体的刷新操作
            mRefreshNew.executeOnAsyncThread(null);
        }
    
       ...
    }
    复制代码
    • Step6 展示层:关联V和VM
    public class HotContentView extends BaseLifecycleView {
    
        private HotContentViewModel mViewModel;
    
        private SwipeRefreshLayout mSwipeRefreshLayout;
        private AutoLoadMoreRecycleView mRecyclerView;
        private HotContentAdapter mContentAdapter;
    
        public HotContentView(@NonNull Context context) {
            super(context);
            视图对象初始化
            ...
            mRecyclerView.setLoadMoreListener(new LoadMoreListener() {
                @Override
                public void onLoadMore() {
                    HotContentItem lastOne = CollectionUtil.lastOne(mViewModel.getData().getValue());
                    if (lastOne == null) {
                        mRecyclerView.completeLoadMore("No more data");
                    } else {
                        mViewModel.loadHistory(lastOne);
                    }
                }
            });
            ...
            mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    //刷新数据
                    mViewModel.refreshNew();
                }
            });
        }
    
        @Override
        protected void onCreate() {
            mViewModel = getViewModel(new NewInstanceFactory(), HotContentViewModel.class);
            mViewModel.getData().observe(this, new Observer<List<HotContentItem>>() {
                @Override
                public void onChanged(@Nullable List<HotContentItem> hotContentItems) {
                    //刷新数据成功
                    mContentAdapter.setItemList(hotContentItems);
                    mSwipeRefreshLayout.setRefreshing(false);
                    ...
                }
            });
            ...
        }
    
        @Override
        protected void onDestroy() {
        }
    
    }
    复制代码
  • 复杂的UI交互指令如何传达给ViewModel
    • 在本文开头"MVP还是MVVM"框架选型中我们已经提过,目前并没有使用到MVVM的精髓"DataBinding",而是通过LiveData观察者模式实现V->VM的单向绑定(即:数据可以从VM自动流向V,但是V的操作指令无法自动传递给VM),因此,复杂交互(譬如:下拉刷新,滚动加载更多)还是需要通过传统的MVP思维在VM中定义功能接口提供给V来调用
  • 除了数据之外,还有状态会影响界面展示
    • 理想状态下,VM提供一个LiveData给View使用,这个LiveData包含了View渲染需要的全部数据,但是很多情况下View并不会只依赖单一类型数据,譬如:下拉刷新操作,会有以下三种结果返回:列表数据,空数据,失败.对于"列表数据"我们可以通过LiveData通知View做整体刷新,但是"空数据""失败"的情况也需要在界面上有所提示,而这两个返回值是不能影响当前的"列表数据"(即:不影响当前的列表展示),而应该看做是独立与数据之外的"指令"更合适,它们最大的特征就是"一次性",不需要像"列表数据"那样存储处理(可以理解成是给界面消费的一次性事件)
    • 再回到LiveData,LiveData主要用来存储相对持久的数据,并且任何时候View从LiveData获取的数据都必须是"完整的"可以用来直接渲染界面的,回到上面"下拉刷新"的例子,如果我们将"空数据""失败"也通过LiveData封装,然后由View来观察这个LiveData(自定义一个Observer),在收到对应的"指令"通知的时候处理"界面提示",这样似乎也能满足VM->View的状态通知需求,问题来了,由于Observer的生命周期很可能会比LiveData的生命周期更短(取决于Observer依赖的LifecycleOwner)(比如:Observer的生命周期和ViewPager里的某一个View一致,LiveData的生命周期和Activity一致),那么当View被复用的时候会再次观察同一个LiveData,然后自动收到LiveData的通知,获取LiveData最新的数据(譬如:"失败"指令),刷新界面(提示"刷新失败"),这样就会很奇怪了,明明没有刷新动作,平白无故提示"刷新失败"
    • 解决办法还是回到"指令"的特征"一次性",定义一个DisposableLiveData,每次执行setData(会通知观察者,也就是View)之后立即将data置空,这样下次再getData时候就会返回null,而不是一个"未预期的数据"
      • 代码实现很简单
      public class DisposableLiveData<T> extends MutableLiveData<T> {
      
          @Override
          public void postValue(T value) {
              super.postValue(value);
              if (value != null) {
                  super.postValue(null);
              }
          }
      
          @Override
          public void setValue(T value) {
              super.setValue(value);
              if (value != null) {
                  super.postValue(null);
              }
          }    
      复制代码
      • 示例代码:
      public class HotContentView extends BaseLifecycleView {
      
          private HotContentViewModel mViewModel;
          private SwipeRefreshLayout mSwipeRefreshLayout;
      
      
          public HotContentView(@NonNull Context context) {
              super(context);
              ...
      
              mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                  @Override
                  public void onRefresh() {
                     //刷新数据
                      mViewModel.refreshNew();
                  }
              });
          }
      
          @Override
          protected void onCreate() {
              mViewModel = getViewModel(new NewInstanceFactory(), HotContentViewModel.class);
             ...
              mViewModel.getFailure().observe(this, new Observer<Failure>() {
                  @Override
                  public void onChanged(@Nullable Failure failure) {
                      //处理失败提示
                      if (failure instanceof RefreshNewFailure) {
                          mSwipeRefreshLayout.setRefreshing(false);
                          ToastManager.getInstance().showToast(getContext(), ((RefreshNewFailure)failure).getMessage(),
                              Toast.LENGTH_SHORT);
                      }
                      ...
                  }
              });
          }
      
          @Override
          protected void onDestroy() {
          }
      
      }
      复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Essential PHP Security

Essential PHP Security

Chris Shiflett / O'Reilly Media / 2005-10-13 / USD 29.95

Being highly flexible in building dynamic, database-driven web applications makes the PHP programming language one of the most popular web development tools in use today. It also works beautifully wit......一起来看看 《Essential PHP Security》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具