1.按钮防抖
/*
* 1. 此处采用了RxBinding:RxView.clicks(button) = 对控件点击进行监听,需要引入依赖:compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
* 2. 传入Button控件,点击时,都会发送数据事件(但由于使用了throttleFirst()操作符,所以只会发送该段时间内的第1次点击事件)
**/
RxView.clicks(button)
.throttleFirst(2, TimeUnit.SECONDS) // 才发送 2s内第1次点击按钮的事件
.subscribe(new Observer<Object>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Object value) {
Log.d(TAG, "发送了网络请求" );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应" + e.toString());
// 获取异常错误信息
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
2.联合判断
- 布局文件 activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请填写姓名"
/>
<EditText
android:id="@+id/age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请填写年龄"
/>
<EditText
android:id="@+id/job"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请填写职业"
/>
<Button
android:id="@+id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提交"
android:enabled="false"
/>
</LinearLayout>
- 主文件MainActivity.java
Observable<CharSequence> nameObservable = RxTextView.textChanges(name).skip(1);
Observable<CharSequence> ageObservable = RxTextView.textChanges(age).skip(1);
Observable<CharSequence> jobObservable = RxTextView.textChanges(job).skip(1);
/*
* 步骤3:通过combineLatest()合并事件 & 联合判断
**/
Observable.combineLatest(nameObservable,ageObservable,jobObservable,new Function3<CharSequence, CharSequence, CharSequence,Boolean>() {
@Override
public Boolean apply(@NonNull CharSequence charSequence, @NonNull CharSequence charSequence2, @NonNull CharSequence charSequence3) throws Exception {
boolean isUserNameValid = !TextUtils.isEmpty(name.getText())
// 2. 年龄信息
boolean isUserAgeValid = !TextUtils.isEmpty(age.getText());
// 3. 职业信息
boolean isUserJobValid = !TextUtils.isEmpty(job.getText()) ;
return isUserNameValid && isUserAgeValid && isUserJobValid;
}
}).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean s) throws Exception {
Log.e(TAG, "提交按钮是否可点击: "+s);
list.setEnabled(s);
}
});
3.数据源合并
/ 用于存放最终展示的数据
String result = "数据源来自 = " ;
/*
* 设置第1个Observable:通过网络获取数据
* 此处仅作网络请求的模拟
**/
Observable<String> network = Observable.just("网络");
/*
* 设置第2个Observable:通过本地文件获取数据
* 此处仅作本地文件请求的模拟
**/
Observable<String> file = Observable.just("本地文件");
/*
* 通过merge()合并事件 & 同时发送事件
**/
Observable.merge(network, file)
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String value) {
Log.d(TAG, "数据源有: "+ value );
result += value + "+";
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
// 接收合并事件后,统一展示
@Override
public void onComplete() {
Log.d(TAG, "获取数据完成");
Log.d(TAG, result );
}
});
结果:
4.网络/缓存策略
// 该2变量用于模拟内存缓存 & 磁盘缓存中的数据
String memoryCache = null;
String diskCache = "从磁盘缓存中获取数据";
/*
* 设置第1个Observable:检查内存缓存是否有该数据的缓存
**/
Observable<String> memory = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
// 先判断内存缓存有无数据
if (memoryCache != null) {
// 若有该数据,则发送
emitter.onNext(memoryCache);
} else {
// 若无该数据,则直接发送结束事件
emitter.onComplete();
}
}
});
/*
* 设置第2个Observable:检查磁盘缓存是否有该数据的缓存
**/
Observable<String> disk = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
// 先判断磁盘缓存有无数据
if (diskCache != null) {
// 若有该数据,则发送
emitter.onNext(diskCache);
} else {
// 若无该数据,则直接发送结束事件
emitter.onComplete();
}
}
});
/*
* 设置第3个Observable:通过网络获取数据
**/
Observable<String> network = Observable.just("从网络中获取数据");
// 此处仅作网络请求的模拟
/*
* 通过concat() 和 firstElement()操作符实现缓存功能
**/
// 1. 通过concat()合并memory、disk、network 3个被观察者的事件(即检查内存缓存、磁盘缓存 & 发送网络请求)
// 并将它们按顺序串联成队列
Observable.concat(memory, disk, network)
// 2. 通过firstElement(),从串联队列中取出并发送第1个有效事件(Next事件),即依次判断检查memory、disk、network
.firstElement()
// 即本例的逻辑为:
// a. firstElement()取出第1个事件 = memory,即先判断内存缓存中有无数据缓存;由于memoryCache = null,即内存缓存中无数据,所以发送结束事件(视为无效事件)
// b. firstElement()继续取出第2个事件 = disk,即判断磁盘缓存中有无数据缓存:由于diskCache ≠ null,即磁盘缓存中有数据,所以发送Next事件(有效事件)
// c. 即firstElement()已发出第1个有效事件(disk事件),所以停止判断。
// 3. 观察者订阅
.subscribe(new Consumer<String>() {
@Override
public void accept( String s) throws Exception {
Log.d(TAG,"最终获取的数据来源 = "+ s);
}
});
5.网络请求出错重连
public class RxJavafixRetrofit2 extends AppCompatActivity {
private static final String TAG = "RxJava";
// 设置变量
// 可重试次数
private int maxConnectCount = 10;
// 当前已重试次数
private int currentRetryCount = 0;
// 重试等待时间
private int waitRetryTime = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 步骤1:创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
.addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
.build();
// 步骤2:创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
// 步骤3:采用Observable<...>形式 对 网络请求 进行封装
Observable<Translation> observable = request.getCall();
// 步骤4:发送网络请求 & 通过retryWhen()进行重试
// 注:主要异常才会回调retryWhen()进行重试
observable.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
// 参数Observable<Throwable>中的泛型 = 上游操作符抛出的异常,可通过该条件来判断异常的类型
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
// 输出异常信息
Log.d(TAG, "发生异常 = "+ throwable.toString());
/**
* 需求1:根据异常类型选择是否重试
* 即,当发生的异常 = 网络异常 = IO异常 才选择重试
*/
if (throwable instanceof IOException){
Log.d(TAG, "属于IO异常,需重试" );
/**
* 需求2:限制重试次数
* 即,当已重试次数 < 设置的重试次数,才选择重试
*/
if (currentRetryCount < maxConnectCount){
// 记录重试次数
currentRetryCount++;
Log.d(TAG, "重试次数 = " + currentRetryCount);
/**
* 需求2:实现重试
* 通过返回的Observable发送的事件 = Next事件,从而使得retryWhen()重订阅,最终实现重试功能
*
* 需求3:延迟1段时间再重试
* 采用delay操作符 = 延迟一段时间发送,以实现重试间隔设置
*
* 需求4:遇到的异常越多,时间越长
* 在delay操作符的等待时间内设置 = 每重试1次,增多延迟重试时间1s
*/
// 设置等待时间
waitRetryTime = 1000 + currentRetryCount* 1000;
Log.d(TAG, "等待时间 =" + waitRetryTime);
return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);
}else{
// 若重试次数已 > 设置重试次数,则不重试
// 通过发送error来停止重试(可在观察者的onError()中获取信息)
return Observable.error(new Throwable("重试次数已超过设置次数 = " +currentRetryCount + ",即 不再重试"));
}
}
// 若发生的异常不属于I/O异常,则不重试
// 通过返回的Observable发送的事件 = Error事件 实现(可在观察者的onError()中获取信息)
else{
return Observable.error(new Throwable("发生了非网络异常(非I/O异常)"));
}
}
});
}
}).subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
.subscribe(new Observer<Translation>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Translation result) {
// 接收服务器返回的数据
Log.d(TAG, "发送成功");
result.show();
}
@Override
public void onError(Throwable e) {
// 获取停止重试的信息
Log.d(TAG, e.toString());
}
@Override
public void onComplete() {
}
});
}
}
6.优雅实现 网络请求轮询
public class RxJavafixRetrofit extends AppCompatActivity {
private static final String TAG = "Rxjava";
// 设置变量 = 模拟轮询服务器次数
private int i = 0 ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 步骤1:创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
.addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
.build();
// 步骤2:创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
// 步骤3:采用Observable<...>形式 对 网络请求 进行封装
Observable<Translation> observable = request.getCall();
// 步骤4:发送网络请求 & 通过repeatWhen()进行轮询
observable.repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {
@Override
// 在Function函数中,必须对输入的 Observable<Object>进行处理,此处使用flatMap操作符接收上游的数据
public ObservableSource<?> apply(@NonNull Observable<Object> objectObservable) throws Exception {
// 将原始 Observable 停止发送事件的标识(Complete() / Error())转换成1个 Object 类型数据传递给1个新被观察者(Observable)
// 以此决定是否重新订阅 & 发送原来的 Observable,即轮询
// 此处有2种情况:
// 1. 若返回1个Complete() / Error()事件,则不重新订阅 & 发送原来的 Observable,即轮询结束
// 2. 若返回其余事件,则重新订阅 & 发送原来的 Observable,即继续轮询
return objectObservable.flatMap(new Function<Object, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(@NonNull Object throwable) throws Exception {
// 加入判断条件:当轮询次数 = 5次后,就停止轮询
if (i > 3) {
// 此处选择发送onError事件以结束轮询,因为可触发下游观察者的onError()方法回调
return Observable.error(new Throwable("轮询结束"));
}
// 若轮询次数<4次,则发送1Next事件以继续轮询
// 注:此处加入了delay操作符,作用 = 延迟一段时间发送(此处设置 = 2s),以实现轮询间间隔设置
return Observable.just(1).delay(2000, TimeUnit.MILLISECONDS);
}
});
}
}).subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
.subscribe(new Observer<Translation>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Translation result) {
// e.接收服务器返回的数据
result.show() ;
i++;
}
@Override
public void onError(Throwable e) {
// 获取轮询结束信息
Log.d(TAG, e.toString());
}
@Override
public void onComplete() {
}
});
}
}
结果:
7.联想搜索优化
- 布局文件:activity_filter.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
// 用于输入搜索的字符
<EditText
android:id="@+id/ed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="输入搜索字段"
/>
// 用于显示联想搜索的结果
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
- 主文件:FilterUsage.java
// 控件绑定
EditText ed;
TextView tv;
ed = (EditText) findViewById(R.id.ed);
tv = (TextView) findViewById(R.id.tv);
/*
* 说明
* 1. 此处采用了RxBinding:RxTextView.textChanges(name) = 对对控件数据变更进行监听(功能类似TextWatcher),需要引入依赖:compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
* 2. 传入EditText控件,输入字符时都会发送数据事件(此处不会马上发送,因为使用了debounce())
* 3. 采用skip(1)原因:跳过 第1次请求 = 初始输入框的空字符状态
**/
RxTextView.textChanges(ed)
.debounce(1, TimeUnit.SECONDS).skip(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<CharSequence>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(CharSequence charSequence) {
tv.setText("发送给服务器的字符 = " + charSequence.toString());
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应" );
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
结果
当然我们也可以抛去RxView框架 使用 Rxjava2.0中 PublishSubject去实现(Kotlin)
//创建PublishSubject
private val mEditPublishSubject by lazy {
PublishSubject.create<String>()
}
//事件观察者
val disposableObserver: DisposableObserver<String?> = object : DisposableObserver<String?>() {
override fun onNext(searchContent: String) {
if (!TextUtils.isEmpty(searchContent)) {
mKeyWord = searchContent
refreshNetData()
}
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
//250毫秒发送一次网络请求**refreshNetData**
mEditPublishSubject.debounce(250, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver)
mCompositeDisposable.add(disposableObserver)
et_search.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
mEditPublishSubject.onNext(s.toString())
}
})
8.优雅实现 网络请求嵌套回调
- 未使用操作符前
// 发送注册网络请求的函数方法
private void register() {
api.register(new RegisterRequest())
.subscribeOn(Schedulers.io()) //在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) //回到主线程去处理请求结果
.subscribe(new Consumer<RegisterResponse>() {
@Override
public void accept(RegisterResponse registerResponse) throws Exception {
Toast.makeText(MainActivity.this, "注册成功", Toast.LENGTH_SHORT).show();
login(); //注册成功, 调用登录的方法
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "注册失败", Toast.LENGTH_SHORT).show();
}
});
}
// 发送登录网络请求的函数方法
private void login() {
api.login(new LoginRequest())
.subscribeOn(Schedulers.io()) //在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) //回到主线程去处理请求结果
.subscribe(new Consumer<LoginResponse>() {
@Override
public void accept(LoginResponse loginResponse) throws Exception {
Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
});
}
*使用操作符后
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Rxjava";
// 定义Observable接口类型的网络请求对象
Observable<Translation1> observable1;
Observable<Translation2> observable2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 步骤1:创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
.addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
.build();
// 步骤2:创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
// 步骤3:采用Observable<...>形式 对 2个网络请求 进行封装
observable1 = request.getCall();
observable2 = request.getCall_2();
observable1.subscribeOn(Schedulers.io()) // (初始被观察者)切换到IO线程进行网络请求1
.observeOn(AndroidSchedulers.mainThread()) // (新观察者)切换到主线程 处理网络请求1的结果
.doOnNext(new Consumer<Translation1>() {
@Override
public void accept(Translation1 result) throws Exception {
Log.d(TAG, "第1次网络请求成功");
result.show();
// 对第1次网络请求返回的结果进行操作 = 显示翻译结果
}
})
.observeOn(Schedulers.io()) // (新被观察者,同时也是新观察者)切换到IO线程去发起登录请求
// 特别注意:因为flatMap是对初始被观察者作变换,所以对于旧被观察者,它是新观察者,所以通过observeOn切换线程
// 但对于初始观察者,它则是新的被观察者
.flatMap(new Function<Translation1, ObservableSource<Translation2>>() { // 作变换,即作嵌套网络请求
@Override
public ObservableSource<Translation2> apply(Translation1 result) throws Exception {
// 将网络请求1转换成网络请求2,即发送网络请求2
return observable2;
}
})
.observeOn(AndroidSchedulers.mainThread()) // (初始观察者)切换到主线程 处理网络请求2的结果
.subscribe(new Consumer<Translation2>() {
@Override
public void accept(Translation2 result) throws Exception {
Log.d(TAG, "第2次网络请求成功");
result.show();
// 对第2次网络请求返回的结果进行操作 = 显示翻译结果
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
System.out.println("登录失败");
}
});
}
}