一、前言

接触RxJava2已经很久了,也看了网上的很多文章,发现基本都是在对RxJava的基本思想介绍之后,再去对各个操作符进行分析,但是看了之后感觉过了不久就忘了。

偶然的机会看到了开源项目 RxJava-Android-Samples,这里一共介绍了十六种RxJava2的使用场景,它从实际的应用场景出发介绍RxJava2的使用,特别适合对于RxJava2已经有初步了解的开发者进一步地去学习如何将其应用到实际开发当中。

因此,我打算跟着这个项目的思路编写一系列实战的介绍并完成示例代码编写,并对该实例中用到的知识进行介绍,做到学以致用。下面,就开始第一个例子的学习,源码的仓库为:RxSample

二、示例

2.1 应用场景

当我们需要进行一些耗时操作,例如下载、访问数据库等,为了不阻塞主线程,往往会将其放在后台进行处理,同时在处理的过程中、处理完成后通知主线程更新UI,这里就涉及到了后台线程和主线程之间的切换。首先回忆一下,在以前我们一般会用以下两种方式来实现这一效果:

  • 创建一个新的子线程,在其run()方法中执行耗时的操作,并通过一个和主线程Looper关联的Handler发送消息给主线程更新进度显示、处理结果。
  • 使用AsyncTask,在其doInBackground方法中执行耗时的操作,调用publishProgress方法通知主线程,然后在onProgressUpdate中更新进度显示,在onPostExecute中显示最终结果。

那么,让我们看一些在RxJava中如何完成这一需求。

2.2 示例代码

我们的界面上有一个按钮mTvDownload,点击之后会发起一个耗时的任务,这里我们用Thread.sleep来模拟耗时的操作,每隔500ms我们会将当前的进度通知主线程,在mTvDownloadResult中显示当前处理的进度。

public class BackgroundActivity extends AppCompatActivity {

    private TextView mTvDownload;
private TextView mTvDownloadResult;
private CompositeDisposable mCompositeDisposable = new CompositeDisposable(); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_background);
mTvDownload = (TextView) findViewById(R.id.tv_download);
mTvDownloadResult = (TextView) findViewById(R.id.tv_download_result);
mTvDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startDownload();
}
});
} private void startDownload() {
final Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() { @Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
for (int i = 0; i < 100; i++) {
if (i % 20 == 0) {
try {
Thread.sleep(500); //模拟下载的操作。
} catch (InterruptedException exception) {
if (!e.isDisposed()) {
e.onError(exception);
}
}
e.onNext(i);
}
}
e.onComplete();
} });
DisposableObserver<Integer> disposableObserver = new DisposableObserver<Integer>() { @Override
public void onNext(Integer value) {
Log.d("BackgroundActivity", "onNext=" + value);
mTvDownloadResult.setText("Current Progress=" + value);
} @Override
public void onError(Throwable e) {
Log.d("BackgroundActivity", "onError=" + e);
mTvDownloadResult.setText("Download Error");
} @Override
public void onComplete() {
Log.d("BackgroundActivity", "onComplete");
mTvDownloadResult.setText("Download onComplete");
}
};
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
} @Override
protected void onDestroy() {
super.onDestroy();
mCompositeDisposable.clear();
}
}

实际的运行结果如下:

三、示例解析

3.1 线程切换

在上面的例子中,涉及到了两种类型的操作:

  • 需要在后台执行的耗时操作,对应于subscribe(ObservableEmitter<Integer> e)中的代码。
  • 需要在主线程进行UI更新的操作,对应于DisposableObserver的所有回调,具体的是在onNext中进行进度的更新;在onCompleteonError中展示最终的处理结果。

那么,这两种类型操作所运行的线程是在哪里指定的呢,关键是下面这句:

observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
  • subscribeOn(Schedulers.io()):指定observablesubscribe方法运行在后台线程。
  • observeOn(AndroidSchedulers.mainThread()):指定observer的回调方法运行在主线程。

这两个函数刚开始的时候很有可能弄混,我是这么记的,subscribeOns开头,可以理解为“上游”开头的谐音,也就是上游执行的线程。

关于这两个函数,还有一点说明:多次调用subscribeOn,会以第一次的为准;而多次调用observeOn则会以最后一次的为准,不过一般我们都不会这么干,就不举例子了。

3.2 线程的类型

subscribeOn/observeOn都要求传入一个Schedulers的子类,它就代表了运行线程类型,下面我们来看一下都有哪些选择:

  • Schedulers.computation():用于计算任务,默认线程数等于处理器的数量。
  • Schedulers.from(Executor executor):使用Executor作为调度器,关于Executor框架可以参考这篇文章:多线程知识梳理(5) - 线程池四部曲之 Executor 框架
  • Schedulers.io( ):用于IO密集型任务,例如访问网络、数据库操作等,也是我们最常使用的。
  • Schedulers.newThread( ):为每一个任务创建一个新的线程。
  • Schedulers.trampoline( ):当其它排队的任务完成后,在当前线程排队开始执行。
  • Schedulers.single():所有任务共用一个后台线程。

以上是在io.reactivex.schedulers包中,提供的Schedulers,而如果我们导入了下面的依赖,那么在io.reactivex.android.schedulers下,还有额外的两个Schedulers可选:

compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
  • AndroidSchedulers.mainThread():运行在应用程序的主线程。
  • AndroidSchedulers.from(Looper looper):运行在该looper对应的线程当中。

3.3 使用 CompositeDisposable 对下游进行管理

如果Activity要被销毁时,我们的后台任务没有执行完,那么就会导致Activity不能正常回收,而对于每一个Observer,都会有一个Disposable对象用于管理,而RxJava提供了一个CompositeDisposable类用于管理这些Disposable,我们只需要将其将入到该集合当中,在ActivityonDestroy方法中,调用它的clear方法,就能避免内存泄漏的发生。

四、小结

这个系列的第一篇文章,我们介绍了如何使用subscribeOn/observeOn来实现后台执行耗时任务,并通知主线程更新进度。

RxJava2-后台执行耗时操作,实时通知 UI 更新(一)的更多相关文章

  1. Winform 界面执行耗时操作--UI卡顿假死问题

    UI卡顿假死问题 误区1:使用不同的线程操作UI控件和耗时操作(即,跨线程操作UI控件CheckForIllegalCrossThreadCalls = false;), 注意:此处只是为了记录... ...

  2. ASP.NET服务器端执行耗时操作的工作记录

    公司之前有这样一个业务需求: 一名同事做出文件a0和b0,然后将a0加密为a1.b0加密为b1:再将文件a0.a1.b0和b1上传至服务器M:同时要将服务器N上的数据表添加一条记录,该记录的ID就是前 ...

  3. Parallel 类并行任务(仅仅当执行耗时操作时,才有必要使用)

    using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using S ...

  4. C# WinForm 异步执行耗时操作并将过程显示在界面中

    private void button3_Click(object sender, EventArgs e)        {            RunAsync(() =>         ...

  5. 主线程不能执行耗时的操作,子线程不能更新Ui

    在Android项目中经常有碰到这样的问题,在子线程中完成耗时操作之后要更新UI,下面就自己经历的一些项目总结一下更新的方法: 在看方法之前看一下Android中消息机制: 引用 Message:消息 ...

  6. C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较

    使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...

  7. AsyncTask 进行耗时操作和UI 更新

    相信各位对 AsyncTask 不会陌生,虽然它有如下弊端: 1. 如果在activiy内部new 一个AsyncTask, 横竖屏切换生成一个新的activity,等结果返回时,处理不好容易出现NP ...

  8. winform 开发中 把耗时操作 封装起来 异步执行(.net 4.0)

    .先定义一个 BackgroundTask.cs 代码如下: public class BackgroundTask { private static WaitDialogForm LoadingDl ...

  9. 网络请求怎么样和UI线程交互? Activity2怎么通知Activity1 更新数据

    1.网络请求怎么样和UI线程交互? 目前我的做法是,建立线程池管理网络请求线程,通过添加task来新增网络请求.所有的网络操作通过统一的request来实现,网络返回结果通过回调onError和onS ...

随机推荐

  1. golang 特殊知识点

    golang 代码不需要分号; 但是又会自己在底层增加;号 ,所以 golang的{左花括号必须在代码的最后一行,而不能在新的一行; golang 代码组织里需要注意 vendor 和 interna ...

  2. hive sql 里面的注释方式

    如果建表ddl 用 comment 这个没问题 那么在sql 语句里面呢,这个貌似不像mysql 那样能用 # // /* */ (左边这些都不行) 其实用 -- comment 就行啦 貌似上面的- ...

  3. Gcode命令【转】

    https://www.jianshu.com/p/f8a328457a45 简述 研究过3D打印机的朋友,都会用到G-code文件.要使用3D打印机打印东西要经过几个步骤:        1.创建3 ...

  4. 自动化web前端测试,自动登录网站.目前发现最靠谱的方法是imacros

    imacros免费版 登录宏代码的示例: //首先登出URL GOTO=http://yoursite/logout.html//打开登录页面URL GOTO=http://yoursite/logi ...

  5. jetty9.4缓存文件目录自定义

    jetty9.4安装包解压之后,有几个修改的地方和jetty7.6有不同,需要单独注意一下: 1. 端口设置 端口设置在${jetty_home}/start.ini中 2. 缓存文件生成目录 项目通 ...

  6. Git diff结果显示分析

    1.diff的三种格式: 正常格式(normal diff) 上下文格式(context diff) 合并格式(unified diff) 2.示例文件为了便于讲解,先新建两个示例文件.第一个文件叫做 ...

  7. C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别

    C#语法——泛型的多种应用   本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...

  8. apache的server-status如何分析的技术说明

    XML/HTML代码 Apache Server Status for www.blogguy.cn Server Version: Apache/2.2.9 (Debian) PHP/5.2.6-1 ...

  9. SATA主机协议的FPGA实现之准备工作

    SATA主机协议的FPGA实现之准备工作   从2月中旬准备开始,经过3个月的奋战,我的又一个项目--基于FPGA的固态硬盘读写控制电路,已经基本实现.由于实用资料的匮乏,以及项目本身颇具挑战性,这个 ...

  10. nginx实现限速

    项目中有一个需求,需要限制每个容器的网速,避免某些容器占用太多资源,导致其他容器无法使用,但是docker对于网速的限制支持的有点弱,由于容器中的所有进程和APP的交互都是通过nginx的,所以就想到 ...