☰
用 RxJava 2 与 RxAndroid 在后台解析 XML 资源:cw-omnibus 的 Rx/XML 示例源码全解读
2026/10/16 3:57:54 网站建设 项目流程
  • 示例工程
  • 教程

【免费下载链接】cw-omnibus

Source code to omnibus edition of _The Busy Coder's Guide to Android Development_

项目地址:https://gitcode.com/gh_mirrors/cw/cw-omnibus
点击查看免费下载

本文以 cw-omnibus 仓库中的 Rx/XML 示例 为骨架,深入讲解如何用 RxJava 2 + RxAndroid 在后台线程解析res/xml/下的 XML 资源,并把解析结果增量送入 RecyclerView 展示。读完本文,你将掌握Observable.create()自定义数据源、Schedulers.io()/AndroidSchedulers.mainThread()线程切换、Disposable生命周期管理,以及XmlPullParser流式解析 XML 资源的完整实战套路。

示例概览:一条"资源 → 解析 → 渲染"的完整链路

仓库中的 Rx/XML/README.markdown 用一句话点明了这个示例的用途:

This sample Android app demonstrates parsing an XML resource in the background, using RxJava and RxAndroid.

也就是说,这个样本的核心意图有两个:

  1. 数据来源特殊:不是从数组或网络取数,而是解析应用自带的 XML 资源(res/xml/words.xml);
  2. 线程模型明确:解析必须发生在后台(I/O 线程),UI 更新回到主线程——这正是 RxJava + RxAndroid 最典型的组合场景。

该示例属于《The Busy Coder's Guide to Android Development》一书 RxJava 相关章节的配套代码(由 CommonsWare 提供,源码头部以 Apache License 2.0 开源)。仓库中该示例的完整结构如下:

  • Rx/XML/app/src/main/java/com/commonsware/android/rx/RxDemo.java:宿主FragmentActivity,负责挂载RxDemoFragment;
  • Rx/XML/app/src/main/java/com/commonsware/android/rx/RxDemoFragment.java:核心逻辑所在,包含 Observable 链、XML 解析源与 RecyclerView 适配器;
  • Rx/XML/app/src/main/res/xml/words.xml:被解析的 XML 资源,内含 25 个拉丁词汇;
  • Rx/XML/app/src/main/res/layout/main.xml:仅声明一个全屏 RecyclerView;
  • Rx/XML/app/build.gradle:依赖与构建配置。

整体数据流为:words.xml→XmlPullParser流式读取 → 逐个发射单词 → 转大写 → 主线程追加到列表 → RecyclerView 逐行插入。

数据源设计:用res/xml资源 + XmlPullParser 打造可订阅的数据流

XML 资源文件:words.xml

words.xml 是一个非常简单的词表,根节点为<words>,每个<word>节点用一个value属性携带单词:

<words> <word value="lorem" /> <word value="ipsum" /> <word value="dolor" /> <word value="sit" /> <word value="amet" /> <!-- ……共 25 个词,以 <word value="purus" /> 结尾 --> </words>

这段资源的解析结果,正好与仓库中 Rx/Simple 与 Rx/Observable 两个样本里硬编码的ITEMS字符串数组一一对应——说明它们演示的是同一批数据、不同的数据来源方式,方便读者对照"数组 vs 资源文件"两种取数思路。

订阅源实现:WordSource implements ObservableOnSubscribe<String>

解析动作被封装在 RxDemoFragment.java 的静态内部类WordSource中,它实现了 RxJava 2 的ObservableOnSubscribe<String>接口,在subscribe(ObservableEmitter<String> emitter)方法内完成整段 XML 解析:

private static class WordSource implements ObservableOnSubscribe<String> { private final Resources resources; WordSource(Context ctxt) { resources=ctxt.getResources(); } @Override public void subscribe(ObservableEmitter<String> emitter) { try { XmlPullParser xpp=resources.getXml(R.xml.words); while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) { if (xpp.getEventType()==XmlPullParser.START_TAG) { if (xpp.getName().equals("word")) { emitter.onNext(xpp.getAttributeValue(0)); } } xpp.next(); } emitter.onComplete(); } catch (Exception e) { emitter.onError(e); } } }

这里有四个值得展开的技术点:

  1. Resources.getXml()加载资源:Android 编译时会把res/xml/下的文件打包进 APK 并以资源 ID 索引,运行时通过Resources#getXml(int)拿到一个XmlPullParser实例,全程无需操作文件路径或 IO 流对象。
  2. 事件驱动式解析:XmlPullParser是流式解析器,通过getEventType()判断当前事件(START_TAG、END_TAG、TEXT、END_DOCUMENT等),用next()推进游标。示例只关心START_TAG事件。
  3. 发射语义:每当解析到名为word的起始标签,就把它的第一个属性值(getAttributeValue(0),即value属性)通过emitter.onNext(...)发射出去;遍历到END_DOCUMENT后调用emitter.onComplete()结束数据流。
  4. 异常即错误:整个解析过程包在try/catch中,任何异常(包括 XML 格式错误、资源不存在)都会被转为emitter.onError(e),从而走上 RxJava 的错误处理通道,而不是让后台线程崩溃。

从源码结构可以看出,WordSource是一个按需懒执行的生产者:只有真正被订阅(subscribe)时,subscribe()方法才会被调用、解析才会开始——这正是响应式编程"数据随订阅流动"的体现。

核心 Observable 链:五段式操作符编排

在RxDemoFragment.onCreate()中,示例用一段链式调用把WordSource接入线程调度与 UI 刷新管线,见 RxDemoFragment.java:

Observable<String> observable=Observable .create(new WordSource(getActivity())) .subscribeOn(Schedulers.io()) .map(s -> (s.toUpperCase())) .observeOn(AndroidSchedulers.mainThread()) .doOnComplete(() -> Toast.makeText(getActivity(), R.string.done, Toast.LENGTH_SHORT).show()); sub=observable.subscribe(s -> adapter.add(s), error -> Toast .makeText(getActivity(), error.getMessage(), Toast.LENGTH_LONG) .show());

把这段链式调用拆开来看:

阶段作用说明
Observable.create(new WordSource(...))建立数据源传入实现了ObservableOnSubscribe<String>的解析器
subscribeOn(Schedulers.io())指定"上游执行线程"解析在 I/O 调度器线程执行,不阻塞主线程
map(s -> s.toUpperCase())数据变换每个单词转大写后才进入下一环节
observeOn(AndroidSchedulers.mainThread())切换"下游观察线程"之后的所有回调回到主线程
doOnComplete(...)旁路通知数据流正常结束时弹 Toast "Done!"
subscribe(...)订阅onNext追加到适配器,onError弹 Toast 展示错误信息

其中subscribe()返回的Disposable被保存在字段sub中(见第 43 行声明),供生命周期结束时释放——这一点会在下文"生命周期管理"小节展开。

值得注意的是map的位置:它在observeOn之前,因此大写转换也运行在 I/O 线程上,避免了在主线程做不必要的计算。这种"重活在上游、UI 更新在下游"的切分,是 RxJava 线程调度的标准姿势。

线程调度:Schedulers.io()与AndroidSchedulers.mainThread()的分工

示例在依赖层面同时引入了rxjava与rxandroid两个库,见 Rx/XML/app/build.gradle:

implementation 'io.reactivex.rxjava2:rxjava:2.1.7' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
  • Schedulers(来自 rxjava):提供Schedulers.io()这类与平台无关的调度器;
  • AndroidSchedulers(来自 rxandroid):提供AndroidSchedulers.mainThread(),其本质是基于 Android 主线程Handler/Looper的调度器,保证回调能回到主线程执行 UI 操作。

示例选择Schedulers.io()而非Schedulers.newThread(),从实践角度很合理:I/O 调度器内部维护可复用的线程池,适合执行像 XML 解析这类存在阻塞与空闲等待的轻量后台任务,避免每次订阅都新建线程。

可以对比同目录下的兄弟样本,进一步理解这种取舍:

  • Rx/Simple 连 rxandroid 都不依赖,直接在AsyncTask的doInBackground()里同步subscribe,UI 更新靠publishProgress()回传;
  • Rx/Observable 开始使用Observable.create()+subscribeOn(Schedulers.newThread()),但解析源仍是硬编码数组;
  • Rx/RxAndroid 用subscribeOn(Schedulers.newThread())+observeOn(AndroidSchedulers.mainThread())演示纯线程调度;
  • 本示例 Rx/XML 则把Schedulers.io()与真实资源解析结合,是最接近生产场景的一版。

这种同主题逐层递进的编排(同一批单词、四种取数与调度方案),本身就是阅读 cw-omnibus Rx 系列最有效率的方式。

生命周期管理:Disposable与onDestroy中的释放

RxJava 2 把"取消订阅"的职责抽象为Disposable。示例在 onDestroy() 中做了标准释放:

@Override public void onDestroy() { if (sub!=null && !sub.isDisposed()) { sub.dispose(); } super.onDestroy(); }
  • isDisposed()先判断是否已释放,避免重复调用;
  • dispose()之后,Observable将停止向订阅者推送事件,后台的解析循环也会随之终止,防止 Fragment 销毁后仍有后台线程回调、触发对已销毁 Context 的访问(如adapter.add()、Toast)。

这个模式与 Rx/RxAndroid 样本中的写法完全一致,属于该系列统一的生命周期约定。

另外注意:示例没有调用setRetainInstance(true)(而同目录的 Rx/Simple、Rx/Observable 样本调用了)。从源码结构看,这意味着该示例不依赖"旋转时保留 Fragment 实例"来维持订阅,而是通过及时dispose()规避泄漏——两种思路都成立,读者可自行权衡。

界面呈现:RecyclerView 与"追加式"适配器

数据到达后,需要被展示出来。示例的界面层同样值得一读。

布局:单个 RecyclerView

main.xml 非常精简,整个界面就是一个撑满屏幕的 RecyclerView:

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" />

适配器:仅支持"追加"的增量插入

内部类RVArrayAdapter继承RecyclerView.Adapter<RowHolder>(见 RxDemoFragment.java),其关键方法是add():

private void add(String word) { words.add(word); notifyItemInserted(words.size()-1); }

每次收到一个单词,就追加到ArrayList末尾,并调用notifyItemInserted()让 RecyclerView 只刷新新增的那一行——这种"逐条插入 + 精准通知"的方式比notifyDataSetChanged()全量刷新更高效,也天然契合流式onNext的节奏。行布局复用系统自带的android.R.layout.simple_list_item_1,RowHolder只负责绑定android.R.id.text1这个 TextView(见 RxDemoFragment.java)。

在onViewCreated()中,RecyclerView 配置了LinearLayoutManager和DividerItemDecoration.VERTICAL分隔线(见 RxDemoFragment.java),完成列表的最终呈现。

宿主 Activity

RxDemo.java 继承android.support.v4.app.FragmentActivity,在onCreate()中通过getSupportFragmentManager()以事务方式把RxDemoFragment挂到系统内容容器android.R.id.content上,并用判空保护避免配置变更时重复添加。

构建配置与运行环境要点

该示例的构建参数集中在 build.gradle,是理解它可运行前提的关键:

apply plugin: 'com.android.application' dependencies { implementation "com.android.support:recyclerview-v7:27.1.1" implementation "com.android.support:support-fragment:27.1.1" implementation 'io.reactivex.rxjava2:rxjava:2.1.7' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' } android { compileSdkVersion 27 buildToolsVersion '27.0.3' defaultConfig { minSdkVersion 15 targetSdkVersion 27 applicationId "com.commonsware.android.rx.xml" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }

几个对复现实验有直接影响的点:

  • 依赖版本:RxJava 2.1.7、RxAndroid 2.0.1,属于 RxJava 2.x 时代的组合;如果你在本机新建工程,io.reactivex.rxjava2的包名与ObservableOnSubscribe、Disposable等 API 均以本示例所用版本为基准。
  • minSdkVersion 15:示例最低支持 Android 4.0.3(API 15)。
  • Java 8 编译:compileOptions开启了 Java 8 兼容,代码中大量使用 lambda 表达式(如s -> s.toUpperCase()、s -> adapter.add(s))都依赖于此。
  • gradle wrapper:gradle-wrapper.properties 指向 Gradle 4.4,配合 gradle.properties 中的org.gradle.jvmargs=-Xmx1536M(JVM 堆内存上限 1536 MB)即可构建。
  • 混淆配置:proguard.cfg 是标准的 Android 组件 keep 规则(Activity、Service、BroadcastReceiver 等),若开启混淆发布,需保留这些规则。
  • Manifest:AndroidManifest.xml 声明了唯一的入口 Activity.RxDemo与 LAUNCHER intent-filter,应用名取自@string/app_name(即 strings.xml 中的 "Rx XML Demo",另有一个done字符串 "Done!" 用于完成提示)。

注意事项与适用边界

基于对源码的逐行核对,使用本示例思路落地到自己的项目时,有几个边界需要留意:

  1. getAttributeValue(0)的隐式约定:WordSource直接取第 0 个属性值,隐含假设是每个<word>节点的value属性恰好排在第一位。若 XML 结构调整(如新增属性),解析结果可能改变,更稳妥的做法是按属性名getAttributeValue(null, "value")取值。
  2. 只处理 START_TAG:TEXT类型文本、嵌套元素均被忽略,该解析器专为"扁平属性列表"设计,不适合带层级或文本内容的 XML。
  3. 适配器仅支持追加:add()总是notifyItemInserted到末尾,没有插入中间位置或删除的能力,属于"流式填充列表"的场景专用实现。
  4. dispose()不保证中断正在执行的解析:dispose()会阻止后续事件下发,但WordSource.subscribe()中的while循环本身没有检查emitter.isDisposed();不过本示例数据量小(25 个词)、解析耗时极短,实际影响可忽略。若数据量大,建议在循环内增加emitter.isDisposed()判断提前跳出。
  5. 版本适用前提:整个示例基于 support library 27.1.1 与 RxJava 2.x,若要迁移到 AndroidX 或 RxJava 3.x,需要同步替换包名与部分 API(如io.reactivex.Observable→io.reactivex.rxjava3.core.Observable)。

小结

cw-omnibus 的 Rx/XML 是一个小而完整的范本:数据源来自真实的 XML 资源解析(XmlPullParser)、线程切换使用了 RxJava 2 的subscribeOn/observeOn组合、界面更新走 RecyclerView 增量插入、生命周期通过Disposable.dispose()收尾。与同目录的 Rx/Simple、Rx/Observable、Rx/RxAndroid 三个样本连起来读,恰好构成一条从"AsyncTask 硬编码"到"后台解析真实资源"的 RxJava 演进主线,是理解 Android 响应式编程落地方式的绝佳入门素材。

  • 示例工程
  • 教程

【免费下载链接】cw-omnibus

Source code to omnibus edition of _The Busy Coder's Guide to Android Development_

项目地址:https://gitcode.com/gh_mirrors/cw/cw-omnibus
点击查看免费下载
上一篇:探索极致舒适:Piantor 机械键盘
下一篇:DCGM-Exporter 项目使用教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询