媒介
Jetpack 架构组件及 “尺度化开发模式” 建立,意味着Android 开发已步入成熟阶段,只有对 MVVM 确有深入明白,才能天然而然写出尺度化、规范化代码。
本次笔者会浅入浅出的先容以下内容,由于它是一个我的学习总结记载,以是比力得当对MVVM不是很熟悉,但又想相识下全貌的读者:
- Jetpack MVVM
- Jetpack Lifecycle
- Jetpack LiveData
- Jetpack ViewModel
- Jetpack DataBinding
Jetpack MVVM
在正文开始前,先回顾下MVP:
MVP,Model-View-Presenter,职责分类如下:
- Model,数据模子层,用于获取和存储数据。
- View,视图层,即Activity/Fragment
- Presenter,控制层,负责业务逻辑。
我们知道,MVP是对MVC的改进,办理了MVC的两个题目:
- View责任明白,逻辑不再写在Activity中,放到了Presenter中;
- Model不再持有View
MVP最常用的实现方式是如许的:
View层吸收到用户操纵事故,关照到Presenter,Presenter举行逻辑处理处罚,然后关照Model更新数据,Model 把更新的数据给到Presenter,Presenter再关照到View 更新界面。
MVP本质是面向接口编程,它也存在一些痛点:
- 会引入大量的IView、IPresenter接口,增长实现的复杂度。
- View和Presenter相互持有,形成耦合。
随着发展,Jetpack MVVM 就应势而生,它是MVVM 模式在Android 开发中的一个详细实现,是Google 官方提供并推荐的MVVM实现方式。它的分层:
- Model层:用于获取和存储数据
- View层:即Activity/Fragment
- ViewModel层:负责业务逻辑
MVVM的焦点是 数据驱动,把解耦做的更彻底(ViewModel不持有view )。
View 产生事故,利用ViewModel举行逻辑处理处罚后,关照Model更新数据,Model把更新的数据给ViewModel,ViewModel自动关照View更新界面
Jetpack Lifecycle
开端
在没有Lifecycle之前,生命周期的管理都是靠手工维持。比如我们常常会在Activity的onStart初始化某些成员(比如MVP的Presenter, MediaPlayer)等,然后在onStop中开释这些成员的内部资源。
class MyActivity extends AppCompatActivity { private MyPresenter presenter; public void onStart(...) { presenter= new MyPresenter (); presenter.start(); } public void onStop() { super.onStop(); presenter.stop(); }}class MyPresenter{ public MyPresenter() { } void start(){ // 耗时操纵 checkUserStatus{ if (result) { myLocationListener.start(); } } } void stop() { // 开释资源 myLocationListener.stop(); }}上述的代码本身是没有太大题目的。它的缺点在于现实生产环境下,会有很多的页面和组件须要相应生命周期的状态厘革,就得在生命周期方法中放置大量的代码,如许的方式就会导致代码(如 onStart() 和onStop())变得痴肥,难以维护。
除此之外尚有一个题目就是:
MyPresenter类中onStart里的checkUserStatus是个耗时操纵,如果耗时过长,Activity 烧毁的时间,还没有实验过来,就已经stop了,然后等一会儿实验过来的时间,myLocationListener又start,但反面不会再有myLocationListener的stop,如许这个组件的资源就不能正常开释了。如果它内部还持有Activity的引用,还会造成内存走漏。
Lifecycle
于是,Lifecycle就出来了,它通过 “模板方法模式” 和 “观察者模式”,将生命周期管理的复杂操纵,放到LifecycleOwner(如 Activity、Fragment 等 “视图控制器” 基类)中封装好。
对于开发者来说,在 “视图控制器” 的类中只需一句 getLifecycle().addObserver(new MyObserver()) ,当Lifecycle的生命周期发生厘革时,MyObserver就可以在本身内部感知到。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_lifecycle); // 使MyObserver感知生命周期 getLifecycle().addObserver(new MyObserver());}看看它是怎么实现的:
# ComponentActivityprivate final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); public Lifecycle getLifecycle() { return mLifecycleRegistry;}# LifecycleRegistrypublic LifecycleRegistry(@NonNull LifecycleOwner provider) { this(provider, true);}private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap = new FastSafeIterableMap<>();public void addObserver(@NonNull LifecycleObserver observer) { mObserverMap.putIfAbsent(observer, statefulObserver); ...}public void removeObserver(@NonNull LifecycleObserver observer) { mObserverMap.remove(observer);}void dispatchEvent(LifecycleOwner owner, Event event) { State newState = event.getTargetState(); mState = min(mState, newState); mLifecycleObserver.onStateChanged(owner, event); mState = newState;}正由于Activity实现了LifecycleOwner,以是才能直接利用getLifecycle()
# ComponentActivityprotected void onCreate(@Nullable Bundle savedInstanceState) { // 关键代码:通过ReportFragment完成生命周期事故分发 ReportFragment.injectIfNeededIn(this); if (mContentLayoutId != 0) { setContentView(mContentLayoutId); }}# ReportFragmentstatic void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) { if (activity instanceof LifecycleOwner) { Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle(); if (lifecycle instanceof LifecycleRegistry) { // 处理处罚生命周期事故,更新当前都状态并关照全部的注册的LifecycleObserver ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event); } }}# LifecycleRegistrypublic void handleLifecycleEvent(@NonNull Lifecycle.Event event) { enforceMainThreadIfNeeded("handleLifecycleEvent"); moveToState(event.getTargetState());}当LifecycleRegistry本身的生命周期改变后,LifecycleRegistry就会逐个关照每一个注册的LifecycleObserver ,并实验对应生命周期的方法。
小结
以是Lifecycle 的存在,是为相识决 “生命周期管理” 划一性的题目。
Jetpack LiveData
开端
在没有LiveData的时间,我们在网络哀求回调、跨页面通讯等场景分发消息,大多是通过EventBus、接口callback的方式去完成。
比如常常利用的EventBus等消息总线的方式会有题目:
它缺乏一种束缚,当我们去利用时,很容易由于随处利用,末了追溯数据泉源的难度就会很大。
别的,EventBus在处理处罚生命周期上也很贫苦,由于须要手动去控制,会容易出现生命周期管理不划一的题目。
LiveData
先看下官方的先容:
LiveData 是一种可观察的数据存储器类。与通例的可观察类差异,LiveData 具有生命周期感知本领,意味着它遵照其他应用组件(如 Activity/Fragment)的生命周期。这种感知本领可确保 LiveData 仅更新处于活泼生命周期状态的应用组件观察者。
如果观察者的生命周期处于 STARTED或 RESUMED状态,则 LiveData 会以为该观察者处于活泼状态,就会将更新关照给活泼的观察者,非活泼的观察者不会收到更改关照。
LiveData 是 观察者模式 的体现,先从LiveData的observe方法看起:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { // LifecycleOwner是DESTROYED状态,直接忽略 if (owner.getLifecycle().getCurrentState() == DESTROYED) { return; } // 绑定生命周期的Observer LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); // 让该Observer可以感知生命周期 owner.getLifecycle().addObserver(wrapper);}observeForever和observe()类似,只不外它会以为观察者不停是活泼状态,不会自动移除观察者。
LiveData很告急的一部分就是数据更新:·
LiveData原生的API提供了2种方式供开发者更新数据, 分别是setValue()和postValue(),调用它们都会 触发观察者并更新UI。
setValue()方法必须在 主线程 举行调用,而postValue()方法更得当在 子线程 中举行调用。postValue()终极也会调用setValue,只须要看下setValue方法就可以了:
protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null);}void dispatchingValue(@Nullable ObserverWrapper initiator) { ... for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); }}private void considerNotify(ObserverWrapper observer) { if (!observer.mActive) { return; } ... observer.mObserver.onChanged((T) mData);}小题目:我们在利用LiveData有一个上风是不会发生内存走漏,是怎么做到的呢?
这须要从上面提到的observe方法中探求答案
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); owner.getLifecycle().addObserver(wrapper);}通报的第一个是 LifecycleOwner,第二个参数Obserser现实就是我们的观察后的回调。这两个参数被封装成了LifecycleBoundObserver对象。
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); if (currentState == DESTROYED) { // Destoryed状态下,自动移除mObserver,制止内存走漏 removeObserver(mObserver); return; } activeStateChanged(shouldBeActive()); ... }这里就表明了为什么LiveData可以或许 自动清除订阅而制止内存走漏 了,由于它内部可以或许感应到Activity大概Fragment的生命周期。
PS:这种筹划非常奇妙,给我们一个开导点:
在我们初识 Lifecycle 组件对它不是明白很透彻的时间,总是下意识以为它可以或许对大的对象举行有用生命周期的管理(比如Presenter),现实上,这种生命周期的管理我们完全可以应用到各个功能的根本组件中,比如大到吃内存的MediaPlayer、绘制筹划复杂的自界说View,小到随处可见的LiveData,都可以通过实现LifecycleObserver接口到达感应生命周期的本领,并内部开释重资源的目的。
小结
LiveData在感知生命周期的本领下,让应用数据发生厘革时通过观察者去更新界面,而且不会出现内存走漏的环境。
Jetpack ViewModel
开端
在没有ViewModel,我们用MVP开发的时间,我们为了实现数据在UI上的展示,通常会写很多UI层和Model层相互调用的代码,这些代码写起来繁琐且一定水平的模版化。别的,某些场景(比方屏幕旋转)烧毁和重新创建界面,那么存储在此中的界面干系数据都会丢失,一样平常都须要手动存储和规复。
为相识决这两个痛点,ViewModel就出场,用ViewModel用于取代MVP中的Presenter
ViewModel 的概念就是如许被提出来的,它就像一个 状态存储器 ,存储着UI中各种各样的状态。
ViewModel的长处
1.更规范化的抽象接口
Google官方建议ViewModel只管包管 纯的业务代码,不要持有任何View层(Activity大概Fragment)或Lifecycle的引用,如许包管了ViewModel内部代码的可测试性,制止由于Context等干系的引用导致测试代码的难以编写(比如,MVP中Presenter层代码的测试就须要额外本钱,比如依靠注入大概Mock,以包管单位测试的举行)。
也正是如许的规范要求,ViewModel不能持有UI层引用,天然也就制止了大概发生的内存走漏。
2.更便于保存数据
当组件被烧毁并重修后,原来组件干系的数据也会丢失。最简单的例子就是屏幕的旋转,如果数据范例比力简单,同时数据量也不大,可以通过onSaveInstanceState()存储数据,组件重修之后通过onCreate(),从中读取Bundle规复数据。但如果是大量数据,不方便序列化及反序列化,则上述方法将不实用。
ViewModel的扩展类则会在这种环境下自动保存其数据,如果Activity被重新创建了,它会收到被之前类似ViewModel实例。当所属Activity制止后,框架调用ViewModel的onCleared()方法开释对应资源。
3.更方便UI组件之间的通讯
一个Activity中的多个Fragment相互通讯是很常见的,如果ViewModel的实例化作用域为Activity的生命周期,则两个Fragment可以持有同一个ViewModel的实例,这也就意味着数据状态的共享。
接下来,分析它的源码是怎么做到这些的:
我们可以通过ViewModelProvider注入ViewModelStoreOwner,从而为引用ViewModel 的页面(比如Activity)创建一个临时的、单独的 ViewModelProvider 实例。并通过这个ViewModelProvider可以获取到ViewModel
# this: ViewModelStoreOwner(interface)ViewModelProvider(this).get(viewModelClass)分创建、获取两步来看,先看创建ViewModelProvider做了什么:
# ViewModelProvider public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { // owner.getViewModelStore(),比如:owner是ComponentActivity this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance());}public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store;}public interface ViewModelStoreOwner { ViewModelStore getViewModelStore();}# ComponentActivity implements ViewModelStoreOwnerpublic ViewModelStore getViewModelStore() { // 为空就创建 ensureViewModelStore(); return mViewModelStore;}void ensureViewModelStore() { if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}这一步是基石:把ViewModelStoreOwner的mViewModelStore绑定到了ViewModelProvider中。简单点说就是同一个ViewModelStoreOwner拿到的是同一个mViewModelStore。
怎样获取对应的ViewModel:
# ViewModelProviderprivate final ViewModelStore mViewModelStore;public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); return get(DEFAULT_KEY + ":" + canonicalName, modelClass);}public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); // 直接返回已存在的viewModel if (modelClass.isInstance(viewModel)) { return (T) viewModel; } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); } // 存储viewModel mViewModelStore.put(key, viewModel); return (T) viewModel;}# ViewModelStorepublic class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); if (oldViewModel != null) { oldViewModel.onCleared(); } }}即通过如许的筹划,来实现类似于单例的效果:每个页面都可以通过ViewModelProvider 注入Activity 这个ViewModelStoreOwner,来共享跨页面的状态;
同时,又不至于完全沦为简单粗暴的单例:每个页面都可以通过 ViewModelProvider 注入this,来管理私有的状态。
比如下面这个详细的例子:
当应用中某个ViewModel 存在既被ViewModelProvider 传入过 Activity,又被传入过某个 Fragment的this 环境,现实上是天生了两个差异的 ViewModel实例,属于差异的 ViewModelStoreOwner。当引用被this 持有的ViewModel 的 页面destory 时,被Activity 持有的ViewModel 的页面并不受影响。
小结
ViewModel是为相识决 “状态管理” 和 “页面通讯” 题目。有了ViewModel,我们在开发的时间,可以大幅减少UI层和Model层相互调用的代码,将更多的重心投入到业务代码的编写。
Jetpack DataBinding
开端
在DataBinding 出现以前,想要更新视图就要引用该视图,然后调用setxxx方法:
TextView textView = findViewById(R.id.sample_text);if (textView != null && viewModel != null) { textView.setText(viewModel.getUserName());}这种方式有几个不好的地方:
- 容易出现空指针(存在差异的横、竖两种布局,如横屏存在此 textView 控件,而竖屏没有),引用该视图一样平常要先判空
- 须要写模板代码 findViewById
- 业务复杂的话,一个控件会在多处调用
DataBinding
DataBinding是个受争议比力大的组件。很多人对 DataBinding 的认知就是在xml中写逻辑:
- 在xml中写表达式逻辑,堕落了debug不了
- 逻辑写在xml里面的话 xml 就负担了 Presenter/ViewModel 的职责,职责变得紊乱了
固然如果站在把逻辑写在xml中的角度看,确实会造成xml中是不能调试的、职责紊乱。
但这不是DataBinding的本质。DataBinding,寄义是 数据绑定,即 布局中的控件 与 可观察的数据 举行绑定。
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}"/>当user.name 被set 新值时,被绑定了该数据的控件即可得到关照和革新。就是说,在利用DataBinding 后,唯一的改变是,你无需手动调用视图来 set 新状态,你只需 set 数据本身。
以是,DataBinding 并非是将 UI 逻辑搬到 XML 中写导致而难以调试 ,它只负责绑定命据,将 UI 控件与其须要的终态数据举行绑定。
双向绑定
上面先容的例子,数据的流向是单向的,只须要监听到数据的变动然后展示到UI上,是个单向绑定。
但有些场景,UI的厘革须要影响到ViewModel层的数据状态,比如UI层的EditText,对它举行编辑并须要更新LiveData的数据。这时就须要 双向绑定。
Android原生控件中,绝大多数的双向绑定利用场景,DataBinding都已经帮我们实现好了,比如EditText
<EditText android:id="@+id/etPassword" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={fragment.viewModel.password }" />相比单向绑定,只须要多一个=符号,就能包管View层和ViewModel层的 状态同步 了
双向绑定利用起来很简单,但界说却稍微比单向绑定贫苦一些,纵然原生的控件DataBinding已经资助我们实现好了,对于三方的控件大概自界说控件,还须要我们本身实现。
举个栗子
这里举个下拉革新SwipeRefreshLayout的例子,来看看双向绑定是怎么实现的:
我们的需求时:当我们为LiveData手动设置值时,SwipeRefreshLayout的UI也会发生对应的变动;反之,当用户手动下拉实验革新操纵时,LiveData的值也会对应的变成为true(代表革新中的状态):
// refreshing现实是一个LiveData:val refreshing: MutableLiveData<Boolean> = MutableLiveData()object SwipeRefreshLayoutBinding { // 1.@BindingAdapter 在数据发生更改时要实验的操纵: // 每当LiveData的状态发生了变动,SwipeRefreshLayout的革新状态也会发生对应的更新。 @JvmStatic @BindingAdapter("app:bind_swipeRefreshLayout_refreshing") fun setSwipeRefreshLayoutRefreshing( swipeRefreshLayout: SwipeRefreshLayout, newValue: Boolean ) { // 判定值是否厘革了,制止无穷循环 if (swipeRefreshLayout.isRefreshing != newValue) swipeRefreshLayout.isRefreshing = newValue } // 2.@InverseBindingAdapter: view视图发生更改时要调用的内容 // 但是它不知道特性何时或怎样更改,以是还须要设置视图监听器 @JvmStatic @InverseBindingAdapter( attribute = "app:bind_swipeRefreshLayout_refreshing", event = "app:bind_swipeRefreshLayout_refreshingAttrChanged" // tag ) fun isSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout): Boolean = swipeRefreshLayout.isRefreshing } // 3\. @BindingAdapter: 事故监听器与相应的 View 实例干系联 // 观察view的状态厘革,每当swipeRefreshLayout革新状态被用户的操纵改变 @JvmStatic @BindingAdapter( "app:bind_swipeRefreshLayout_refreshingAttrChanged", // tag requireAll = false ) fun setOnRefreshListener( swipeRefreshLayout: SwipeRefreshLayout, bindingListener: InverseBindingListener? ) { if (bindingListener != null) // 监听下拉革新 swipeRefreshLayout.setOnRefreshListener { bindingListener.onChange() } }双向绑定将SwipeRefreshLayout的革新状态抽象成为了一个LiveData<Boolean>,我们只须要在xml中界说好,之后就可以在ViewModel中围绕这个状态举行代码的编写。
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:layout_width="match_parent" android:layout_height="match_parent" app:bind_swipeRefreshLayout_refreshing="@={fragment.viewModel.refreshing}"></androidx.swiperefreshlayout.widget.SwipeRefreshLayout>留意事项:制止死循环
双向绑定有一个致命的题目,那就是无穷循环会导致的ANR非常。
当View层UI状态被改变,ViewModel对应发生更新,同时,这个更新又回关照View层去革新UI,这个革新UI的操纵又会关照ViewModel去更新.......
因此,为了包管不会无穷的死循环导致App的ANR非常的发生,我们须要在最初的代码块中加一个判定,包管只有View状态发生了变动,才会去更新UI。
小结
DataBinding通过让 “控件” 与 “可观察数据” 发生绑定,它的本质是将终态数据 绑定到View ,而不是在xml写逻辑,当该数据被 set 新内容时,被绑定该数据的控件即可被关照和革新。
参考
笔者学习过程参考了以下博客,想深入细节的可以看看:
Android官方架构组件ViewModel:从宿世今生到追本溯源
Android官方架构组件DataBinding-Ex: 双向绑定篇
“终于懂了“系列:Jetpack AAC完备分析(一)Lifecycle 完全把握!
“终于懂了“系列:Jetpack AAC完备分析(二)LiveData 完全把握!
作者:树獭非懒
链接:https://juejin.cn/post/7159404464313466894
|