- 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
- 一个框架的好坏,通常会有以下几个衡量指标:
- 是否可以解决当前的业务问题
- 是否具备好的可扩展性
- 是否具备好的可测试性
- 是否遵循模块化设计原则
- 逻辑,界面,数据是否分离
- 当然,还有更多的衡量指标,我们在这里不一一列举,上图所示的是一个圆环依赖结构,从内到外分别是:业务数据->业务逻辑层->接口适配层->界面,遵循"依赖倒置原则",内部圆圈不能依赖外部圆圈
- 遵从单向依赖原则,我们的模块内部也划分了一下三个层级,从下往上分别是:
- 数据层:
- 主要用来提供界面展示及交互所需要数据,通常会定义获取数据的策略接口,选择不同的实现(DB,内存,网络等)
- 不依赖其他层级,被逻辑层依赖
- 逻辑层(领域层)
- 这一层跟业务强相关,包含复杂的业务逻辑,譬如:获取数据,提交数据,数据存储策略的选择及数据融合等
- 依赖数据层,被展示层依赖
- 展示层(表现层)
- 这一层的主要工作有以下几个:
- 构建用户可见的界面
- 为界面展示提供必要的数据
- 接收并处理用户交互事件
- 复杂的业务逻辑都委派给逻辑层(领域层)来处理,这里的ViewModel可以理解成一个接口适配器,只负责建立与View之间的通信渠道,然后传递数据或接受指令,自身并不处理复杂的业务逻辑
- 依赖逻辑层(领域层)
- 这一层的主要工作有以下几个:
- 数据层:
- 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中获取到最新的完整的数据列表,刷新展示界面
- 前面已经提到了,usecase主要用来处理复杂的业务逻辑,减轻ViewModel负担
- BaseUseCase可以看做是一个模板方法类(当然这个模板不一定适用所有业务场景),内部会做一些"线程调度""LiveData赋值"等业务无相关的操作,具体的业务逻辑交给子类实现
- 这里有一个Either<Failure, T>返回值,这个是 java 8函数式编程的一个特性,类似于 c语言 里的union(共同体),主要用来 以类型安全的方式返回两个(或多个)值,感兴趣的同学可以自行google
- 定义一个获取(读/写)数据的策略接口,实现不同的数据读写策略,也可以是多个策略的组合使用,根据具体的业务场景来决定,最大的好处就是可扩展性好,逻辑层(领域层)不用关心数据具体从哪里来
- 模块划分有两种典型的思路,"按功能用途分模块","按业务特性分模块",前者的一个常规做法就是按照Model,View,Present(Controler)等角色对文件进行分组,这样做最大的弊端就是不利于业务拆分及多人协作编程,所以,我们推荐按照"业务特性分模块",譬如:主界面,详情页,登录页等都是一个相对独立的模块
- 然后,如何界定一个独立的子模块,需要满足下面几个条件:
- 相对独立的界面展示(android里的一个Activity或一个Fragment)
- 相对独立的数据来源(你的界面渲染所需要的数据,可以通过独立的数据仓库获取,譬如:独立的服务端api接口,独立的数据表)
- 用户交互产生的影响尽可能的收敛在界面内(譬如:下拉刷新产生的数据只用来渲染当前页面)
- 具备一个闭环的生命周期(模块使用的内存是可回收的,不建议用单例来实现跨模块内存共享)
- 简单概括就是:如果一个模块在脱离其他模块的情况下,依然能以缺省的方式独立运行,那么它就是一个相对独立的模块
- 我们以一个列表界面为例子,运行效果:
- 按照以下步骤开发
- 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() { } } 复制代码
- Step1 数据层:数据仓库实现
- 复杂的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() { } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Android模块化改造以及模块化通信框架
- OSGi模块化框架详解
- 学习 CocoaPods:Swift、框架以及模块
- FEZ前端模块化工程开发框架
- gf框架之gmlock - 内存锁模块
- Asf PHP扩展框架之预警模块介绍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
决战618:探秘京东技术取胜之道
京东集团618作战指挥中心 / 电子工业出版社 / 2017-11 / 99
《决战618:探秘京东技术取胜之道》以京东技术团队备战618为主线,集合京东数百位技术专家,对京东所有和618相关的关键技术系统进行了一次全面的梳理和总结,是京东技术体系的智慧结晶。 《决战618:探秘京东技术取胜之道》从前端的网站、移动入口到后端的结算、履约、物流、供应链等体系,系统展示了京东最新的技术成就。同时,也涵盖了京东正在充分运用大数据、人工智能等先进技术对所有技术体系架构进行整体......一起来看看 《决战618:探秘京东技术取胜之道》 这本书的介绍吧!