1. 简介

RxJava

"RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences",用过之后可以将其特质归纳为异步与简洁。

RxAndroid

"This module adds the minimum classes to RxJava that make writing reactive components in Android applications easy and hassle-free. More specifically, it provides a Scheduler that schedules on the main thread or any given Looper",RxJava的扩展,优雅地处理异步请求,配合Lambda表达式精简处理回调,增强程序可读性。

ButterKnife

"Field and method binding for Android views which uses annotation processing to generate boilerplate code for you",帮助我们告别findViewById,在编译阶段绑定控件对应的Java成员变量和xml标识id。

 

2. 依赖

Project build.grade文件

buildscript->dependencies项:

1 classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'

Module build.grade文件

apply项:

1 apply plugin: 'com.jakewharton.butterknife'

dependencies项:

1 compile 'io.reactivex:rxjava:1.1.5'2 compile 'io.reactivex:rxandroid:1.2.0'3 compile 'com.jakewharton:butterknife:8.5.1'4 annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'

一般热门开源库的更新速度是很快的,可以根据其版本间的变化来选择性地更新项目中的版本号。但是不推荐1.1.+这种写法,因为这样写之后每次构建项目都会加载库1.1.开头的最新版本。优点是不用担心最新的是1.1.1还是1.1.2,Studio会自动同步;缺点是如果项目一段时期内不需要新特性,那会因同步而浪费时间,且当版本号升为1.2.或2.0.开头时会无能为力。

 

3. 举例

测试项目放在Github上,基于RxJava 1.1.5、RxAndroid 1.2.0、ButterKnife 8.5.1,通过ButterKnife的注解和RxJava/RxAndroid的异步来简化原先由findViewById和Thread(runOnUiThread/Handler)或AsyncTask来实现的过程,功能主要是利用新旧两种形式来绑定控件、获取资源图片并显示在ImageView上。其实这篇文章是为了记录最近对RxJava的学习情况,Butterknife是顺带用在了项目中,接下来也会对其用法进行描述。

3.1 控件绑定

findViewById,ImageView id p_w_picpath_right<-->mImageRight:

1 mImageRight = (ImageView) findViewById(R.id.p_w_picpath_right);

ButterKnife

单个,p_w_picpath_left<-->mImageLeft:

1 @BindView(R.id.p_w_picpath_left)2 public ImageView mImageLeft;

多个,{p_w_picpath_left, p_w_picpath_middle, p_w_picpath_right}<-->mImageList:

1 @BindViews({R.id.p_w_picpath_left, R.id.p_w_picpath_middle, R.id.p_w_picpath_right})2 public List mImageList;

findViewById绑定控件时成员变量的访问修饰符只和实际的作用范围有关,一般为声明为private,而public等也没什么问题(当然优秀的程序员要养成良好的习惯,往往将作用域限定在最小范围内)。而ButterKnife注解形式要求对应的变量一定不能是private或static修饰,为什么呢?下面会给出答案。

1 ButterKnife.bind(this);

项目编译时ButterKnife工程中ButterKnifeProcessor类的process()会先执行三个操作:

a 扫描Java代码中所有的ButterKnife注解@BindView(s)、@OnClick、@OnItemClicked等;

b 当发现一个类中含有任何一个注解时,ButterKnifeProcessor会生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口;

c 这个ViewBinder类中包含了所有对应的代码,比如@BindView注解对应findViewById(),@OnClick对应setOnClickListener()等;

Activity调用ButterKnife.bind(this)后,ButterKnife会去加载对应的ViewBinder类调用它们的bind(),完成成员变量和控件标识的绑定。

可以看出,除了@BindViews部分,我们给ButterKnife的信息只有this这个参数了(定义为bind(@NonNull Activity target))。所以,绑定过程中ButterKnife会利用this来访问Activity中的变量和标识,如果变量声明成了private,就必须通过反射机制来处理,导致性能下降。目前还没有弄明白为什么不能用static修饰,希望知道的朋友能够指点。

 

3.2 Thread实现

 1 private void loadImageByThread() { 2     new Thread() { 3  4         @Override 5         public void run() { 6             final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_left); 7             MainActivity.this.runOnUiThread(new Runnable() { 8  9                 @Override10                 public void run() {11                     mImageLeft.setImageBitmap(bitmap);12                 }13             });14         }15     }.start();16 }

功能是将drawable文件夹中名为ic_left的图片显示在mImageLeft绑定的ImageView上面(布局左边,后面的例子会将不同图片显示在中间与右边,就不再解释了)。新建类Thread匿名对象开启子线程加载图片,然后调用Activity成员runOnUiThread()切换回主线程将图片显示在ImageView上。

简单起见例子中获取的图片是项目中事先准备好的资源文件,速度很快,而实际应用中一般是手机中或网络服务器上保存的图片,加载过程会比较耗时,所以才需要利用异步处理机制。

 

3.3 RxJava实现1

 1 private void loadImageByRxJava1() { 2     Observable.create(new Observable.OnSubscribe() { 3  4         @Override 5         public void call(Subscriber subscriber) { 6             Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_middle); 7             subscriber.onNext(bitmap); 8             subscriber.onCompleted(); 9         }10     })11     .subscribeOn(Schedulers.io())12     .observeOn(AndroidSchedulers.mainThread())13     .subscribe(new Observer() {14 15         @Override16         public void onNext(Bitmap bitmap) {17             mImageList.get(1).setImageBitmap(bitmap);18         }19 20         @Override21         public void onCompleted() {22             Toast.makeText(MainActivity.this, "Complete!", Toast.LENGTH_SHORT).show();23         }24 25         @Override26         public void onError(Throwable e) {27             Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();28         }29     });30 }

就算之前没有接触过RxJava,而对观察者模式有所了解的话,是不难看懂这段代码的。如果什么都不了解,建议先去学习观察者模式,知其所以然心里才会踏实,况且设计模式是神一样的存在,多了解总一点不会错。

代码分为三段来看就简单多了,关键在于类Observable(被观察者)和接口Observer(观察者):

第1部分2-10行,将实现了call()的接口OnSubscribe(类Observable内部)匿名对象传入create()来创建对象,泛型参数类型为Bitmap;

第2部分13-29行,将实现了onNext()、onCompleted()及onError()的接口Observer匿名对象传入subscribe()完成事件的订阅;

第3部分11-12行,从名字也可以看出,subscribeOn(Schedulers.io())指定接口OnSubscribe的call()会在线程池中以I/O操作的形式运行,observeOn(AndroidSchedulers.mainThread())指定接口Observer的三个方法在主线程中运行;

现在来看看代码是如何工作的:

当调用了subscribe()完成订阅后,call()就开始执行了。如前面所述,会在io线程中加载图片,然后调用观察者的onNext()和onCompleted(),接下来就会在主线程中更新ImageView显示内容并用Toast给出完成提示。如果图片获取过程中发生错误,则会调用onError()给出错误提示。

注意onError()和onCompleted()至多只会执行一个,且由于线程池是无限制的,大量的I/O调度操作将创建许多个线程并占用内存,需要根据实际情况在性能和便捷之间进行取舍,其实Rxjava默认的调度器是Schedulers.computation()。

 

3.4 RxJava实现2

 1 private void loadImageByRxJava2() { 2     Observable.from(new Integer[]{R.drawable.ic_right}) 3     .map(new Func1() { 4  5         @Override 6         public Bitmap call(Integer idIcRight) { 7             Bitmap bitmap = BitmapFactory.decodeResource(getResources(), idIcRight); 8             return bitmap; 9         }10     })11     .subscribeOn(Schedulers.io())12     .observeOn(AndroidSchedulers.mainThread())13     .subscribe(new Action1() {  //onNext14 15         @Override16         public void call(Bitmap bitmap) {17             mImageRight.setImageBitmap(bitmap);18         }19     }, new Action1() {  //onError20 21         @Override22         public void call(Throwable e) {23             Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();24         }25     }, new Action0() {  //onComplete26 27         @Override28         public void call() {29             Toast.makeText(MainActivity.this, "Complete!", Toast.LENGTH_SHORT).show();30         }31     });32 }

这是第二种形式,做的事情和3.3节是一样的(除了将图片显示在中间还是右边)。只要看两个部分代码即可:

第1部分2-10行,通过from()读入数组参数(哪怕只有一个元素也要以数组的形式,而且不能是int这样的基础类型)的同时创建了一个类Observable匿名对象,然后调用map()会将之前的数组元素一一映射到接口Func1匿名对象实现的call()中;

第2部分13-31行,将实现了call()的接口Action1和Action0匿名对象作为观察者传入subscribe()完成事件的订阅;

对比后可以发现:

a 这里被观察者的call()是有返回值的(之前没有),接收一个整型参数,返回一个Bitmap对象;

b 接口Action1的call()是有参的,Action0的call()是无参的,正好分别对应onNext(有参)、onError(有参)和onCompleted(无参),而传入的顺序也必须按照N->E->C;

那么当数组中有多个元素时,实际的订阅情况到底是什么样的?首先是一一映射,然后是异步和独立,即一个Integer参数会执行一次call(),获取并返回Bitmap后会调用一次观察者的第1、3个call(),失败则调用一次第二个call(),N个参数就进行N轮这个过程,实现批处理需求是很简洁和高效的。

 

4. 总结

RxJava还有很多好用的类/接口和方法,建议搞Android开发的朋友有时间研究一二。最后放上结果图: