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项:

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

Module build.grade文件

apply项:

 apply plugin: 'com.jakewharton.butterknife'

dependencies项:

 compile 'io.reactivex:rxjava:1.1.5'
compile 'io.reactivex:rxandroid:1.2.0'
compile 'com.jakewharton:butterknife:8.5.1'
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:

 mImageRight = (ImageView) findViewById(R.id.image_right);

ButterKnife

单个,image_left<-->mImageLeft:

 @BindView(R.id.image_left)
public ImageView mImageLeft;

多个,{image_left, image_middle, image_right}<-->mImageList:

 @BindViews({R.id.image_left, R.id.image_middle, R.id.image_right})
public List<ImageView> mImageList;

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

 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实现

 private void loadImageByThread() {
new Thread() { @Override
public void run() {
final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_left);
MainActivity.this.runOnUiThread(new Runnable() { @Override
public void run() {
mImageLeft.setImageBitmap(bitmap);
}
});
}
}.start();
}

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

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

3.3 RxJava实现1

 private void loadImageByRxJava1() {
Observable.create(new Observable.OnSubscribe<Bitmap>() { @Override
public void call(Subscriber<? super Bitmap> subscriber) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_middle);
subscriber.onNext(bitmap);
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Bitmap>() { @Override
public void onNext(Bitmap bitmap) {
mImageList.get(1).setImageBitmap(bitmap);
} @Override
public void onCompleted() {
Toast.makeText(MainActivity.this, "Complete!", Toast.LENGTH_SHORT).show();
} @Override
public void onError(Throwable e) {
Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
}
});
}

就算之前没有接触过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

 private void loadImageByRxJava2() {
Observable.from(new Integer[]{R.drawable.ic_right})
.map(new Func1<Integer, Bitmap>() { @Override
public Bitmap call(Integer idIcRight) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), idIcRight);
return bitmap;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Bitmap>() { //onNext @Override
public void call(Bitmap bitmap) {
mImageRight.setImageBitmap(bitmap);
}
}, new Action1<Throwable>() { //onError @Override
public void call(Throwable e) {
Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
}
}, new Action0() { //onComplete @Override
public void call() {
Toast.makeText(MainActivity.this, "Complete!", Toast.LENGTH_SHORT).show();
}
});
}

这是第二种形式,做的事情和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开发的朋友有时间研究一二。最后放上结果图:

开源库RxJava、ButterKnife学习记录的更多相关文章

  1. 开源库RxJava、ButterKnife

    1. 简介 RxJava "RxJava is a Java VM implementation of Reactive Extensions: a library for composin ...

  2. java开源项目之IQQ学习记录之项目环境搭建与启动

    本文链接地址:http://blog.csdn.net/sushengmiyan/article/details/18779727 作者:sushengmiyan 现在就码字说说今天晚上搞定的一个项目 ...

  3. java开源项目之IQQ学习记录之单例模式与log4j日志记录

    作者:sushengmiyan 本文地址:http://blog.csdn.net/sushengmiyan/article/details/18992741 打开IQQ项目,打开包iqq.app中的 ...

  4. 在别的地方看的<<给程序员介绍一些C++开源库>>,记录给大家共同学习

    首先说明这篇文章不是出自我手,大家共同学习. 引用地址:http://oss.org.cn/?action-viewnews-itemid-61998. C++开源库,欢迎补充. C++在“商业应用” ...

  5. MXNet 学习 (1) --- 最易上手的深度学习开源库 --- 安装及环境搭建

    安装环境:Win 10 专业版 64位 + Visual Studio 2015 Community. 记录下自己在有GPU的环境下安装配置MXNet的过程.该过程直接使用MXNet release ...

  6. 开源项目Material Calendar View 学习记录 (一)

    开源项目Material Calendar View 学习记录 Github: https://github.com/prolificinteractive/material-calendarview ...

  7. DCMTK开源库的学习笔记4:利用ini配置文件对dcm影像进行归档

    转:http://blog.csdn.net/zssureqh/article/details/8846337 背景介绍: 医学影像PACS工作站的服务端需要对大量的dcm文件进行归档,写入数据库处理 ...

  8. 发布开源库到JCenter所遇到的一些问题记录

    这周末自己瞎折磨了下,如何发布开源库到 JCenter,然后这过程中碰到了一些问题,在此记录分享一下 本篇是基于上一篇:教你一步步发布一个开源库到 JCenter 介绍的流程.步骤中所遇到的问题,所以 ...

  9. 深度学习开源库tiny-dnn的使用(MNIST)

    tiny-dnn是一个基于DNN的深度学习开源库,它的License是BSD 3-Clause.之前名字是tiny-cnn是基于CNN的,tiny-dnn与tiny-cnn相关又增加了些新层.此开源库 ...

随机推荐

  1. VisualVM监控远程主机上的JAVA应用程序

    使用VisualVM监控远程主机上JAVA应用程序时,需要开启远程主机上的远程监控访问,或者在远程JAVA应用程序启动时,开启远程监控选项,两种方法,选择其中一种就可以开启远程监控功能,配置完成后就可 ...

  2. python运用中文注释时报错解决方法

    写了一段简单的代码,不知 为什么总是报错,后来上网查了一下才知道原因,当用中文进行注释时需要添加如下代码:# coding=utf-8          (注意:该段代码必须放在最前面才能有用,并且 ...

  3. python 自动化接口测试(6)

    迎接新的一波更新吧,这次是基于图灵机器人的一个api接口的测试. 这是api的接口:http://www.tuling123.com/openapi/api 我们试着通过浏览器直接访问看下 这是反馈的 ...

  4. qt5的.ui文件在VS2010中无法编译问题

    自己手动添加的.ui文件在VS中是无法右键编译的,也即是说,在用QT designer编辑过的.ui文件无法实时更新相应的ui_XX.h文件,造成调试结果无法显示编辑过的新界面. 解决办法: 右键.u ...

  5. 用 Python 编写剪刀、石头、布的小游戏(快速学习python语句)

    import random#定义手势类型allList = ['石头','剪刀','布']#定义获胜的情况winList = [['石头','剪刀'],['剪刀','布'],['步','石头']]pr ...

  6. iOS开发 - 啰嗦讲解 Runloop

    写在前面的 为什么要了解 RunLoop?如果你想成为一个高级iOS开发工程师,那这是你必须了解的东西,他能帮助你更好的理解底层实现的原理,可以利用它的特性做出一些高效又神奇的功能.RunLoop这个 ...

  7. es6基础系列二:Number

    es6中关于Number类型的变化,主要有以下几点 Number.isInteger(新增,判断是否为整数) Number.EPSILON(新增,判断是否可忽略的误差) Number.MAX_SAFE ...

  8. loopj.com android-async-http

    loopj.com android-async-http Android异步Http客户端 用于Android的基于回调的Http客户端库   下载版本1.4.9(最新) 或者在github上fork ...

  9. Java集合的区别和选择

              Collection |--List       有序,可重复 |--ArrayList 底层数据结构是数组,查询快,增删慢. 线程不安全,效率高 |--Vector 底层数据结构 ...

  10. cocos2d-x - C++/Lua交互

    使用tolua++将自定义的C++类嵌入,让lua脚本使用 一般过程: 自定义类 -> 使用tolua++工具编译到LuaCoco2d.cpp中 -> lua调用 步骤一:自定义一个C++ ...