MVVM下的Jetpack焦点组件

程序员 2024-9-20 10:52:40 6 0 来自 中国
媒介

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
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:45, Processed in 0.173189 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表