我正在参加「启航方案」

前言

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层接收到用户操作事情,告诉到PresenterPresenter进行逻辑处理,然后告诉Model更新数据,Model 把更新的数据给到PresenterPresenter再告诉到View 更新界面。

MVP实质是面向接口编程,它也存在一些痛点:

  • 会引进很多的IViewIPresenter接口,增加完结的杂乱度。
  • ViewPresenter彼此持有,形成耦合。

跟着开展,Jetpack MVVM 就应势而生,它是MVVM 形式在Android 开发中的一个详细完结,是Google 官方供给并引荐的MVVM完结办法。它的分层:

  • Model层:用于获取和存储数据
  • View层:即Activity/Fragment
  • ViewModel层:担任事务逻辑

MVVM的核心是 数据驱动,把解耦做的更完全(ViewModel不持有view )。

View 产生事情,运用ViewModel进行逻辑处理后,告诉Model更新数据,Model把更新的数据给ViewModelViewModel主动告诉View更新界面

Jetpack Lifecycle

来源

在没有Lifecycle之前,生命周期的办理都是靠手工保持。比方咱们常常会在ActivityonStart初始化某些成员(比方MVPPresenterMediaPlayer)等,然后在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了,然后等一会儿履行过来的时分,myLocationListenerstart,但后边不会再有myLocationListenerstop,这样这个组件的资源就不能正常开释了。假如它内部还持有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());
}

看看它是怎样完结的:

# ComponentActivity
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);  
public Lifecycle getLifecycle() {
  return mLifecycleRegistry;
}
​
# LifecycleRegistry
public 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()

# ComponentActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
  // 要害代码:经过ReportFragment完结生命周期事情分发
  ReportFragment.injectIfNeededIn(this); 
  if (mContentLayoutId != 0) {
    setContentView(mContentLayoutId);
   }
}
# ReportFragment
static 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);
     }
   }
}
​
# LifecycleRegistry
public 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 仅更新处于活泼生命周期状况的运用组件调查者。

假如调查者的生命周期处于 STARTEDRESUMED状况,则 LiveData 会以为该调查者处于活泼状况,就会将更新告诉给活泼的调查者,非活泼的调查者不会收到更改告诉。

LiveData调查者形式 的表现,先从LiveDataobserve办法看起:

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

observeForeverobserve()类似,只不过它会以为调查者一直是活泼状况,不会主动移除调查者。

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等相关的引证导致测验代码的难以编写(比方,MVPPresenter层代码的测验就需求额定本钱,比方依赖注入或许Mock,以确保单元测验的进行)。

也正是这样的规范要求,ViewModel不能持有UI层引证,天然也就防止了可能产生的内存走漏。

2.更便于保存数据

当组件被毁掉并重建后,本来组件相关的数据也会丢掉。最简单的比方便是屏幕的旋转,假如数据类型比较简单,一起数据量也不大,能够经过onSaveInstanceState()存储数据,组件重建之后经过onCreate(),从中读取Bundle康复数据。但假如是很多数据,不便利序列化及反序列化,则上述办法将不适用。

ViewModel的扩展类则会在这种状况下主动保存其数据,假如Activity被重新创立了,它会收到被之前相同ViewModel实例。当所属Activity终止后,结构调用ViewModelonCleared()办法开释对应资源。

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 ViewModelStoreOwner
public ViewModelStore getViewModelStore() {
  // 为空就创立
  ensureViewModelStore();
  return mViewModelStore;
}
​
void ensureViewModelStore() {
 if (mViewModelStore == null) {
   mViewModelStore = new ViewModelStore();
  }
}

这一步是柱石:把ViewModelStoreOwnermViewModelStore绑定到了ViewModelProvider中。简单点说便是同一个ViewModelStoreOwner拿到的是同一个mViewModelStore

怎么获取对应的ViewModel

# ViewModelProvider
private 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;
}
​
# ViewModelStore
public 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,又被传入过某个 Fragmentthis 状况,实践上是生成了两个不同的 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.nameset 新值时,被绑定了该数据的控件即可取得告诉和改写。便是说,在运用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去更新…….

因此,为了确保不会无限的死循环导致AppANR异常的产生,咱们需求在开始的代码块中加一个判断,确保只有View状况产生了改动,才会去更新UI。

小结

DataBinding经过让 “控件” 与 “可调查数据” 产生绑定,它的实质是将终态数据 绑定到View ,而不是在xml写逻辑,当该数据被 set 新内容时,被绑定该数据的控件即可被告诉和改写。

参阅

笔者学习进程参阅了以下博客,想深化细节的能够看看:

Android官方架构组件ViewModel:从宿世今生到追根究底

Android官方架构组件DataBinding-Ex: 双向绑定篇

“总算懂了“系列:Jetpack AAC完好解析(一)Lifecycle 完全把握!

“总算懂了“系列:Jetpack AAC完好解析(二)LiveData 完全把握!