java 2 2=5_RxJava2 实战知识梳理(5) - 简单及进阶的轮询操作

RxJava2 实战系列文章

一、示例

1.1 应用场景

今天,我们介绍一种新的场景,轮询操作。也就是说,我们会尝试间隔一段时间就向服务器发起一次请求,在使用RxJava之前,该需求的实现一般有两种方式:

通过Handler发送延时消息,在handleMessage中请求服务器之后,再次发送一个延时消息,直到达到循环次数为止。

使用Java提供的定时器Timer。

我们尝试使用RxJava2提供的操作符来实现这一需求,这里演示两种方式的轮询,并将单次访问的次数限制在5次:

固定时延:使用intervalRange操作符,每间隔3s执行一次任务。

变长时延:使用repeatWhen操作符实现,第一次执行完任务后,等待4s再执行第二次任务,在第二次任务执行完成后,等待5s,依次递增。

2.2 示例

public class PollingActivity extends AppCompatActivity {

private static final String TAG = PollingActivity.class.getSimpleName();

private TextView mTvSimple;

private TextView mTvAdvance;

private CompositeDisposable mCompositeDisposable;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_polling);

mTvSimple = (TextView) findViewById(R.id.tv_simple);

mTvSimple.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

startSimplePolling();

}

});

mTvAdvance = (TextView) findViewById(R.id.tv_advance);

mTvAdvance.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

startAdvancePolling();

}

});

mCompositeDisposable = new CompositeDisposable();

}

private void startSimplePolling() {

Log.d(TAG, "startSimplePolling");

Observable observable = Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS).take(5).doOnNext(new Consumer() {

@Override

public void accept(Long aLong) throws Exception {

doWork(); //这里使用了doOnNext,因此DisposableObserver的onNext要等到该方法执行完才会回调。

}

});

DisposableObserver disposableObserver = getDisposableObserver();

observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);

mCompositeDisposable.add(disposableObserver);

}

private void startAdvancePolling() {

Log.d(TAG, "startAdvancePolling click");

Observable observable = Observable.just(0L).doOnComplete(new Action() {

@Override

public void run() throws Exception {

doWork();

}

}).repeatWhen(new Function, ObservableSource>() {

private long mRepeatCount;

@Override

public ObservableSource apply(Observable objectObservable) throws Exception {

//必须作出反应,这里是通过flatMap操作符。

return objectObservable.flatMap(new Function>() {

@Override

public ObservableSource apply(Object o) throws Exception {

if (++mRepeatCount > 4) {

//return Observable.empty(); //发送onComplete消息,无法触发下游的onComplete回调。

return Observable.error(new Throwable("Polling work finished")); //发送onError消息,可以触发下游的onError回调。

}

Log.d(TAG, "startAdvancePolling apply");

return Observable.timer(3000 + mRepeatCount * 1000, TimeUnit.MILLISECONDS);

}

});

}

});

DisposableObserver disposableObserver = getDisposableObserver();

observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);

mCompositeDisposable.add(disposableObserver);

}

private DisposableObserver getDisposableObserver() {

return new DisposableObserver() {

@Override

public void onNext(Long aLong) {}

@Override

public void onError(Throwable throwable) {

Log.d(TAG, "DisposableObserver onError, threadId=" + Thread.currentThread().getId() + ",reason=" + throwable.getMessage());

}

@Override

public void onComplete() {

Log.d(TAG, "DisposableObserver onComplete, threadId=" + Thread.currentThread().getId());

}

};

}

private void doWork() {

long workTime = (long) (Math.random() * 500) + 500;

try {

Log.d(TAG, "doWork start, threadId=" + Thread.currentThread().getId());

Thread.sleep(workTime);

Log.d(TAG, "doWork finished");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

@Override

protected void onDestroy() {

super.onDestroy();

mCompositeDisposable.clear();

}

}

startSimplePolling对应于固定时延轮询:

fa1828d70192

startAdvancePolling对应于变长时延轮询:

fa1828d70192

三、示例解析

下面,就让我们一起来分析一下上面这两个例子中涉及到的知识点。

3.1 intervalRange & doOnNext 实现固定时延轮询

对于固定时延轮询的需求,采用的是intervalRange的方式来实现,它是一个创建型操作符,该Observable第一次先发射一个特定的数据,之后间隔一段时间再发送一次,它是interval和range的结合体,这两个操作符的原理图为:

fa1828d70192

interval 原理图

fa1828d70192

range 原理图

该操作符的优势在于:

与interval相比,它可以指定第一个发送数据项的时延、指定发送数据项的个数。

与range相比,它可以指定两项数据之间发送的时延。

intervalRange的接收参数的含义为:

start:发送数据的起始值,为Long型。

count:总共发送多少项数据。

initialDelay:发送第一个数据项时的起始时延。

period:两项数据之间的间隔时间。

TimeUnit:时间单位。

在轮询操作中一般会进行一些耗时的网络请求,因此我们选择在doOnNext进行处理,它会在下游的onNext方法被回调之前调用,但是它的运行线程可以通过subscribeOn指定,下游的运行线程再通过observerOn切换会主线程,通过打印对应的线程ID可以验证结果。

当要求的数据项都发送完毕之后,最后会回调onComplete方法。

3.2 repeatWhen 实现变长时延轮询

3.2.1 使用 repeatWhen 实现重订阅

之所以可以通过repeatWhen来实现轮询,是因为它为我们提供了重订阅的功能,而重订阅有两点要素:

上游告诉我们一次订阅已经完成,这就需要上游回调onComplete函数。

我们告诉上游是否需要重订阅,通过repeatWhen的Function函数所返回的Observable确定,如果该Observable发送了onComplete或者onError则表示不需要重订阅,结束整个流程;否则触发重订阅的操作。

其原理图如下所示:

fa1828d70192

repeatWhen 原理图

repeatWhen的难点在于如何定义它的Function参数:

Function的输入是一个Observable,输出是一个泛型ObservableSource>。

如果输出的Observable发送了onComplete或者onError则表示不需要重订阅,结束整个流程;否则触发重订阅的操作。也就是说,它 仅仅是作为一个是否要触发重订阅的通知,onNext发送的是什么数据并不重要。

对于每一次订阅的数据流 Function 函数只会回调一次,并且是在onComplete的时候触发,它不会收到任何的onNext事件。

在Function函数中,必须对输入的 Observable进行处理,这里我们使用的是flatMap操作符接收上游的数据,对于flatMap的解释,大家可以参考 RxJava2 实战知识梳理(4) - 结合 Retrofit 请求新闻资讯 。

而当我们不需要重订阅时,有两种方式:

返回Observable.empty(),发送onComplete消息,但是DisposableObserver并不会回调onComplete。

返回Observable.error(new Throwable("Polling work finished")),DisposableObserver的onError会被回调,并接受传过去的错误信息。

3.2.2 使用 Timer 实现两次订阅之间的时延

以上就是对于repeatWhen的解释,与repeatWhen相类似的还有retryWhen操作符,这个我们在下一篇文章中再介绍,接下来,我们看一下如何实现两次事件的时延。

前面我们分析过,重订阅触发的时间是在返回的ObservableSource发送了onNext事件之后,那么我们通过该ObservableSource延迟发送一个事件就可以实现相应的需求,这里使用的是time操作符,它的原理图如下所示,也就是,在订阅完成后,等待指定的时间它才会发送消息。

fa1828d70192

timer 原理图

3.2.3 使用 doOnComplete 完成轮询的耗时操作

由于在订阅完成时会发送onComplete消息,那么我们就可以在doOnComplete中进行轮询所要进行的具体操作,它所运行的线程通过subscribeOn指定。

更多文章,欢迎访问我的 Android 知识梳理系列:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是您提供的代码的注释: ``` // 引入GPIO库 void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置GPIOB.8引脚为推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 使能GPIOD时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); // 配置GPIOD.15引脚为带下拉电阻的输入模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &GPIO_InitStructure); } // 主函数 void main(void) { // LED_Init(); GPIO_Configuration(); int flag = 0; led_init(); while (1) { // 如果GPIOD.15引脚被触摸 if (GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_15) == Bit_SET) { // 触摸 if (flag == 0) { D5_off(); // 关闭LED flag = 1; } else { D5_on(); // 打开LED flag = 0; } // 等待松开 while (GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_15) == Bit_SET) {} } } } ``` 这段代码主要是配置了两个GPIO引脚,一个为输出模式的GPIOB.8引脚,另一个为带下拉电阻的输入模式的GPIOD.15引脚。在主函数中,通过轮询的方式检测GPIOD.15引脚是否被触摸,如果被触摸则改变LED状态,并等待松开再继续轮询。需要注意的是,这段代码中没有使用中断,而是采用了轮询的方式检测GPIO引脚状态,因此可能会存在一定的延迟。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值