当前位置:   article > 正文

Android技术栈(四)Android Jetpack MVVM 完全实践

view层与model层没有隔离

1 MVVM总览

本文包含AndroidMVVM体系中的很多部分,主要对ViewModel+DataBinding+RxJava+LiveData+Lifecycle等笔者所使用的技术体系进行解析.

本文字数较多,内容较为完整并且后续还会追加更新,阅读本篇文章需要较长时间,建议读者分段阅读.

所有文字均为个人学习总结和理解,仅供参考,如有纰漏还请指出,笔者不胜感激.

1.1 配置环境

  • 笔者的Android Studio版本=3.2
  • Jetpack最低兼容到Android=2.1,API=7

1.2 为什么要选择MVVM?

要回答这个问题首先就要介绍MVCMVP这两种模式,从MVCMVVM其实大家想的都是怎么把ModelView尽可能的拆开(熟悉三者定义的朋友可以跳过该节).

1.2.1 MVC

MVCModel-View-Controller)即传统Android开发中最常用的模式:

  • 通常使用Activity/Fragment作为Controller层,
  • android.view.View的子类以xml构建文件构建起的布局作为View
  • SQLite数据库,网络请求作为Model层.

但由于Activity/Fragment的功能过于强大并且实际上包含了部分View层功能,导致最后Activity/Fragment既承担了View的责任,又承担了Controller的责任.所以一般较复杂的页面,Activity/Fragment很容易堆积代码,最终导致Controller混杂了View层和业务逻辑(也就是你们所知道的一个Activity三千行)

MVCView层与Model几乎几乎完全没有隔离,View层可以直接操作Model层,Model层的回调里也可能会直接给View赋值.Controller的概念被弱化,最后只剩下MV没有C了.

这也将导致但你想把某个界面上的元素进行更新时,他会牵扯到一堆跟Model层相关的代码,这个问题在你变更Model层的时候同样也会出现,这个问题其实是没有很好的将逻辑分层导致的.

1.2.2 MVP

MVPModel-View-Presenter)架构设计,是当下最流行的开发模式,目前主要以Google推出的TodoMVP为主,MVP不是一种框架,它实际上更类似一种分层思想,一种接口约定,具体体现在下面:

  • 定义IView接口,并且在接口中约定View层的各种操作,使用android.view.View的子类以xml构建文件构建起的布局Activity/Fragment作为布局控制器,实现IView这个View层的接口,View层的实际实现类保留一个IPresenter接口的实例.
  • 定义IPresenter接口,并且在接口中约定Presenter层的各种操作.可以使用一个与View无关的类实现它,一般是XxxPresenterImpl.通常情况下Presenter层会包含Model层的引用和一个IView接口的引用,但不应该直接或者间接引用Viewandroid.view.View的子类,甚至是操作的参数中也最好不要有android.view.View的子类传进来,因为它应该只负责业务逻辑和数据的处理并通过统一的接口IView传递到View层.
  • 不需要为Model层定义一个IModel的接口,这一层是改造最小的.以前该怎么来现在也差不多该怎么来.但是现在Presenter把它和View隔开了,Presenter就可以作为一段独立的逻辑被复用.

MVP模式解决了MVC中存在的分层问题,Presenter层被突出强调,实际上也就是真正意义上实现了的MVC

但是MVP中其实仍然存在一些问题,比如当业务逻辑变得复杂以后,IPresenterIView层的操作数量可能将会成对的爆炸式增长,新增一个业务逻辑,可能要在两边增加数个通信接口,这种感觉很蠢.

并且,我们要知道一个Presenter是要带一个IView的,当一个Presenter需要被复用时,对应的View就要去实现所有这些操作,但往往一些操作不是必须实现的,这样会留下一堆TODO,很难看.

1.2.3 MVVM

MVVMModel-View-ViewModel)由MVP模式演变而来,它由View层,DataBinding,ViewModel层,Model层构成,是MVP的升级版并由GoogleJetpack工具包提供框架支持:

  • View层包含布局,以及布局生命周期控制器(Activity/Fragment)
  • DataBinding用来实现View层与ViewModel数据的双向绑定(但实际上在Android JetpackDataBinding只存在于布局和布局生命周期控制器之间,当数据变化绑定到布局生命周期控制器时再转发给ViewModel,布局控制器可以持有DataBindingViewModel不应该持有DataBinding)
  • ViewModelPresenter大致相同,都是负责处理数据和实现业务逻辑,但是ViewModel层不应该直接或者间接地持有View层的任何引用,因为一个ViewModel不应该直达自己具体是和哪一个View进行交互的.ViewModel主要的工作就是将Model提供来的数据直接翻译成View层能够直接使用的数据,并将这些数据暴露出去,同时ViewModel也可以发布事件,供View层订阅.
  • Model层与MVP中一致.

MVVM的核心思想是观察者模式,它通过事件和转移View数据持有权来实现View层与ViewModel层的解耦.

MVVMView不是数据的实际持有者,它只负责数据如何呈现以及点击事件的传递,不做的数据处理工作,而数据的处理者和持有者变成ViewModel,它通过接收View层传递过来的时间改变自身状态,发出事件或者改变自己持有的数据触发View的更新.

MVVM解决了MVP中的存在的一些问题,比如它无需定义接口,ViewModelView层彻底无关更好复用,并且有GoogleAndroid Jetpack作为强力后援.

但是MVVM也有自己的缺点,那就是使用MVVM的情况下ViewModelView层的通信变得更加困难了,所以在一些极其简单的页面中请酌情使用,否则就会有一种脱裤子放屁的感觉,在使用MVP这个道理也依然适用.

2 DataBinding

2.1 坑

要用一个框架那么就要先说它的点.那就是不建议在使用DataBinding的模块同时使用apply plugin: 'kotlin-kapt'.

因为现在kapt还有很多Bug,使用kapt时,在WindowsDataBinding格式下的xml中如果包含有中文,会报UTF-8相关的错误.

笔者一开始猜想这是由于JVM启动参数没有设置成-Dfile.encoding=UTF-8导致的,在gradle.properties中改过了,无果,Stack Overflow搜过了,没找到,如果有大佬知道怎么解决,还请指点一二

如果你在模块中同时使用kotlinDataBinding是可以的,但是请一定不要使用kapt,除非JB那帮大佬搞定这些奇怪的问题.

这就意味这你所有的kotlin代码都不能依赖注解处理器来为你的代码提供附加功能,但是你可以把这些代码换成等价的Java实现,它们可以工作得很好.

2.2 DataBinding的兼容性

先说一点,DataBinding风格的xml会有"奇怪"的东西入侵Android原生的xml格式,这种格式LayoutInfalter是无法理解,但是,当你对这些奇怪的xml使用LayoutInfalter#inflate时亦不会报错,并且布局也正常加载了,这是为什么呢?

这是因为在打包时,Gradle通过APT把你的DataBinding风格的xml全部翻译了一遍,让LayoutInfalter能读懂他们,正是因为这个兼容的实现,而使得我们可以在使用和不使用DataBinding间自由的切换.

2.3 DataBinding风格的XML

要想使用DataBinding,先在模块的build.gradle中添加

  1. android{
  2. //省略...
  3. dataBinding {
  4. enabled = true
  5. }
  6. }
  7. 复制代码

来启用DataBinding支持.

DataBinding不需要额外的类库支持,它被附加在你的android插件中,它的版本号与你的android插件版本一致.

  1. classpath 'com.android.tools.build:gradle:3.3.2'
  2. 复制代码

DataBinding风格的xml中,最外层必须是layout标签,并且不支持merge标签,编写xml就像下面这样

  1. <layout
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto">
  4. <data>
  5. <variable
  6. name="text"
  7. type="String"/>
  8. <variable
  9. name="action"
  10. type="android.view.View.OnClickListener"/>
  11. </data>
  12. <TextView
  13. android:onClick="@{action}"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"/>
  16. <layout/>
  17. 复制代码
2.3.1 变量领域

data标签包裹的是变量领域,在这里你可以使用variable定义这个布局所要绑定的变量类型,使用name来指定变量名,然后用type来指定其类型.

如果一些类型比较长,而且由需要经常使用你可以像Java一样使用import导入他们(java.lang.*会被默认导入),然后就不用写出完全限定名了,就像这样

  1. <import
  2. type="android.view.View"
  3. alias="Action"/>
  4. <variable
  5. name="action"
  6. type="Action"/>
  7. 复制代码

有必要时(比如名字冲突),你还可以用Action为一个类型指定一个别名,这样你就能在下文中使用这个别名.

2.3.2 转义字符

熟悉xml的同学可能都知道<>xml中是非法字符,那么要使用泛型的时候,我们就需要使用xml中的转义字符&lt;&gt;来进行转义

  1. //↓错误,编译时会报错×
  2. <variable
  3. name="list"
  4. type="java.util.List<String>"/>
  5. //↓正确,可以通过编译√
  6. <variable
  7. name="list"
  8. type="java.util.List&lt;String&gt;"/>
  9. 复制代码

data标签结束后就是原本的布局编写的位置了,这部分基本和以前差不多,只是加入了DataBinding表达式

  1. <data>
  2. //......
  3. <data/>
  4. <TextView
  5. android:onClick="@{action}"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"/>
  8. 复制代码
2.3.3 DataBinding表达式

@{}包裹的位置被称为DataBinding表达式,DataBinding表达式几乎支持Java所有的运算符,并且增加了一些额外的操作,这允许我们在xml中有一定的Java编程体验,学过Java web的同学可能会觉得它很像JSP:

  • 不需要xml转义的二元运算+,-,/,*,%,||,|,^,==
  • 需要xml转义的二元运算&&,>> >>>,<<,>,<,>=,<=,与泛型一样运算符>=,>,<,<=等,也是需要转义的,&需要用&amp;转义,这确实有些蹩脚,但这是xml的局限性,我们无法避免,所以在DataBinding风格的xml中应该尽可能的少用这些符号.
  • lambda表达式@{()->persenter.doSomething()}
  • 三元运算?:
  • null合并运算符??,若左边不为空则选择左边,否则选择右边
  1. android:text="@{nullableString??`This a string`}"
  2. 复制代码
  • 自动导入的context变量,你可以在xml中的任意表达式使用context这个变量,该Context是从该布局的根ViewgetContext获取的,如果你设置了自己的context变量,那么将会覆盖掉它
  • 若表达式中有字符串文本xml需要特殊处理
  1. 用单引号包围外围,表达式使用双引号
  2. android:text='@{"This a string"}'
  3. 或者使用`包围字符串,对,就Esc下面那个键的符号
  4. android:text="@{`This a string`}"
  5. 复制代码
  • 判断类型instanceof
  • 括号()
  • 空值null
  • 方法调用,字段访问,以及GetterSetter的简写,比如User#getNameUser#setName现在都可以直接写成@{user.name},这种表达式也是最简单的表达式,属于直接赋值表达式
  • 默认值default,在xml
  1. `android:text="@{file.name, default=`no name`}"`
  2. 复制代码
  • 下标[],不只是数组,List,SparseArray,Map现在都可以使用该运算符
  • 使用@读取资源文件,如下,但是不支持读取mipmap下的文件
  1. android:text="@{@string/text}"
  2. //或者把它作为表达式的一部分
  3. android:padding="@{large? @dimen/large : @dimen/small}"
  4. 复制代码

有一些资源需要显示引用

类型正常情况DataBinding表达式引用
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
ColorStateList@animator@stateListAnimator
StateListAnimator@color@colorStateList

还有一些操作是DataBinding表达式中没有的,我们无法使用它们:

  • 没有this
  • 没有super
  • 不能创建对象new
  • 不能使用泛型方法的显示调用Collections.<String>emptyList()

编写简单的DataBinding表达式,就像下面这样

  1. <data>
  2. <improt type="android.view.View"/>
  3. <variable
  4. name="isShow"
  5. type="Boolean"/>
  6. <data/>
  7. <TextView
  8. android:visibility="@{isShow?View.VISIBLE:View.GONE}"
  9. android:text="@{@string/text}"
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"/>
  12. 复制代码

应该避免出现较为复杂的DataBinding表达式,以全部都是直接赋值表达式为佳,数据的处理应该交给布局控制器或者ViewModel来做,布局应该只负责渲染数据.

2.3.4 使用在Java中生成的ViewDataBinding

使用DataBindingAndroid Studio会为每个xml布局生成一个继承自ViewDataBinding的子类型,来帮助我们将xml文件中定义的绑定关系映射到Java中.

比如,如果你有一个R.layout.fragment_main的布局文件,那么他就会为你在当前包下生成一个,FragmentMainBindingViewDataBinding.

Java实化DataBinding风格xml布局与传统方式有所不同.

  • Actvity
  1. private ActivityHostBinding mBinding;
  2. @Override
  3. protected void onCreate(@Nullable Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. mBinding = DataBindingUtil.setContentView(this, R.layout.activity_host);
  6. }
  7. 复制代码
  • 在自定义ViewFragment
  1. private FragmentMainBinding mBinding;
  2. @Nullable
  3. @Override
  4. public View onCreateView(@NonNull LayoutInflater inflater,
  5. @Nullable ViewGroup container,
  6. @Nullable Bundle savedInstanceState) {
  7. mBinding = DataBindingUtil.inflate(inflater,
  8. R.layout.fragment_main,
  9. container,
  10. false);
  11. return mBinding.getRoot();
  12. }
  13. 复制代码
  • 在已经使用普通LayoutInfalter实例化的View上(xml必须是DataBinding风格的,普通LayoutInflater实例化布局时不会触发任何绑定机制,DataBindingUtil#bind才会发生绑定)
  1. View view = LayoutInflater.from(context).inflate(R.layout.item_view,null,false);
  2. ItemViewBinding binding = DataBindingUtil.bind(view);
  3. 复制代码

你在xml设置的变量他会在这个类中为你生成对应的GetterSetter.你可以调用它们给界面赋值,比如之前的我们定义的action.

  1. //这里的代码是Java8的lambda
  2. mBinding.setAction(v->{
  3. //TODO
  4. })
  5. 复制代码
2.3.5 使用BR文件

它还会为你生成一个类似RBR文件,里面包含了你在DataBinding风格xml中定义的所有变量名的引用(由于使用的是APT生成,有时候需要Rebuild Project才能刷新),比如我们之前的action,它会为我们生成BR.action,我们可以这么使用它

  1. mBinding.setVariable(BR.action,new View.OnClickListener(){
  2. @Override
  3. void onClick(View v){
  4. //TODO
  5. }
  6. })
  7. 复制代码
2.3.6 传递复杂对象

在之前给xml中的变量中赋值时,我们用的都是一些类似String的简单对象,其实我们也可以定义一些复杂的对象,一次性传递到xml布局中

  1. //java
  2. public class File
  3. {
  4. public File(String name,
  5. String size,
  6. String path)
  7. {
  8. this.name = name;
  9. this.size = size;
  10. this.path = path;
  11. }
  12. public final String name;
  13. public final String size;
  14. public final String path;
  15. }
  16. //xml
  17. <data>
  18. <variable
  19. name="file"
  20. type="org.kexie.android.sample.bean.File"/>
  21. <data/>
  22. <LinearLayout
  23. android:orientation="vertical"
  24. android:layout_width="match_parent"
  25. android:layout_height="match_parent"/>
  26. <TextView
  27. android:text="@{file.name}"
  28. android:layout_width="match_parent"
  29. android:layout_height="wrap_content"/>
  30. <TextView
  31. android:text="@{file.size}"
  32. android:layout_width="match_parent"
  33. android:layout_height="wrap_content"/>
  34. <TextView
  35. android:text="@{file.path}"
  36. android:layout_width="match_parent"
  37. android:layout_height="wrap_content"/>
  38. <LinearLayout/>
  39. 复制代码

个人认为绑定到xml中的数据最好是不可变的,所以上面的字段中我使用了final,但这不是必须的,根据你自己的需求来进行定制

2.3.7 绑定并非立即发生

这里有一点值得注意的是,你给ViewDataBinding的赋值并不是马上生效的,而是在当前方法执行完毕回到事件循环后,并保证在下一帧渲染之前得到执行,如果需要立即执行,请调用ViewDataBinding#executePendingBindings

2.3.8 使用android:id

如果你使用了android:id,那么这个View就也可以当成一个变量在下文的DataBinding表达式中使用,就像写Java.它还会帮你View绑定到ViewDataBinding中,你可以这么使用它们

  1. //xml
  2. <TextView
  3. android:id="@+id/my_text"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_context"/>
  6. <TextView
  7. android:id="@+id/my_text2"
  8. android:text="@{my_text.getText()}"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_context"/>
  11. //在java中my_text被去掉下划线,更符合java的命名习惯
  12. mBinding.myText.setText("This is a new text");
  13. 复制代码

用过ButterKnife的同学可能都知道,ButterKnife出过一次与gradle版本不兼容的事故,但是DataBinding是与gradle打包在一起发布的,一般不会出现这种问题,如果你不想用ButterKnife但有不想让DataBinding的风格的写法入侵你的xml太狠的话,只使用android:id将会是一个不错的选择.

2.4 正向绑定

某些第三方View是肯定没有适配DataBinding的,业界虽然一直说MVVM好,但现在MVP的开发方式毕竟还是主流,虽然这种情况我们可以用android:id,然后在Activity/Fragment中解决,但有时候我们想直接在xml中配置,以消除一些样板代码,这时候就需要自定义正向绑定.

2.4.1 自定义正向绑定适配器

我们可以使用@BindingAdapter自定义在xml中可使用的View属性,名字空间是不需要的,加了反而还会给你警告.

  1. @Target(ElementType.METHOD)
  2. public @interface BindingAdapter {
  3. /**
  4. * 与此绑定适配器关联的属性。
  5. */
  6. String[] value();
  7. /**
  8. * 是否必须为每个属性分配绑定表达式,或者是否可以不分配某些属性。
  9. * 如果为false,则当至少一个关联属性具有绑定表达式时,将调用BindingaAapter。
  10. */
  11. boolean requireAll() default true;
  12. }
  13. //@BindingAdapter需要一个静态方法,该方法的第一个参数是与该适配器兼容的View类型
  14. //从第二个参数开始,依次是你自定义的属性传进来的值.
  15. //使用requireAll来指定这些属性是全部需要,还是只要一个就可以
  16. //如果requireAll = false,触发适配器绑定时,没有被设置的属性将获得该类型的默认值
  17. //框架优先使用自定义的适配器处理绑定
  18. @BindingAdapter(value = {"load_async", "error_handler"},requireAll = true)
  19. public static void loadImage(ImageView view, String url, String error) {
  20. Glide.with(view)
  21. .load(url)
  22. .error(Glide.with(view).load(error))
  23. .into(view);
  24. }
  25. //在xml中使用它(下面那两个网址都不是实际存在的)
  26. <ImageView
  27. load_async="@{`http://android.kexie.org/image.png`}"
  28. error_handler="@{`http://android.kexie.org/error.png`}"
  29. android:layout_width="match_parent"
  30. android:layout_height="match_parent"/>
  31. 复制代码
2.4.2 第三方View适配

DataBinding风格的xml还能在一定程度上适配第三方View

  1. //如果你的自定义View中有这么一个Setter↓
  2. public class RoundCornerImageView extends AppCompatImageView{
  3. //......
  4. public void setRadiusDp(float dp){
  5. //TODO
  6. }
  7. }
  8. //那么你可以在xml中使用radiusDp来使用它
  9. <org.kexie.android.ftper.widget.RoundCornerImageView
  10. radiusDp="@{100}"
  11. android:id="@+id/progress"
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent"
  14. android:layout_gravity="center"
  15. android:scaleType="centerCrop"
  16. android:src="@drawable/progress"/>
  17. //它会自己为你去找名称为setRadiusDp并且能接受100为参数的方法.
  18. 复制代码
2.4.3 xml中的属性重定向

使用@BindingMethod来将xml属性重定向:

  1. @Target(ElementType.ANNOTATION_TYPE)
  2. public @interface BindingMethod {
  3. //需要重定向的View类型
  4. Class type();
  5. //需要重定向的属性名
  6. String attribute();
  7. //需要重定向到的方法名
  8. String method();
  9. }
  10. //这是DataBinding源码中,DataBinding对于系统自带的TextView编写的适配器
  11. //这是androidx.databinding.adapters.TextViewBindingAdapter的源码
  12. @BindingMethods({
  13. @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
  14. @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
  15. @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
  16. //......
  17. })
  18. public class TextViewBindingAdapter {
  19. //......
  20. }
  21. //这样就可以建立起xml中属性与View中Setter的联系
  22. 复制代码
2.4.4 添加转换层

使用@BindingConversion为添加转换层

  1. @BindingConversion
  2. public static ColorDrawable toDrawable(int color) {
  3. return new ColorDrawable(color);
  4. }
  5. //可以把color整形转换为android:src可接受的ColorDrawable类型
  6. //但是转换只适用于直接的赋值
  7. //如果你写了复杂的表达式,比如使用了?:这种三元运算符
  8. //那就照顾不到你了
  9. 复制代码

2.5 反向绑定

有正向绑定就一定有反向绑定,正向绑定和反向绑定一起构成了双向绑定.

在我们之前编写的DataBinding表达式中,比如TextViewandroid:text之类的属性我们都是直接赋值一个String过去的,这就是正向绑定,我们给View的值能够直接反应到View上,而反向绑定就是View值的变化和也能反应给我们.

2.5.1 使用双向绑定

所有使用之前所有使用@{}包裹的都是正向绑定,而双向绑定是@={},并且只支持变量,字段,Setter(比如User#setName,就写@={user.name})的直接编写并且不支持复杂表达式

2.5.2 兼容LiveData与ObservableField

实际上,android:text不只能接受String,当使用双向绑定时,它也能接受MutableLiveData<String>ObservableField<String>作为赋值对象,这种赋值会将TextViewandroid:text的变化绑定到LiveData(实际上是MutableLiveData)或者是ObservableField上,以便我们在View的控制层(Activity/Fragment)更好地观察他们的变化.

当然除了ObservableFieldandroidx.databinding包下还有不装箱的ObservableInt,ObservableFloat等等.

但是为了支持LiveData我们必须开启第二版的DataBinding APT.

在你的gradle.properties添加

  1. android.databinding.enableV2=true
  2. 复制代码

现在我们可以通过LiveData(实际上是MutableLiveData)android:text的变化绑定到Activity/Fragment

  1. //xml
  2. <data>
  3. <variable
  4. name="liveText"
  5. type="MutableLiveData&lt;String&gt;">
  6. <data/>
  7. <TextView
  8. android:text="@={text}"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_context"/>
  11. //然后在Activity/Fragment中
  12. MutableLiveData<String> liveText = new MutableLiveData<String>();
  13. mBinding.setLiveText(liveText);
  14. liveText.observe(this,text->{
  15. //TODO 观察View层变化
  16. });
  17. 复制代码
2.5.3 自定义反向绑定适配器

下面我们回到androidx.databinding.adapters.TextViewBindingAdapter的源码,继续对自定义反向绑定适配器进行分析.

  1. //我们可以看到源码中使用了@InverseBindingAdapter自定义了一个反向绑定器
  2. //指定了其属性以及相关联的事件
  3. @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
  4. public static String getTextString(TextView view) {
  5. return view.getText().toString();
  6. }
  7. //并为这个事件添加了一个可接受InverseBindingListener的属性
  8. //为了说明方便,下面的代码已简化,源码并非如此,但主要逻辑相同
  9. @BindingAdapter(value = {"android:textAttrChanged"})
  10. public static void setTextWatcher(TextView view , InverseBindingListener textAttrChanged){
  11. view.addTextChangedListener(new TextWatcher(){
  12. //......
  13. @Override
  14. public void onTextChanged(CharSequence s, int start, int before, int count) {
  15. textAttrChanged.onChange();
  16. }
  17. });
  18. }
  19. //至此android:text的反向绑定完成
  20. //当你使用@={}时实际上是用android:textAttrChanged属性向TextView设置了TextWatcher
  21. //传入的InverseBindingListener是反向绑定监听器
  22. //当调用InverseBindingListener的onChange时
  23. //会调用@BindingAdapter所注解的方法将获得数据并写回到变量中.
  24. 复制代码

2.6 配合DataBinding打造通用RecyclerView.Adapter

下面进行一个小小的实战吧,我们可以站在巨人的肩膀上造轮子.

  1. //导入万能适配器作为基类,可以大大丰富我们通用适配器的功能
  2. implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46'
  3. 复制代码

由于基类很强大所以代码不多:

  1. //X是泛型,可以是你在item中所使用的java bean
  2. public class GenericQuickAdapter<X>
  3. extends BaseQuickAdapter<X, GenericQuickAdapter.GenericViewHolder> {
  4. //BR中的变量名
  5. protected final int mName;
  6. //layoutResId是DataBinding风格的xml
  7. public GenericQuickAdapter(int layoutResId, int name) {
  8. super(layoutResId);
  9. mName = name;
  10. openLoadAnimation();
  11. }
  12. @Override
  13. protected void convert(GenericViewHolder helper, X item) {
  14. //触发DataBinding
  15. helper.getBinding().setVariable(mName, item);
  16. }
  17. public static class GenericViewHolder extends BaseViewHolder {
  18. private ViewDataBinding mBinding;
  19. public GenericViewHolder(View view) {
  20. super(view);
  21. //绑定View获得ViewDataBinding
  22. mBinding = DataBindingUtil.bind(view);
  23. }
  24. @SuppressWarnings("unchecked")
  25. public <T extends ViewDataBinding> T getBinding() {
  26. return (T) mBinding;
  27. }
  28. }
  29. }
  30. //实例化
  31. GenericQuickAdapter<File> adapter = new GenericQuickAdapter<>(R.layout.item_file,BR.file);
  32. //在xml中使用起来就像这样
  33. <layout>
  34. <data>
  35. <variable
  36. name="file"
  37. type="org.kexie.android.sample.bean.File"/>
  38. <data/>
  39. <LinearLayout
  40. android:orientation="vertical"
  41. android:layout_width="match_parent"
  42. android:layout_height="match_parent"/>
  43. <TextView
  44. android:text="@{file.name}"
  45. android:layout_width="match_parent"
  46. android:layout_height="wrap_content"/>
  47. <TextView
  48. android:text="@{file.size}"
  49. android:layout_width="match_parent"
  50. android:layout_height="wrap_content"/>
  51. <TextView
  52. android:text="@{file.path}"
  53. android:layout_width="match_parent"
  54. android:layout_height="wrap_content"/>
  55. <LinearLayout/>
  56. <layout/>
  57. 复制代码

3 Lifecycle

Android中,组件的管理组件的生命周期一直是一个比较麻烦的东西,而自Google推出Android Jetpack组件包以来,这个问题得到的比较妥善的解决,Lifecycle组件后来也成为Android Jetpack的核心。

3.1 导入

AndroidX为例,要使用Lifecycle组件,先在模块的build.gradle文件中添加依赖:

  1. api 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha02'
  2. 复制代码

由于Lifecycle组件由多个包构成,使用api导入时即可将其依赖的包全部导入该模块,包括commonlivedataprocessruntimeviewmodelservice等。

如果要使用Lifecycle中的注解,你还需要添加如下注解处理器,以便在编译时,完成对相应注解的处理。

  1. annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.0.0'
  2. 复制代码

对于一个App来说,使用Lifecycle组件是没有任何侵入性的,因为他已经天然的融合到Googleappcompat库中了,而如今无论是什么应用程序都几乎离不开appcompat,可以说集成Lifecycle只是启用了之前没用过的功能罢了。

3.2 LifecycleOwner

LifecycleOwnerLifecycle组件包中的一个接口,所有需要管理生命周期的类型都必须实现这个接口。

  1. public interface LifecycleOwner
  2. {
  3. /**
  4. * Returns the Lifecycle of the provider.
  5. *
  6. * @return The lifecycle of the provider.
  7. */
  8. @NonNull
  9. Lifecycle getLifecycle();
  10. }
  11. 复制代码

但其实很多时候我们根本无需关心LifecycleOwner的存在。在Android中, FragmentActivityService都是具有生命周期的组件,但是Google已经让他们都实现了LifecycleOwner这个接口,分别是androdx.fragment.app.FragmentAppCompatActivityandroidx.lifecycle.LifecycleService.

在项目中,只要继承这些类型,可以轻松的通过LifecycleOwner#getLifecycle()获取到Lifecycle实例.这是一种解耦实现,LifecycleOwner不包含任何有关生命周期管理的逻辑,实际的逻辑都在Lifecycle实例中,我们可以通过传递Lifecycle实例而非LifecycleOwner来防止内存泄漏.

Lifecycle这个类的只有这三个方法:

  1. @MainThread
  2. public abstract void removeObserver(@NonNull LifecycleObserver observer);
  3. @MainThread
  4. @NonNull
  5. public abstract State getCurrentState();
  6. @MainThread
  7. public abstract void addObserver(@NonNull LifecycleObserver observer);
  8. 复制代码

getCurrentState()可以返回当前该LifecycleOwner的生命周期状态,该状态与LifecycleOwner上的某些回调事件相关,只会出现以下几种状态,在Java中以一个枚举类抽象出来定义在Lifecycle类中。

  1. public enum State
  2. {
  3. DESTROYED,
  4. INITIALIZED,
  5. CREATED,
  6. STARTED,
  7. RESUMED;
  8. }
  9. 复制代码
  • DESTROYED,在组件的onDestroy调用前,会变成该状态,变成此状态后将不会再出现任何状态改变,也不会发送任何生命周期事件

  • INITIALIZED,构造函数执行完成后但onCreate未执行时为此状态,是最开始时的状态

  • CREATED,在onCreate调用之后,以及onStop调用前会变成此状态

  • STARTED,在onStart调用之后,以及onPause调用前会变成此状态

  • RESUMED,再onResume调用之后会变成此状态

addObserver,此方法可以给LifecycleOwner添加一个观察者,来接收LifecycleOwner上的回调事件。回调事件也是一个枚举,定义在Lifecycle类中:

  1. public enum Event
  2. {
  3. /**
  4. * Constant for onCreate event of the {@link LifecycleOwner}.
  5. */
  6. ON_CREATE,
  7. /**
  8. * Constant for onStart event of the {@link LifecycleOwner}.
  9. */
  10. ON_START,
  11. /**
  12. * Constant for onResume event of the {@link LifecycleOwner}.
  13. */
  14. ON_RESUME,
  15. /**
  16. * Constant for onPause event of the {@link LifecycleOwner}.
  17. */
  18. ON_PAUSE,
  19. /**
  20. * Constant for onStop event of the {@link LifecycleOwner}.
  21. */
  22. ON_STOP,
  23. /**
  24. * Constant for onDestroy event of the {@link LifecycleOwner}.
  25. */
  26. ON_DESTROY,
  27. /**
  28. * An {@link Event Event} constant that can be used to match all events.
  29. */
  30. ON_ANY
  31. }
  32. 复制代码

每种事件都对应着Fragment/Activity中的事件。

3.3 LifecycleObserver

LifecycleObserver是生命周期的观察者,可能是这个包中我们最常用的接口了.

查看源码得知,他就是一个空接口,不包含任何实现,但是若我们想使用,还是得继承此接口。

  1. public interface LifecycleObserver { }
  2. 复制代码

继承LifecycleObserver后使用@OnLifecycleEvent注解(这时之前申明得注解处理器派上了用场),并设置需要监听的生命周期回调事件。

  1. @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
  2. public void test()
  3. {
  4. ///TODO...
  5. }
  6. 复制代码

然后在Activity/Fragment中:

  1. getLifecycle().addObserver(yourLifecycleObserver);
  2. 复制代码

即可在运行时收到相应的的回调事件,但是注意添加@OnLifecycleEvent注解的方法应该是包内访问权限或是public的,否则可能在编译时会报错,或者收不到回调。

若想在运行时移除LifecycleObserver,同样也还有Lifecycle#removeObserver方法。

4 LiveData

LiveData是对Android组件生命周期感知的粘性事件,也就是说,在LiveData持有数据时,你去订阅它就能收到他最后一次接收到的数据.在实战中,我们能用到的LiveData一般是它的两个子类MutableLiveDataMediatorLiveData.

4.1 LiveData基本使用

我们可以通过LiveData#observe来观察它所持有的值的变化,还可以通过LiveData#getValue来直接获取内部保存的值(非线程安全)

  1. //LiveData 一般是用来给ViewModel保存数据的
  2. public class MyViewModel extends ViewModel{
  3. private MutableLiveData<Boolean> mIsLoading = new MutableLiveData<>();
  4. LiveData<Boolean> isLoading(){
  5. return mIsLoading;
  6. }
  7. }
  8. //Activity/Fragment观察ViewModel
  9. mViewModel.isLoading().observe(this, isLoading -> {
  10. //TODO 发生在主线程,触发相关处理逻辑
  11. });
  12. //LiveData是依赖Lifecycle实现的
  13. //传入的this是LifecycleOwner
  14. //LiveData只会通知激活态的(STARTED和RESUMED)的LifecycleOwner
  15. //并且在Activity/Fragment被重建也能重新接收到LiveData保存的数据
  16. //在组件DESTROYED时,LiveData会把它移出观察者列表
  17. //当然你也可以不关联LifecycleOwner,让订阅一直保持.
  18. //需要这样时需要使用observeForever
  19. mViewModel.isLoading().observeForever(isLoading -> {
  20. //TODO
  21. });
  22. //这个订阅永远不会被取消
  23. //除非你显示调用LiveData#removeObserver
  24. 复制代码

4.2 MutableLiveData

顾名思义就是可变的LiveData,基类LiveData默认是不可变的,MutableLiveData开放了能够改变其内部所持有数据的接口.

  1. public class MutableLiveData<T> extends LiveData<T> {
  2. /**
  3. * Creates a MutableLiveData initialized with the given {@code value}.
  4. *
  5. * @param value initial value
  6. */
  7. public MutableLiveData(T value) {
  8. super(value);
  9. }
  10. /**
  11. * Creates a MutableLiveData with no value assigned to it.
  12. */
  13. public MutableLiveData() {
  14. super();
  15. }
  16. @Override
  17. public void postValue(T value) {
  18. super.postValue(value);
  19. }
  20. @Override
  21. public void setValue(T value) {
  22. super.setValue(value);
  23. }
  24. }
  25. 复制代码

分别是postValuesetValue,其中setValue内部检查线程是否为主线程,不允许在子线程中使用,用了就报错.postValue会将值通过主线程的Handler转发到主线程上.

LiveData可以有初始值,也可以没有,如果在没有初始值的情况下被订阅,则订阅者不会收到任何的值.

4.3 MediatorLiveData

MediatorLiveData继承自MutableLiveData,它主要用来实现多个LiveData数据源的合并.

  1. public class MediatorLiveData<T> extends MutableLiveData<T> {
  2. private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
  3. @MainThread
  4. public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
  5. Source<S> e = new Source<>(source, onChanged);
  6. Source<?> existing = mSources.putIfAbsent(source, e);
  7. if (existing != null && existing.mObserver != onChanged) {
  8. throw new IllegalArgumentException(
  9. "This source was already added with the different observer");
  10. }
  11. if (existing != null) {
  12. return;
  13. }
  14. if (hasActiveObservers()) {
  15. e.plug();
  16. }
  17. }
  18. @MainThread
  19. public <S> void removeSource(@NonNull LiveData<S> toRemote) {
  20. Source<?> source = mSources.remove(toRemote);
  21. if (source != null) {
  22. source.unplug();
  23. }
  24. }
  25. @CallSuper
  26. @Override
  27. protected void onActive() {
  28. for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
  29. source.getValue().plug();
  30. }
  31. }
  32. @CallSuper
  33. @Override
  34. protected void onInactive() {
  35. for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
  36. source.getValue().unplug();
  37. }
  38. }
  39. private static class Source<V> implements Observer<V> {
  40. final LiveData<V> mLiveData;
  41. final Observer<? super V> mObserver;
  42. int mVersion = START_VERSION;
  43. Source(LiveData<V> liveData, final Observer<? super V> observer) {
  44. mLiveData = liveData;
  45. mObserver = observer;
  46. }
  47. void plug() {
  48. mLiveData.observeForever(this);
  49. }
  50. void unplug() {
  51. mLiveData.removeObserver(this);
  52. }
  53. @Override
  54. public void onChanged(@Nullable V v) {
  55. if (mVersion != mLiveData.getVersion()) {
  56. mVersion = mLiveData.getVersion();
  57. mObserver.onChanged(v);
  58. }
  59. }
  60. }
  61. }
  62. 复制代码

它比MutableLiveData多了两个方法addSourceremoveSource,通过这两个方法我们可以将其他LiveData合并到此LiveData上,当其他LiveData发生改变时,此LiveData就能收到通知.

  1. @MainThread
  2. public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged)
  3. @MainThread
  4. public <S> void removeSource(@NonNull LiveData<S> toRemote)
  5. 复制代码

通过查看源码,我们可以知道在有观察者时LiveData#onActive会被回调,MediatorLiveData会在内部迭代,用observeForever订阅所有被合并进来的LiveData,这样就能接收所有LiveData的变化,在没有观察者时LiveData#onInactive会被回调,此时执行反操作removeObserver.

4.4 变换

使用androidx.lifecycle.Transformations这个工具类可以将持有一种类型的LiveData转换为另一种LiveData.他有类似于RxJava的使用方式.

  1. LiveData<Boolean> boolLiveData = getBoolLiveData();
  2. LiveData<String> stringLiveData = Transformations.map(boolLiveData,bool->Boolean.toString(bool));
  3. 复制代码

上面只是一个演示,实际上可以执行更为复杂的逻辑,并且这种转换是惰性的,在没有激活态观察者时,这种转换不会发生.

5 ViewModel

5.1 自定义ViewModel

ViewModel其实没什么可说的,其源码主要的部分其实就只有这些

  1. public abstract class ViewModel {
  2. protected void onCleared() {
  3. }
  4. }
  5. 复制代码

简直一目了然,我们可以在ViewModel上使用LiveData作为字段保存数据,并编写业务逻辑(数据处理逻辑).就像这样

  1. public class MyViewModel extends ViewModel
  2. {
  3. public MutableLiveData<String> username = new MutableLiveData<>();
  4. public MutableLiveData<String> password = new MutableLiveData<>();
  5. public MutableLiveData<String> text = new MutableLiveData<>();
  6. public void action1(){
  7. //TODO
  8. }
  9. public void initName(){
  10. username.setValue("Luke Luo");
  11. }
  12. //......
  13. @Override
  14. protected void onCleared() {
  15. //TODO 清理资源
  16. }
  17. }
  18. 复制代码

onCleared会在组件销毁的时候回调,我们可以重写这个方法在ViewModel销毁时添加一些自定义清理逻辑.

ViewModel还有一个子类AndroidViewModel也是一目了然,只是保存了Application实例而已.

  1. public class AndroidViewModel extends ViewModel {
  2. @SuppressLint("StaticFieldLeak")
  3. private Application mApplication;
  4. public AndroidViewModel(@NonNull Application application) {
  5. mApplication = application;
  6. }
  7. /**
  8. * Return the application.
  9. */
  10. @SuppressWarnings("TypeParameterUnusedInFormals")
  11. @NonNull
  12. public <T extends Application> T getApplication() {
  13. //noinspection unchecked
  14. return (T) mApplication;
  15. }
  16. }
  17. 复制代码

5.2 自定义ViewModel构造方式

我们可以通过ViewModelProviders来获取ViewModel,这样获取的ViewModel会绑定组件的生命周期(即在销毁时自动调用onCleared)

  1. mViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
  2. 复制代码

AndroidLifecycle实现中框架向Activity中添加了一个继承了系统FragmentReportFragment来汇报组件的生命周期,如果你使用的是appcompatFragment,那么它对你就是不可见的,所以一定要避免使用系统的Fragment(在API28中已被标记为弃用).

ViewModel通过Lifecycle来管理自身释放,在组件的ON_DESTROY事件来到时,它的onCleared()也会被调用.

如果你想有自定义构造函数参数的ViewModel那你就得继承ViewModelProvider.AndroidViewModelFactory

  1. //自定义构造函数的ViewModel
  2. public class NaviViewModel extends AndroidViewModel
  3. {
  4. private AMapNavi mNavi;
  5. public NaviViewModel(AMapNavi navi,Application application)
  6. {
  7. super(application);
  8. mNavi = navi;
  9. }
  10. //......
  11. }
  12. //继承并重写create
  13. public final class NaviViewModelFactory
  14. extends ViewModelProvider.AndroidViewModelFactory
  15. {
  16. private final AMapNavi navi;
  17. private final Application application;
  18. public NaviViewModelFactory(@NonNull Context context, AMapNavi navi)
  19. {
  20. super((Application) context.getApplicationContext());
  21. this.application = (Application) context.getApplicationContext();
  22. this.navi = navi;
  23. }
  24. @NonNull
  25. @Override
  26. public <T extends ViewModel> T create(@NonNull Class<T> modelClass)
  27. {
  28. try
  29. {
  30. Constructor<T> constructor = modelClass
  31. .getConstructor(Application.class, AMapNavi.class);
  32. return constructor.newInstance(application, navi);
  33. } catch (Exception e)
  34. {
  35. return super.create(modelClass);
  36. }
  37. }
  38. }
  39. //使用
  40. NaviViewModelFactory factory = new NaviViewModelFactory(context, navi);
  41. mViewModel = ViewModelProviders.of(this, factory).get(NaviViewModel.class);
  42. 复制代码

说白了就是反射调用构造函数创建,也是一目了然.

6 RxJava

本篇文章只是针对响应式编程在MVVM体系下的应用,不对RxJava展开深度讨论,但是后面还会专门出一篇文章讨论RxJava的有关知识.

RxJavaMVVM中主要用于发布事件,下面是需要注意的一些点.

6.1 使用AutoDispose

RxJava是响应式编程这种思想在JVM这个平台上的实现,所以它一开始并没有为Android平台的特点而做出优化.

就像上面所介绍过的一样,Android的组件是有明确的生命周期的,如果在组件销毁后,RxJava仍有后台线程在运行且你的Observer引用了你的Activity,就会造成内存泄漏.

但其实RxJava是提供了释放机制的,那就是Disposeable,只不过这个实现这个机制的逻辑需要我们手动在Activity#onDestroy中进行硬编码,这会带来大量的样板代码.

为了解决这一局面,在Android Jetpack还没有诞生的时候,有大神开发了RxLifecycle,但是这个框架需要强制继承基类,对于一些现有项目的改造来说,其实是不太友好的,个人感觉并没有从根本上解决问题.

Android Jetpack诞生后AutoDispose给了我们另外一条出路.它使用RxJava2中的as运算符,将订阅者转换成能够自动释放订阅者对象.

在你的build.gradle中添加依赖:

  1. implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
  2. implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
  3. implementation 'com.uber.autodispose:autodispose:1.1.0'
  4. implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.1.0'
  5. 复制代码

一个简单的示例:

  1. Observable.just(new Object())
  2. //使用AutoDispose#autoDisposable
  3. //并使用AndroidLifecycleScopeProvider#form
  4. //指定LifecycleOwner和需要在哪一个事件进行销毁
  5. //关键↓是这行
  6. .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(activity, Lifecycle.Event.ON_DESTROY)))
  7. .subscribe();
  8. 复制代码

上面代码的时间订阅将会在组件的Lifecycle.Event.ON_DESTROY事件来到时被释放,当然你也可以指定其他事件时释放.

6.2 防止多重点击

首先你可以使用JW大神RxBinding来实现这一需求,但是今天我们不讨论RxBinding,因为网上的讨论RxBinding的文章已经太多了,随便抓一篇出来都已经非常优秀.

今天我们模仿RxBinding实现一个简单的,轻量化的,基于Java动态代理的,并且兼容所有第三方View所自定义Listener接口的防止多重点击机制.

二话不说先上代码:

  1. import androidx.collection.ArrayMap;
  2. import androidx.lifecycle.Lifecycle;
  3. import androidx.lifecycle.LifecycleOwner;
  4. import com.uber.autodispose.AutoDispose;
  5. import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;
  6. import io.reactivex.subjects.PublishSubject;
  7. import java.lang.reflect.Method;
  8. import java.lang.reflect.Proxy;
  9. import java.util.Map;
  10. import java.util.concurrent.TimeUnit;
  11. import static io.reactivex.android.schedulers.AndroidSchedulers.mainThread;
  12. public final class RxOnClick<X>
  13. {
  14. //默认最低的可取的时间
  15. private static final int MINI_TIME = 200;
  16. private final Class<X> mInterface;
  17. private X mInner;
  18. private LifecycleOwner mOwner;
  19. private int mTime;
  20. private Lifecycle.Event mEvent;
  21. private RxOnClick(Class<X> type)
  22. {
  23. mInterface = type;
  24. }
  25. //从一个创建接口类型创建
  26. public static <X> RxOnClick<X> create(Class<X> type)
  27. {
  28. return new RxOnClick<>(type);
  29. }
  30. //实际处理事件的Listener
  31. public RxOnClick<X> inner(X inner)
  32. {
  33. mInner = inner;
  34. return this;
  35. }
  36. //依附于的组件也就是LifecycleOwner
  37. public RxOnClick<X> owner(LifecycleOwner owner)
  38. {
  39. mOwner = owner;
  40. return this;
  41. }
  42. //只去time毫秒内的第一个结果作为有效结果
  43. public RxOnClick<X> throttleFirst(int time)
  44. {
  45. mTime = time;
  46. return this;
  47. }
  48. //在哪一个事件进行释放
  49. public RxOnClick<X> releaseOn(Lifecycle.Event event)
  50. {
  51. mEvent = event;
  52. return this;
  53. }
  54. //创建代理类实例
  55. @SuppressWarnings("unchecked")
  56. public X build()
  57. {
  58. //检查参数
  59. if (mInterface == null || !mInterface.isInterface())
  60. {
  61. throw new IllegalArgumentException();
  62. }
  63. if (mTime < MINI_TIME)
  64. {
  65. mTime = MINI_TIME;
  66. }
  67. if (mEvent == null)
  68. {
  69. mEvent = Lifecycle.Event.ON_DESTROY;
  70. }
  71. if (mOwner == null || mInner == null)
  72. {
  73. throw new IllegalStateException();
  74. }
  75. //用反射遍历获取所有方法
  76. Map<Method, PublishSubject<Object[]>> subjectMap = new ArrayMap<>();
  77. for (Method method : mInterface.getDeclaredMethods())
  78. {
  79. PublishSubject<Object[]> subject = PublishSubject.create();
  80. subject.throttleFirst(mTime, TimeUnit.MILLISECONDS)
  81. .observeOn(mainThread())
  82. .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(mOwner, mEvent)))
  83. .subscribe(args -> method.invoke(mInner, args));
  84. subjectMap.put(method, subject);
  85. }
  86. //使用动态代理代理代理该接口并使用PublishSubject进行转发
  87. return (X) Proxy.newProxyInstance(mInterface.getClassLoader(),
  88. new Class[]{mInterface},
  89. (proxy, method, args) -> {
  90. //Object类的方法直接调用
  91. if (Object.class.equals(method.getDeclaringClass()))
  92. {
  93. return method.invoke(proxy, args);
  94. }
  95. //否则转换为Rx事件流
  96. PublishSubject<Object[]> subject = subjectMap.get(method);
  97. if (subject != null)
  98. {
  99. subject.onNext(args);
  100. }
  101. return null;
  102. });
  103. }
  104. }
  105. 复制代码

上面类在设计上采用了Builder模式,所以它实际是一个Builder.

其核心原理就是使用Java的动态代理机制创建Listener的代理类,代理类不处理事件,而是将事件通过PublishSubject(释放订阅后接收到的事件)转换为RxJava事件流推送到真正处理事件的Listener上.

这样我们就可以在这个事件流上对事件做手脚了,并且这样还能兼容RxBinding所不能兼容的第三方自定义View.

比如上面就加入了xxx毫秒内只取第一次点击和绑定组件的生命周期,用起来的时候就像是下面,依然非常简洁并且非常的有用:

  1. View.OnClickListener listener = RxOnClick
  2. .create(View.OnClickListener.class)
  3. .owner(this)
  4. .inner(v -> {
  5. //TODO
  6. })
  7. .build();
  8. 复制代码

7 使用MVVM改造Android现有体系

笔者就Android现有体系下的各种类库框架,通过自己实践的得出的经验将其进行如下归类,观点仅供参考,在实践中应该视项目特点进行适当进行改造.

7.1 View层

现有体系下的内容:

  • Activity/Fragment(布局生命周期与逻辑控制器)
  • android.view.View及其子类

设计原则:

  • View层不应该承担处理数据的责任,它应该只负责数据如何显示.
  • 它不应该直接持有Model层的任何引用,也不应该直接持有Model层的数据.
  • View层正常的行为应该是观察某个ViewModel,间接获取该ViewModelModel层中获取并处理过能在View层上直接显示的数据,数据由ViewModel保存,这样可以保证在Activity重建时页面上有关的数据不会丢失而且也不会造成View层与Model层的耦合.

7.2 DataBinding

现有体系下的内容:

  • Jetpack DataBinding 函数库
  • ViewAdapter
  • ......

设计原则:

  • 理想状态下,DataBindingView构建的关系应该是数据驱动的,即只要数据不改变View层实现的变更不会导致逻辑的重新编写(如把TextView改成EditText也不需要修改一行代码).
  • 虽然DataBinding函数库已经完成了大多数DataBinding应该做的事,但是不要为了数据驱动而排斥使用android:id来获取View并对View直接赋值,虽然这不够数据驱动,但是适当使用是可以的,毕竟AndroidView层目前还没有办法做到完全的数据驱动(主要是第三方库的兼容问题).
  • Adapter应该属于DataBinding的一种,与DataBinding函数库中生成的DataBinding相同,它也是使用数据来触发View层的改变.所以尽可能不要把它写到ViewModel中,但这不是必须的,做在对List操作要求比较高的情况下可以写到ViewModel中,但要保证一个原则——ViewModel应该只负责提供数据,而不应该知道这些数据要与何种View进行交互.

7.3 事件传递

现有体系下的内容:

  • EventBus事件总线
  • RxJava事件流

设计原则:

  • Jetpack中实现的LiveData能够很好的作为数据持有者,并且是生命周期感知的,但是有些时候我们需要向View层发送一些单次的数据,这时LiveData并不能够很好地工作.RxjavaEventBus是更好的选择.

7.4 ViewModel层

现有体系下的内容:

  • Jetpack ViewModel
  • Jetpack LiveData
  • 用于将Model数据转换成View能直接显示的数据的工具类
  • ......

设计原则:

  • ViewModel通常应该使用LiveData持有View层数据的实际控制权
  • ViewModel可以包含操作,但是ViewModel不应该直接或者间接地引用View,即使是方法中的参数也最好不要,因为ViewModel不应该知道自己到底是与哪一个View进行交互.
  • ViewModelModel的关系应该是——将Model层产生的数据翻译View层能够直接消化吸收的数据。
  • ViewModel可以向View层发送事件,然后View可以订阅这些事件以收到ViewModel层的通知.

7.5 Model

现有体系下的内容:

  • 部分与Activity无关的系统服务
  • Room(SQLite数据库)
  • Retrofit(网络数据)
  • SharedPreferences
  • ......

设计原则:

  • 涉及Activity请一定不要包含进来,如WindowManager,它们属于View层.
  • Model层主要是原始数据的来源,由于存储格式/传输格式显示格式存在的巨大差异,View层往往并不能很好的直接消化这些数据,这时就需要一个中间人作为翻译,由此抽象出了ViewModel.

8 实战

我编写了一个简单的FTP客户端作为本次MVVM博文的演示Demo,该项目简单实践了QMUI+MVVM+DataBinding+RxJava+LiveData+Room技术栈并由kotlinJava混编写成,支持断点续传,代码质量比较一般,有爱自取.

9 参考资料以及扩展阅读

10 结语

有些日子没更新文章了,最近发生了一些事让笔者彻底从无限的狂热中冷静下来,开始耐心做事.

本篇文章多达10000+字,感谢您在百忙之中抽空观看.所有内容均为个人学习总结与理解,仅供参考.

如果喜欢我的文章别忘了给我点个,拜托了这对我来说真的很重要.

转载于:https://juejin.im/post/5c973ac6f265da60f561199f

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/254033
推荐阅读
相关标签
  

闽ICP备14008679号