- 示例工程
- 教程
【免费下载链接】cw-omnibus
Source code to omnibus edition of _The Busy Coder's Guide to Android Development_
本文以 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.
也就是说,这个样本的核心意图有两个:
- 数据来源特殊:不是从数组或网络取数,而是解析应用自带的 XML 资源(
res/xml/words.xml); - 线程模型明确:解析必须发生在后台(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); } } }这里有四个值得展开的技术点:
Resources.getXml()加载资源:Android 编译时会把res/xml/下的文件打包进 APK 并以资源 ID 索引,运行时通过Resources#getXml(int)拿到一个XmlPullParser实例,全程无需操作文件路径或 IO 流对象。- 事件驱动式解析:
XmlPullParser是流式解析器,通过getEventType()判断当前事件(START_TAG、END_TAG、TEXT、END_DOCUMENT等),用next()推进游标。示例只关心START_TAG事件。 - 发射语义:每当解析到名为
word的起始标签,就把它的第一个属性值(getAttributeValue(0),即value属性)通过emitter.onNext(...)发射出去;遍历到END_DOCUMENT后调用emitter.onComplete()结束数据流。 - 异常即错误:整个解析过程包在
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!" 用于完成提示)。
注意事项与适用边界
基于对源码的逐行核对,使用本示例思路落地到自己的项目时,有几个边界需要留意:
getAttributeValue(0)的隐式约定:WordSource直接取第 0 个属性值,隐含假设是每个<word>节点的value属性恰好排在第一位。若 XML 结构调整(如新增属性),解析结果可能改变,更稳妥的做法是按属性名getAttributeValue(null, "value")取值。- 只处理 START_TAG:
TEXT类型文本、嵌套元素均被忽略,该解析器专为"扁平属性列表"设计,不适合带层级或文本内容的 XML。 - 适配器仅支持追加:
add()总是notifyItemInserted到末尾,没有插入中间位置或删除的能力,属于"流式填充列表"的场景专用实现。 dispose()不保证中断正在执行的解析:dispose()会阻止后续事件下发,但WordSource.subscribe()中的while循环本身没有检查emitter.isDisposed();不过本示例数据量小(25 个词)、解析耗时极短,实际影响可忽略。若数据量大,建议在循环内增加emitter.isDisposed()判断提前跳出。- 版本适用前提:整个示例基于 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_
相关推荐
cw-omnibus 示例解读:用 greenrobot EventBus 2.x 打通后台轮询与 UI 更新
cw omnibus 示例解读:用 greenrobot EventBus 2.x 打通后台轮询与 UI 更新 本指南以 cw omnibus 仓库中的 Eve
示例工程教程循环工程(Loop Engineering)概念溯源:核心来源、思想谱系与仓库落地映射
循环工程(Loop Engineering)概念溯源:核心来源、思想谱系与仓库落地映射 导读 本文以仓库中的 Sources & Attribution 来源文
示例工程教程AndroidKeyStore 授权超时机制实战:cw-omnibus SecureTimeout 示例源码解析
AndroidKeyStore 授权超时机制实战:cw omnibus SecureTimeout 示例源码解析 SecureTimeout 是 cw omni
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考