原文出处: C.L. Wang(@SpikeKing )
RxAndroid是RxJava的扩展, 优雅地处理异步请求. RxAndroid配合Lambda表达式, 精简处理回调, 使程序更具有可读性. Rx作为Android最优秀的开源库之一, 极大地提高生产力, 我们需要掌握. 本文由浅入深, 介绍一些常见的使用方法, 并附有源码.
更多: http://www.wangchenlong.org/
本文代码的GitHub下载地址.
要点包含:
(1) 链式表达式的使用方式.
(2) Lambda的应用.
(3) Rx处理网络请求.
(4) 线程自动管理, 防止内存泄露.
(5) RxBinding绑定控件的异步事件.
基础
当然, 从一个崭新的HelloWorld项目开始.
添加Gradle配置.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 65.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
compile'com.jakewharton:butterknife:7.0.1'
compile'io.reactivex:rxandroid:1.1.0'// RxAndroid
compile'io.reactivex:rxjava:1.1.0'// 推荐同时加载RxJava
RxAndroid是本文的核心依赖, 同时添加RxJava. 还有ButterKnife注解库.
Lambda表达式, 是写出优雅代码的关键, 参考.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 200.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
plugins{
id"me.tatarka.retrolambda"version"3.2.4"
}
android{
...
compileOptions{
sourceCompatibilityJavaVersion.VERSION_1_8
targetCompatibilityJavaVersion.VERSION_1_8
}
}
Gradle 2.1+
以上, 配置非常简单, 添加一个plugin和一个Java1.8兼容即可.
从主MainActivity
跳转至SimpleActivity
.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 590.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 主Activity, 用于跳转各个模块.
*
* @author wangchenlong
*/
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 跳转简单的页面
publicvoidgotoSimpleModule(Viewview){
startActivity(newIntent(this,SimpleActivity.class));
}
// 跳转复杂的页面
publicvoidgotoMoreModule(Viewview){
startActivity(newIntent(this,MoreActivity.class));
}
// 跳转Lambda的页面
publicvoidgotoLambdaModule(Viewview){
startActivity(newIntent(this,LambdaActivity.class));
}
// 跳转网络的页面
publicvoidgotoNetworkModule(Viewview){
startActivity(newIntent(this,NetworkActivity.class));
}
// 跳转线程安全的页面
publicvoidgotoSafeModule(Viewview){
startActivity(newIntent(this,SafeActivity.class));
}
}
在SimpleActivity
中, 创建一个观察者, 收到字符串的返回.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 236.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
// 观察事件发生
Observable.OnSubscribemObservableAction=newObservable.OnSubscribe<String>(){
@Overridepublicvoidcall(Subscriber<?superString>subscriber){
subscriber.onNext(sayMyName());// 发送事件
subscriber.onCompleted();// 完成事件
}
};
...
// 创建字符串
privateStringsayMyName(){
return"Hello, I am your friend, Spike!";
}
创建两个订阅者, 使用字符串输出信息.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 461.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 订阅者, 接收字符串, 修改控件
Subscriber<String>mTextSubscriber=newSubscriber<String>(){
@OverridepublicvoidonCompleted(){
}
@OverridepublicvoidonError(Throwablee){
}
@OverridepublicvoidonNext(Strings){
mTvText.setText(s);// 设置文字
}
};
// 订阅者, 接收字符串, 提示信息
Subscriber<String>mToastSubscriber=newSubscriber<String>(){
@OverridepublicvoidonCompleted(){
}
@OverridepublicvoidonError(Throwablee){
}
@OverridepublicvoidonNext(Strings){
Toast.makeText(SimpleActivity.this,s,Toast.LENGTH_SHORT).show();
}
};
在页面中, 观察者接收信息, 发送至主线程AndroidSchedulers.mainThread()
, 再传递给订阅者, 由订阅者最终处理消息. 接收信息可以是同步, 也可以是异步.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 236.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
ButterKnife.bind(this);
// 注册观察活动
@SuppressWarnings("unchecked")
Observable<String>observable=Observable.create(mObservableAction);
// 分发订阅信息
observable.observeOn(AndroidSchedulers.mainThread());
observable.subscribe(mTextSubscriber);
observable.subscribe(mToastSubscriber);
}
最基础的RxAndroid使用.
更多
我们已经熟悉了初步的使用方式, 在接着学习一些其他方法, 如
just
: 获取输入数据, 直接分发, 更加简洁, 省略其他回调.
from
: 获取输入数组, 转变单个元素分发.
map
: 映射, 对输入数据进行转换, 如大写.
flatMap
: 增大, 本意就是增肥, 把输入数组映射多个值, 依次分发.
reduce
: 简化, 正好相反, 把多个数组的值, 组合成一个数据.
来看看这个示例, 设置两个不同类型数组, 作为输入源, 根据不同情况分发数据.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 1226.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 更多的RxAndroid的使用方法.
* <p>
* Created by wangchenlong on 15/12/30.
*/
publicclassMoreActivityextendsActivity{
@Bind(R.id.simple_tv_text)TextViewmTvText;
finalString[]mManyWords={"Hello","I","am","your","friend","Spike"};
finalList<String>mManyWordList=Arrays.asList(mManyWords);
// Action类似订阅者, 设置TextView
privateAction1<String>mTextViewAction=newAction1<String>(){
@Overridepublicvoidcall(Strings){
mTvText.setText(s);
}
};
// Action设置Toast
privateAction1<String>mToastAction=newAction1<String>(){
@Overridepublicvoidcall(Strings){
Toast.makeText(MoreActivity.this,s,Toast.LENGTH_SHORT).show();
}
};
// 设置映射函数
privateFunc1<List<String>,Observable<String>>mOneLetterFunc=newFunc1<List<String>,Observable<String>>(){
@OverridepublicObservable<String>call(List<String>strings){
returnObservable.from(strings);// 映射字符串
}
};
// 设置大写字母
privateFunc1<String,String>mUpperLetterFunc=newFunc1<String,String>(){
@OverridepublicStringcall(Strings){
returns.toUpperCase();// 大小字母
}
};
// 连接字符串
privateFunc2<String,String,String>mMergeStringFunc=newFunc2<String,String,String>(){
@OverridepublicStringcall(Strings,Strings2){
returnString.format("%s %s",s,s2);// 空格连接字符串
}
};
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
ButterKnife.bind(this);
// 添加字符串, 省略Action的其他方法, 只使用一个onNext.
Observable<String>obShow=Observable.just(sayMyName());
// 先映射, 再设置TextView
obShow.observeOn(AndroidSchedulers.mainThread())
.map(mUpperLetterFunc).subscribe(mTextViewAction);
// 单独显示数组中的每个元素
Observable<String>obMap=Observable.from(mManyWords);
// 映射之后分发
obMap.observeOn(AndroidSchedulers.mainThread())
.map(mUpperLetterFunc).subscribe(mToastAction);
// 优化过的代码, 直接获取数组, 再分发, 再合并, 再显示toast, Toast顺次执行.
Observable.just(mManyWordList)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(mOneLetterFunc)
.reduce(mMergeStringFunc)
.subscribe(mToastAction);
}
// 创建字符串
privateStringsayMyName(){
return"Hello, I am your friend, Spike!";
}
}
这次简化调用代码, 因为有时候我们对异常并不是很关心,
只要能catch
异常即可, 因此流仅仅关注真正需要的部分.
输入字符串, 变换大写, 输出至控件中显示.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 110.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
// 添加字符串, 省略Action的其他方法, 只使用一个onNext.
Observable<String>obShow=Observable.just(sayMyName());
// 先映射, 再设置TextView
obShow.observeOn(AndroidSchedulers.mainThread())
.map(mUpperLetterFunc).subscribe(mTextViewAction);
just
可以非常简单的获取任何数据, 分发时, 选择使用的线程.
map
是对输入数据加工, 转换类型, 输入Func1
, 准换大写字母.
Func1
代表使用一个参数的函数, 前面是参数, 后面是返回值.
Action1
代表最终动作, 因而不需要返回值, 并且一个参数.
输入数组, 单独分发数组中每一个元素, 转换大写, 输入Toast连续显示.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 110.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
// 单独显示数组中的每个元素
Observable<String>obMap=Observable.from(mManyWords);
// 映射之后分发
obMap.observeOn(AndroidSchedulers.mainThread())
.map(mUpperLetterFunc).subscribe(mToastAction);
from
是读取数组中的值, 每次单独分发, 并分发多次, 其余类似.
输入数组, 映射为单独分发, 并组合到一起, 集中显示.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 110.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
// 优化过的代码, 直接获取数组, 再分发, 再合并, 再显示toast, Toast顺次执行.
Observable.just(mManyWordList)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(mOneLetterFunc)
.reduce(mMergeStringFunc)
.subscribe(mToastAction);
这次是使用just
分发数组, 则分发数据就是数组, 并不是数组中的元素.
flatMap
把数组转换为单独分发, Func1
内部使用from
拆分数组.
reduce
把单独分发数据集中到一起, 再统一分发, 使用Func2
.
最终使用Action1
显示获得数据. 本次代码也更加简洁.
由此我们可以观察到, Rx的写法可以是多种多样, 合理的写法会更加优雅.
效果
Lambda
Lambda表达式和Rx非常契合, 可以省略大量的内部类, 如Func和Action.
我们把上个示例, 用Lambda再写一次, 功能相同.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 851.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* Lambda表达式写法
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicclassLambdaActivityextendsActivity{
@Bind(R.id.simple_tv_text)TextViewmTvText;
finalString[]mManyWords={"Hello","I","am","your","friend","Spike"};
finalList<String>mManyWordList=Arrays.asList(mManyWords);
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
ButterKnife.bind(this);
// 添加字符串, 省略Action的其他方法, 只使用一个onNext.
Observable<String>obShow=Observable.just(sayMyName());
// 先映射, 再设置TextView
obShow.observeOn(AndroidSchedulers.mainThread())
.map(String::toUpperCase).subscribe(mTvText::setText);
// 单独显示数组中的每个元素
Observable<String>obMap=Observable.from(mManyWords);
// 映射之后分发
obMap.observeOn(AndroidSchedulers.mainThread())
.map(String::toUpperCase)
.subscribe(this::showToast);
// 优化过的代码, 直接获取数组, 再分发, 再合并, 再显示toast, Toast顺次执行.
Observable.just(mManyWordList)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(Observable::from)
.reduce(this::mergeString)
.subscribe(this::showToast);
}
// 创建字符串
privateStringsayMyName(){
return"Hello, I am your friend, Spike!";
}
// 显示Toast
privatevoidshowToast(Strings){
Toast.makeText(LambdaActivity.this,s,Toast.LENGTH_SHORT).show();
}
// 合并字符串
privateStringmergeString(Strings1,Strings2){
returnString.format("%s %s",s1,s2);
}
}
这次没有使用常规的Lambda表达式, 而是更简单的方法引用(Method References)
.
方法引用: 方法参数和返回值与Lambda表达式相同时, 使用方法名代替.
网络请求
Retrofit是网络请求库, 刚推出2.0版本. Rx的一个核心应用就是处理异步网络请求, 结合Retrofit, 会更加方便和简洁. 参考.
引入库
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 131.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
compile'com.android.support:recyclerview-v7:23.1.1'// RecyclerView
compile'com.squareup.retrofit:retrofit:2.0.0-beta2'// Retrofit网络处理
compile'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'// Retrofit的rx解析库
compile'com.squareup.retrofit:converter-gson:2.0.0-beta2'// Retrofit的gson库
compile'com.squareup.picasso:picasso:2.5.2'// Picasso网络图片加载
recyclerview
和picasso
为了显示. retrofit
系列是网络请求.
主页使用一个简单的列表视图, 展示Github的用户信息.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 551.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Rx的网络请求方式
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicclassNetworkActivityextendsActivity{
@Bind(R.id.network_rv_list)RecyclerViewmRvList;// 列表
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network);
ButterKnife.bind(this);
// 设置Layout管理器
LinearLayoutManagerlayoutManager=newLinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRvList.setLayoutManager(layoutManager);
// 设置适配器
UserListAdapteradapter=newUserListAdapter(this::gotoDetailPage);
NetworkWrapper.getUsersInto(adapter);
mRvList.setAdapter(adapter);
}
// 点击的回调
publicinterfaceUserClickCallback{
voidonItemClicked(Stringname);
}
// 跳转到库详情页面
privatevoidgotoDetailPage(Stringname){
startActivity(NetworkDetailActivity.from(NetworkActivity.this,name));
}
}
在列表中提供点击用户信息跳转至用户详情.
NetworkWrapper.getUsersInto(adapter)
请求网络, 设置适配器信息.
关键部分, 适配器, 其中包含ViewHolder类和数据类.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 1106.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 显示列表
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicclassUserListAdapterextendsRecyclerView.Adapter<UserListAdapter.UserViewHolder>{
privateList<GitHubUser>mUsers;// 用户名集合
privateNetworkActivity.UserClickCallbackmCallback;// 用户点击项的回调
publicUserListAdapter(NetworkActivity.UserClickCallbackcallback){
mUsers=newArrayList<>();
mCallback=callback;
}
publicvoidaddUser(GitHubUseruser){
mUsers.add(user);
notifyItemInserted(mUsers.size()-1);// 最后一位
}
@OverridepublicUserViewHolderonCreateViewHolder(ViewGroupparent,intviewType){
Viewitem=LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_network_user,parent,false);
returnnewUserViewHolder(item,mCallback);
}
@OverridepublicvoidonBindViewHolder(UserViewHolderholder,intposition){
holder.bindTo(mUsers.get(position));
}
@OverridepublicintgetItemCount(){
returnmUsers.size();
}
// Adapter的ViewHolder
publicstaticclassUserViewHolderextendsRecyclerView.ViewHolder{
@Bind(R.id.network_item_iv_user_picture)ImageViewmIvUserPicture;
@Bind(R.id.network_item_tv_user_name)TextViewmTvUserName;
@Bind(R.id.network_item_tv_user_login)TextViewmTvUserLogin;
@Bind(R.id.network_item_tv_user_page)TextViewmTvUserPage;
publicUserViewHolder(ViewitemView,NetworkActivity.UserClickCallbackcallback){
super(itemView);
ButterKnife.bind(this,itemView);
// 绑定点击事件
itemView.setOnClickListener(v->
callback.onItemClicked(mTvUserLogin.getText().toString()));
}
// 绑定数据
publicvoidbindTo(GitHubUseruser){
mTvUserName.setText(user.name);
mTvUserLogin.setText(user.login);
mTvUserPage.setText(user.repos_url);
Picasso.with(mIvUserPicture.getContext())
.load(user.avatar_url)
.placeholder(R.drawable.ic_person_black_24dp)
.into(mIvUserPicture);
}
}
// 用户类, 名称必须与Json解析相同
publicstaticclassGitHubUser{
publicStringlogin;
publicStringavatar_url;
publicStringname;
publicStringrepos_url;
}
}
添加数据addUser
, 其中notifyItemInserted
通知更新.
可以自动生成Json解析类的网站.
首先创建Retrofit
`服务, 通过服务获取数据, 再依次分发给适配器.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 521.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 用户获取类
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicclassNetworkWrapper{
privatestaticfinalString[]mFamousUsers=
{"SpikeKing","JakeWharton","rock3r","Takhion","dextorer","Mariuxtheone"};
// 获取用户信息
publicstaticvoidgetUsersInto(finalUserListAdapteradapter){
GitHubServicegitHubService=
ServiceFactory.createServiceFrom(GitHubService.class,GitHubService.ENDPOINT);
Observable.from(mFamousUsers)
.flatMap(gitHubService::getUserData)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter::addUser);
}
// 获取库信息
publicstaticvoidgetReposInfo(finalStringusername,finalRepoListAdapteradapter){
GitHubServicegitHubService=
ServiceFactory.createServiceFrom(GitHubService.class,GitHubService.ENDPOINT);
gitHubService.getRepoData(username)
.flatMap(Observable::from)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter::addRepo);
}
}
网络请求无法在主线程上执行, 需要启动异步线程, 如Schedulers.newThread()
.
使用工厂模式ServiceFactory
创建服务, 也可以单独创建服务.
创建Retrofit
服务的工厂类.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 521.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 用户获取类
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicclassNetworkWrapper{
privatestaticfinalString[]mFamousUsers=
{"SpikeKing","JakeWharton","rock3r","Takhion","dextorer","Mariuxtheone"};
// 获取用户信息
publicstaticvoidgetUsersInto(finalUserListAdapteradapter){
GitHubServicegitHubService=
ServiceFactory.createServiceFrom(GitHubService.class,GitHubService.ENDPOINT);
Observable.from(mFamousUsers)
.flatMap(gitHubService::getUserData)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter::addUser);
}
// 获取库信息
publicstaticvoidgetReposInfo(finalStringusername,finalRepoListAdapteradapter){
GitHubServicegitHubService=
ServiceFactory.createServiceFrom(GitHubService.class,GitHubService.ENDPOINT);
gitHubService.getRepoData(username)
.flatMap(Observable::from)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter::addRepo);
}
}
这是Retrofit 2.0的写法, 注意需要添加Rx和Gson的解析.
设置网络请求的Url.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 266.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
/**
* GitHub的服务
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicinterfaceGitHubService{
StringENDPOINT="https://api.github.com";
// 获取个人信息
@GET("/users/{user}")
Observable<UserListAdapter.GitHubUser>getUserData(@Path("user")Stringuser);
// 获取库, 获取的是数组
@GET("/users/{user}/repos")
Observable<RepoListAdapter.GitHubRepo[]>getRepoData(@Path("user")Stringuser);
}
显示用户
详情页面与主页类似, 参考代码, 不做细说.
线程安全
Rx的好处之一就是可以防止内存泄露, 即根据页面生命周期, 处理异步线程的结束. 可以使用RxLifecycle库处理生命周期.
Activity
类继承RxAppCompatActivity
, 替换AppCompatActivity
.
启动一个循环线程.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 506.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Rx的线程安全
* <p>
* Created by wangchenlong on 15/12/31.
*/
publicclassSafeActivityextendsRxAppCompatActivity{
privatestaticfinalStringTAG="DEBUG-WCL: "+SafeActivity.class.getSimpleName();
@Bind(R.id.simple_tv_text)TextViewmTvText;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
ButterKnife.bind(this);
Observable.interval(1,TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())// 管理生命周期, 防止内存泄露
.subscribe(this::showTime);
}
privatevoidshowTime(Longtime){
mTvText.setText(String.valueOf("时间计数: "+time));
Log.d(TAG,"时间计数器: "+time);
}
@Override
protectedvoidonPause(){
super.onPause();
Log.w(TAG,"页面关闭!");
}
}
继承RxAppCompatActivity
, 添加bindToLifecycle
方法管理生命周期. 当页面onPause
时, 会自动结束循环线程. 如果注释这句代码, 则会导致内存泄露.
RxBinding
RxBinding是Rx中处理控件异步调用的方式, 也是由Square公司开发, Jake负责编写. 通过绑定组件, 异步获取事件, 并进行处理. 编码风格非常优雅.
除了RxJava, 再添加RxBinding的依赖.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 80.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
// RxBinding
compile'com.jakewharton.rxbinding:rxbinding:0.3.0'
compile'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0'
compile'com.jakewharton.rxbinding:rxbinding-design:0.3.0'
Toolbar和Fab, 两个较新的控件.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 560.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xmlversion="1.0"encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".BindingActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/rxbinding_t_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:popupTheme="@style/AppTheme.PopupOverlay"
tools:targetApi="21"/>
</android.support.design.widget.AppBarLayout>
<includelayout="@layout/content_rxbinding"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/rxbinding_fab_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"/>
</android.support.design.widget.CoordinatorLayout>
两个EditText控件, 对比传统方法和RxBinding方法.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 626.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/activity_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".BindingActivity"
tools:showIn="@layout/activity_binding">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/usual_approach"/>
<EditText
android:id="@+id/rxbinding_et_usual_approach"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="@null"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/reactive_approach"/>
<EditText
android:id="@+id/rxbinding_et_reactive_approach"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="@null"/>
<TextView
android:id="@+id/rxbinding_tv_show"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
使用ButterKnife注入控件, 使用RxBinding方式绑定控件, 异步监听事件.
<textarea wrap="soft" class="crayon-plain print-no" data-settings="dblclick" readonly="readonly" style="border-width: 0px; outline: none; display: block; -webkit-appearance: none; padding-top: 0px; padding-right: 5px; padding-left: 5px; width: 608px; height: 1451.5px; resize: none; overflow: auto; margin: 0px; position: absolute; opacity: 0; box-shadow: none; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; -webkit-box-shadow: none; white-space: pre; word-wrap: normal; color: rgb(0, 0, 0); tab-size: 4; z-index: 0; line-height: 15px !important; font-family: Monaco, MonacoRegular, 'Courier New', monospace !important;"></textarea>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* Rx绑定
* <p>
* Created by wangchenlong on 16/1/25.
*/
publicclassBindingActivityextendsAppCompatActivity{
@Bind(R.id.rxbinding_t_toolbar)ToolbarmTToolbar;
@Bind(R.id.rxbinding_et_usual_approach)EditTextmEtUsualApproach;
@Bind(R.id.rxbinding_et_reactive_approach)EditTextmEtReactiveApproach;
@Bind(R.id.rxbinding_tv_show)TextViewmTvShow;
@Bind(R.id.rxbinding_fab_fab)FloatingActionButtonmFabFab;
@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binding);
ButterKnife.bind(this);
initToolbar();// 初始化Toolbar
initFabButton();// 初始化Fab按钮
initEditText();// 初始化编辑文本
}
// 初始化Toolbar
privatevoidinitToolbar(){
// 添加菜单按钮
setSupportActionBar(mTToolbar);
ActionBaractionBar=getSupportActionBar();
// 添加浏览按钮
if(actionBar!=null){
actionBar.setDisplayHomeAsUpEnabled(true);
}
RxToolbar.itemClicks(mTToolbar).subscribe(this::onToolbarItemClicked);
RxToolbar.navigationClicks(mTToolbar).subscribe(this::onToolbarNavigationClicked);
}
// 点击Toolbar的项
privatevoidonToolbarItemClicked(MenuItemmenuItem){
Stringm="点击\""+menuItem.getTitle()+"\"";
Toast.makeText(this,m,Toast.LENGTH_SHORT).show();
}
// 浏览点击
privatevoidonToolbarNavigationClicked(Voidv){
Toast.makeText(this,"浏览点击",Toast.LENGTH_SHORT).show();
}
@OverridepublicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_rxbinding,menu);
returnsuper.onCreateOptionsMenu(menu);
}
// 初始化Fab按钮
privatevoidinitFabButton(){
RxView.clicks(mFabFab).subscribe(this::onFabClicked);
}
// 点击Fab按钮
privatevoidonFabClicked(Voidv){
Snackbarsnackbar=Snackbar.make(findViewById(android.R.id.content),"点击Snackbar",Snackbar.LENGTH_SHORT);
snackbar.show();
RxSnackbar.dismisses(snackbar).subscribe(this::onSnackbarDismissed);
}
// 销毁Snackbar, event参考{Snackbar}
privatevoidonSnackbarDismissed(intevent){
Stringtext="Snackbar消失代码:"+event;
Toast.makeText(this,text,Toast.LENGTH_SHORT).show();
}
// 初始化编辑文本
privatevoidinitEditText(){
// 正常方式
mEtUsualApproach.addTextChangedListener(newTextWatcher(){
@Override
publicvoidbeforeTextChanged(CharSequences,intstart,intcount,intafter){
}
@OverridepublicvoidonTextChanged(CharSequences,intstart,intbefore,intcount){
mTvShow.setText(s);
}
@OverridepublicvoidafterTextChanged(Editables){
}
});
// Rx方式
RxTextView.textChanges(mEtReactiveApproach).subscribe(mTvShow::setText);
}
}
Toolbar使用RxToolbar监听点击事件; Snackbar使用RxSnackbar监听;
EditText使用RxTextView监听; 其余使用RxView监听.
OK, That’s all. Enjoy it!
0 0