开源库RxJava、ButterKnife
1. 简介
"RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences",用过之后可以将其特质归纳为异步与简洁。
"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表达式精简处理回调,增强程序可读性。
"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 image_right<-->mImageRight:
1 mImageRight = (ImageView) findViewById(R.id.image_right);
ButterKnife
单个,image_left<-->mImageLeft:
1 @BindView(R.id.image_left)
2 public ImageView mImageLeft;
多个,{image_left, image_middle, image_right}<-->mImageList:
1 @BindViews({R.id.image_left, R.id.image_middle, R.id.image_right})
2 public List<ImageView> 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 @Override
10 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<Bitmap>() {
3
4 @Override
5 public void call(Subscriber<? super Bitmap> 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<Bitmap>() {
14
15 @Override
16 public void onNext(Bitmap bitmap) {
17 mImageList.get(1).setImageBitmap(bitmap);
18 }
19
20 @Override
21 public void onCompleted() {
22 Toast.makeText(MainActivity.this, "Complete!", Toast.LENGTH_SHORT).show();
23 }
24
25 @Override
26 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<Integer, Bitmap>() {
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<Bitmap>() { //onNext
14
15 @Override
16 public void call(Bitmap bitmap) {
17 mImageRight.setImageBitmap(bitmap);
18 }
19 }, new Action1<Throwable>() { //onError
20
21 @Override
22 public void call(Throwable e) {
23 Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
24 }
25 }, new Action0() { //onComplete
26
27 @Override
28 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个call(),N个参数就进行N轮这个过程。如果失败则调用一次第二个call(),全部成功则调用一次第三个call(),实现批处理需求是很简洁和高效的。
4. 总结
RxJava还有很多好用的类/接口和方法,建议搞Android开发的朋友有时间研究一二。最后放上结果图: