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 ListmImageList;
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 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 () {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开发的朋友有时间研究一二。最后放上结果图: