1. 这不是“学两个库”,而是重构Android网络层的实战路径
RxJava和Retrofit这两个词在Android开发圈里,早就不只是“热门框架”那么简单了。它们是2015年前后那波响应式编程浪潮里真正落地、被成千上万App验证过的组合拳——不是教科书里的概念演示,而是每天在主线程崩溃、网络超时重试、多接口并发、数据转换混乱这些真实战场里杀出来的生存方案。我带过三届校招新人,第一周必做的一件事,就是删掉他们用AsyncTask写的登录模块,换成RxJava + Retrofit的链式调用。为什么?因为AsyncTask在Activity重建时容易内存泄漏,而Retrofit的Call对象本身不持有上下文,RxJava的Disposable又能精准控制生命周期绑定。这不是炫技,是把“网络请求失败后弹Toast”这种基础动作,从“写十行if-else判断状态码”压缩到一行
.onErrorResumeNext()
就能兜底的工程效率。你搜“Android Studio怎么设置中文”,说明你刚装好环境;但当你真正开始写第一个网络请求时,会发现官方文档里那句“Retrofit converts your HTTP API into a Java interface”背后,藏着的是OkHttp拦截器怎么加Token、GsonConverterFactory怎么处理null字段、RxJava的背压策略怎么防OOM这些必须亲手踩坑才能懂的细节。这篇文章不讲“什么是Observable”,也不列一堆API方法签名,只聚焦一件事:如何用这套组合,在真实项目中稳定发出请求、优雅处理错误、无缝绑定页面生命周期、并为后续加入Kotlin协程留出平滑升级路径。适合正在用原生HttpURLConnection写接口、或刚接触Retrofit但卡在“怎么把JSON转成List
”的新手,也适合想把老项目里散落各处的Callback回调收口成统一响应流的中级开发者。
2. 整体设计思路:为什么是RxJava + Retrofit,而不是其他组合?
2.1 Retrofit不是“替代OkHttp”,而是“定义契约”的抽象层
很多人第一次用Retrofit时会困惑:既然底层还是OkHttp,那我直接用OkHttp不更简单?错。Retrofit的核心价值在于 将HTTP通信协议转化为Java接口契约 。举个实际例子:某电商App的首页需要同时加载轮播图、商品列表、促销Banner三个接口。如果用OkHttp,你得手动拼URL、设置Header、解析JSON、处理异常,每个接口都得重复写一遍。而Retrofit让你定义一个接口:
public interface ApiService {
@GET("v1/banner")
Observable<BannerResponse> getBanners();
@GET("v1/products")
Observable<ProductListResponse> getProducts(@Query("page") int page);
@POST("v1/login")
Observable<LoginResponse> login(@Body LoginRequest request);
}
看到没?
@GET
、
@Query
、
@Body
这些注解,本质是把HTTP的动词、参数位置、数据格式等语义,直接映射到Java方法签名上。Retrofit在编译期就生成了实现类,运行时只需调用
apiService.getBanners()
,它自动完成URL拼接、Header注入、序列化、网络调度。这带来的好处是:
接口变更时,只需改接口定义,所有调用点立刻编译报错
——比在几十个Activity里手动搜索
http://api.xxx.com/v1/banner
安全十倍。我曾维护过一个金融类App,后端把
/v1/products
改成
/v2/products
,用Retrofit的话,改一行接口定义+重新编译,IDE立刻标红所有调用处;而用OkHttp硬编码URL的模块,上线前靠人工grep漏掉了一个冷门Fragment的请求,导致用户进入该页面时白屏。这就是契约的力量。
2.2 RxJava不是“为了响应式而响应式”,而是解决“异步嵌套地狱”的手术刀
再看RxJava。它的核心价值常被误解为“链式调用很酷”。其实不然。真正的痛点是
多异步任务的时序控制与错误传播
。比如一个典型场景:用户点击“同步通讯录”按钮,需先调用
getContactList()
获取本地联系人,再对每个联系人调用
checkInServer(contact)
检查是否已注册,最后汇总结果。用传统Callback写:
// 伪代码:三层嵌套回调
getContactList(new Callback<List<Contact>>() {
public void onSuccess(List<Contact> contacts) {
for (Contact c : contacts) {
checkInServer(c, new Callback<Boolean>() {
public void onSuccess(Boolean exists) {
if (exists) {
// 再调用updateContact()
updateContact(c, new Callback<Void>() { ... });
}
}
});
}
}
});
这种代码的维护成本极高:错误处理分散(每个Callback都要写onFailure)、取消逻辑复杂(用户中途退出要逐层cancel)、线程切换混乱(UI更新必须切回主线程)。RxJava用
flatMap
把嵌套展平:
contactListObservable
.flatMap(contact -> checkInServer(contact)
.map(exists -> exists ? updateContact(contact) : Completable.complete())
)
.subscribe(
() -> showSuccess(),
error -> showError(error)
);
关键点在于:
整个流程变成一个可观察的数据流,错误统一在subscribe的第二个参数捕获,取消只需调用Disposable.dispose()
。我参与过一个医疗App的改造,旧版用AsyncTask嵌套四层,每次修改一个接口都要花半小时理清回调链;换成RxJava后,新增一个“上传用户定位”步骤,只需在链中插入
.andThen(uploadLocation())
,其他逻辑零改动。这才是RxJava不可替代的价值——它让异步逻辑像同步代码一样线性可读。
2.3 二者组合的化学反应:生命周期绑定与背压管理
单独用Retrofit,你得自己管理Call的cancel;单独用RxJava,你得自己写网络请求逻辑。二者结合后,产生了质变:
-
生命周期绑定 :通过
compose(bindToLifecycle())(配合RxLifecycle)或bindUntilEvent(ActivityEvent.DESTROY),让网络请求在Activity销毁时自动取消,彻底杜绝内存泄漏。我们曾统计过,某社交App崩溃日志中37%是Activity has leaked window,引入此机制后归零。 -
背压管理 :当上游(如传感器每毫秒发一个数据)速度远超下游(UI每秒刷新一次)时,RxJava的
onBackpressureBuffer()能缓存溢出数据,避免OOM。虽然网络请求通常不涉及高频数据流,但在处理大文件分片上传、WebSocket实时消息时,这是救命功能。
提示:不要盲目追求“最新技术”。Kotlin协程现在更流行,但RxJava在Java项目中仍有不可替代性——尤其当你需要
retryWhen()实现指数退避重试、或concatMapEager()并行执行多个独立请求时,协程的async/await写法反而更冗长。
3. 核心细节解析:从零搭建可复用的网络层
3.1 基础依赖配置与版本选择
在
app/build.gradle
中添加依赖,
版本必须严格匹配
,否则会出现
NoSuchMethodError
:
// Retrofit核心
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// 将JSON转为Java对象(Gson)
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// 适配RxJava2(注意:RxJava3需用adapter-rxjava3)
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
// RxJava2核心
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
// 网络拦截器(用于日志、Token)
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
为什么选2.9.0?因为它是Retrofit 2.x最后一个稳定版,兼容Android 4.0+且无已知OOM漏洞;而RxJava2.2.21修复了2.1.x中
observeOn()
在低内存设备上的死锁问题。我见过太多团队因随意升级到Retrofit 2.10.0(需AndroidX强制迁移)导致构建失败,最终退回2.9.0。
生产环境永远选“最新稳定版”,而非“最新版”
。
3.2 Retrofit初始化:单例模式与动态BaseUrl
Retrofit实例必须全局单例,否则会创建大量OkHttpClient浪费资源。但BaseUrl可能随环境变化(开发/测试/生产),不能硬编码:
public class RetrofitClient {
private static volatile Retrofit instance;
public static Retrofit getInstance(String baseUrl) {
if (instance == null) {
synchronized (RetrofitClient.class) {
if (instance == null) {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor()) // 日志拦截器
.addInterceptor(new TokenInterceptor()) // Token拦截器
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss") // 统一时间格式
.create();
instance = new Retrofit.Builder()
.baseUrl(baseUrl) // 动态传入
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
}
}
return instance;
}
}
关键点:
-
volatile保证多线程安全,synchronized双重检查避免重复初始化; -
LoggingInterceptor用于开发环境打印请求/响应, 上线前必须移除 ,否则泄露敏感信息; -
TokenInterceptor在每次请求Header中自动添加Authorization: Bearer xxx,避免每个接口都手动setHeader; -
GsonBuilder().setDateFormat()解决后端返回时间字符串格式不一致问题(如"2023-01-01"vs"2023-01-01T00:00:00Z")。
注意:
addCallAdapterFactory()必须在addConverterFactory()之后调用,否则Retrofit无法识别RxJava类型。这个顺序错误会导致IllegalArgumentException: Unable to create call adapter,调试时需重点检查。
3.3 数据模型设计:应对后端JSON结构不规范
后端返回的JSON往往不规范,比如
code
字段可能是int或String,
data
字段可能为null或空对象。Gson默认会抛出
JsonParseException
。解决方案是自定义TypeAdapter:
public class ApiResponseAdapter<T> implements JsonSerializer<ApiResponse<T>>, JsonDeserializer<ApiResponse<T>> {
@Override
public JsonElement serialize(ApiResponse<T> src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("code", src.getCode());
jsonObject.addProperty("msg", src.getMsg());
if (src.getData() != null) {
jsonObject.add("data", context.serialize(src.getData()));
} else {
jsonObject.add("data", JsonNull.INSTANCE); // 显式设为null
}
return jsonObject;
}
@Override
public ApiResponse<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
int code = jsonObject.get("code").getAsInt();
String msg = jsonObject.get("msg").getAsString();
// 关键:处理data为null或非对象的情况
JsonElement dataElement = jsonObject.get("data");
T data = null;
if (dataElement != null && !dataElement.isJsonNull()) {
try {
// 尝试解析为泛型T
data = context.deserialize(dataElement, getDataType(typeOfT));
} catch (Exception e) {
// 解析失败时设为null,避免崩溃
data = null;
}
}
return new ApiResponse<>(code, msg, data);
}
}
然后在GsonBuilder中注册:
Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken<ApiResponse<User>>(){}.getType(), new ApiResponseAdapter<>())
.create();
这样即使后端返回
{"code":200,"msg":"success","data":null}
,也能安全解析为
ApiResponse<User>
,
getData()
返回null而非抛异常。我们曾因后端未按约定返回
data
字段,导致App在某个小众机型上闪退,加了此适配器后问题消失。
3.4 错误统一处理:从HTTP状态码到业务异常
网络请求失败分三层:网络层(DNS失败)、HTTP层(404/500)、业务层(code=1001表示登录过期)。RxJava的
onErrorResumeNext()
可逐层兜底:
apiService.login(user)
.subscribeOn(Schedulers.io()) // 切换到IO线程执行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 切换回主线程更新UI
.doOnSubscribe(disposable -> showLoading()) // 请求开始时显示loading
.doFinally(() -> hideLoading()) // 无论成功失败都隐藏loading
.onErrorResumeNext(throwable -> {
if (throwable instanceof IOException) {
// 网络异常:无网络、超时
return Observable.just(ApiResponse.error(-1, "网络连接失败,请检查网络设置"));
} else if (throwable instanceof HttpException) {
// HTTP异常:4xx/5xx
HttpException httpException = (HttpException) throwable;
int code = httpException.code();
if (code == 401) {
// 登录过期,跳转登录页
startActivity(LoginActivity.class);
return Observable.empty(); // 不向下传递数据
}
return Observable.just(ApiResponse.error(code, "服务器异常,请稍后重试"));
} else {
// 其他异常(如JSON解析失败)
return Observable.just(ApiResponse.error(-2, "数据解析错误"));
}
})
.filter(response -> response.getCode() == 200) // 过滤业务失败响应
.map(ApiResponse::getData) // 提取data字段
.subscribe(
user -> navigateToHome(user),
error -> showError(error.getMessage())
);
这里的关键技巧:
-
doOnSubscribe()和doFinally()确保loading状态与请求生命周期严格同步; -
onErrorResumeNext()将各种异常统一转为ApiResponse,保持下游数据流类型一致; -
filter()在数据流中过滤掉业务失败(如code=1001),避免在subscribe中写大量if判断。
4. 实操过程:从发起请求到展示数据的完整链路
4.1 定义API接口与响应模型
以“获取用户信息”为例,后端返回JSON:
{
"code": 200,
"msg": "success",
"data": {
"id": 123,
"name": "张三",
"avatar": "https://xxx.com/avatar.jpg",
"level": 5
}
}
定义Java模型:
// 基础响应包装类
public class ApiResponse<T> {
private int code;
private String msg;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.code = 200;
response.msg = "success";
response.data = data;
return response;
}
public static <T> ApiResponse<T> error(int code, String msg) {
ApiResponse<T> response = new ApiResponse<>();
response.code = code;
response.msg = msg;
return response;
}
// getter/setter省略
}
// 用户数据模型
public class User {
private long id;
private String name;
private String avatar;
private int level;
// 注意:Gson要求字段名与JSON完全一致,或用@JsonProperty("avatar_url")
@SerializedName("avatar_url")
private String avatarUrl;
// 构造函数、getter/setter
}
定义API接口:
public interface UserService {
@GET("user/profile")
Observable<ApiResponse<User>> getUserProfile();
}
4.2 在Activity中发起请求并绑定生命周期
public class MainActivity extends AppCompatActivity {
private Disposable disposable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化Retrofit
String baseUrl = BuildConfig.DEBUG ? "https://dev-api.example.com/" : "https://api.example.com/";
Retrofit retrofit = RetrofitClient.getInstance(baseUrl);
UserService userService = retrofit.create(UserService.class);
// 发起请求
disposable = userService.getUserProfile()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<ApiResponse<User>>bindToLifecycle()) // 绑定Activity生命周期
.doOnSubscribe(d -> {
disposable = d; // 保存Disposable以便手动取消
showLoading();
})
.doFinally(this::hideLoading)
.onErrorResumeNext(throwable -> {
// 统一错误处理(见3.4节)
return Observable.just(ApiResponse.error(-1, "获取用户信息失败"));
})
.filter(response -> response.getCode() == 200)
.map(ApiResponse::getData)
.subscribe(
user -> {
// 成功获取用户数据
updateUI(user);
},
error -> {
// 这里只会收到onErrorResumeNext返回的error响应
Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show();
}
);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (disposable != null && !disposable.isDisposed()) {
disposable.dispose(); // 主动取消,双重保险
}
}
}
关键实操细节:
-
bindToLifecycle()来自rxlifecycle-android库,它监听Activity的onDestroy()事件自动调用dispose(); -
doOnSubscribe()中保存disposable,是为了在onDestroy()中手动取消——某些场景(如横竖屏切换)下bindToLifecycle()可能失效; -
filter()放在map()之前,确保只有code=200的响应才进入数据提取,避免getData()返回null导致NPE。
4.3 处理列表数据与分页加载
列表页常需分页加载,Retrofit支持
@Query
动态传参:
public interface ArticleService {
@GET("articles")
Observable<ApiResponse<List<Article>>> getArticles(
@Query("page") int page,
@Query("size") int size
);
}
在Fragment中实现上拉加载:
public class ArticleListFragment extends Fragment {
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private int currentPage = 1;
private boolean isLoading = false;
private void loadMoreArticles() {
if (isLoading) return;
isLoading = true;
Disposable disposable = articleService.getArticles(currentPage, 20)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<ApiResponse<List<Article>>>bindToLifecycle())
.doFinally(() -> isLoading = false)
.onErrorResumeNext(throwable -> {
// 错误处理同上
return Observable.just(ApiResponse.error(-1, "加载失败"));
})
.filter(response -> response.getCode() == 200)
.map(ApiResponse::getData)
.subscribe(
articles -> {
if (articles.isEmpty()) {
showNoMoreData();
} else {
articleAdapter.addData(articles);
currentPage++;
}
},
error -> showError(error.getMessage())
);
compositeDisposable.add(disposable);
}
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear(); // 批量取消所有请求
}
}
这里用
CompositeDisposable
管理多个请求,比单个
Disposable
更高效。
clear()
在Fragment销毁时一次性取消所有请求,避免内存泄漏。
4.4 文件上传与进度监听
Retrofit支持
@Multipart
上传文件,但默认不提供进度回调。需自定义
RequestBody
:
public class ProgressRequestBody extends RequestBody {
private final RequestBody delegate;
private final ProgressListener listener;
public ProgressRequestBody(RequestBody delegate, ProgressListener listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public long contentLength() throws IOException {
return delegate.contentLength();
}
@Override
public MediaType contentType() {
return delegate.contentType();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
CountingSink countingSink = new CountingSink(sink);
BufferedSink bufferedSink = Okio.buffer(countingSink);
delegate.writeTo(bufferedSink);
bufferedSink.flush();
}
private final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
private long contentLength = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
if (contentLength == 0) {
contentLength = contentLength();
}
listener.onProgress(bytesWritten, contentLength);
}
}
}
使用时:
File file = new File("/path/to/image.jpg");
RequestBody requestFile = new ProgressRequestBody(
RequestBody.create(MediaType.parse("image/jpeg"), file),
(bytesWritten, contentLength) -> {
int progress = (int) ((bytesWritten * 100) / contentLength);
updateUploadProgress(progress);
}
);
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
apiService.uploadImage(body)
.subscribe(...);
实测发现:
contentLength()
在
writeTo()
中首次调用会触发计算,因此需在
write()
中延迟获取,否则进度条会从100%开始倒退。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
IllegalArgumentException: Unable to create call adapter
|
Retrofit未添加
RxJava2CallAdapterFactory
,或添加顺序错误
|
检查
addCallAdapterFactory()
是否在
addConverterFactory()
之后;确认依赖版本匹配
|
在
Retrofit.Builder()
中确保
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
在
addConverterFactory()
之后调用
|
NullPointerException
在
getData()
|
后端返回
data
字段为null,Gson未配置
serializeNulls()
|
用Charles抓包确认返回JSON;检查GsonBuilder是否注册了
ApiResponseAdapter
|
使用3.3节的
ApiResponseAdapter
,或在GsonBuilder中添加
.serializeNulls()
|
请求成功但
onNext()
不触发
|
subscribeOn()
未指定IO线程,或
observeOn()
未切回主线程
|
在
subscribe()
前加
doOnSubscribe()
打印日志,确认线程是否正确
|
必须显式调用
subscribeOn(Schedulers.io())
和
observeOn(AndroidSchedulers.mainThread())
|
Activity has leaked window
崩溃
| Activity销毁后,Dialog或Toast仍在显示 |
在
onDestroy()
中调用
dialog.dismiss()
;检查
subscribe()
中是否在主线程更新UI
|
使用
compose(bindToLifecycle())
自动取消请求;所有UI操作前加
isFinishing()
判断
|
OutOfMemoryError
加载大图
| RxJava未启用背压,上游数据流过快 |
监控内存使用;检查是否在
flatMap()
中未限制并发数
|
在
flatMap()
后加
.onBackpressureBuffer(100, BufferOverflowStrategy.DROP_LATEST)
|
5.2 调试网络请求的黄金三步法
第一步:
确认请求是否发出
在
LoggingInterceptor
中打印日志:
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Log.d("Retrofit", "Request: " + request.url() + " " + request.method());
long t1 = System.nanoTime();
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.d("Retrofit", "Response: " + response.code() + " in " + (t2 - t1) / 1e6 + "ms");
return response;
}
如果Logcat中看不到
Request:
日志,说明Retrofit未初始化或接口未调用。
第二步:
确认响应是否解析成功
在
subscribe()
前加
doOnNext()
:
apiService.getData()
.doOnNext(response -> Log.d("Retrofit", "Raw response: " + response.toString()))
.subscribe(...);
如果
response.toString()
显示
code=0
,说明Gson解析失败,需检查模型字段名是否与JSON一致。
第三步:
确认线程是否正确
在
subscribe()
中打印当前线程:
.subscribe(
data -> Log.d("Thread", "onNext thread: " + Thread.currentThread().getName()),
error -> Log.d("Thread", "onError thread: " + Thread.currentThread().getName())
);
如果
onNext
中显示
Thread[main]
,说明
observeOn()
生效;若显示
Thread[RxComputationThreadPool-1]
,则未切回主线程,UI更新会失效。
5.3 生产环境避坑经验
-
禁止在
Application.onCreate()中初始化Retrofit :某些厂商ROM会在此时杀死进程,导致RetrofitClient.getInstance()返回null。应在首次网络请求时懒加载。 -
Token过期自动刷新
:不要在
TokenInterceptor中直接调用刷新接口(会阻塞主线程),应改用retryWhen():apiService.getData() .retryWhen(errors -> errors.flatMap(error -> { if (error instanceof HttpException && ((HttpException) error).code() == 401) { return refreshToken(); // 返回Observable<AuthToken> } return Observable.error(error); })) -
混淆配置
:ProGuard需保留Retrofit相关类:
-keep class retrofit2.** { *; } -keep class okhttp3.** { *; } -keep class com.google.gson.** { *; } -keep class io.reactivex.** { *; }
我在某新闻App上线前夜遇到一个诡异问题:Debug包正常,Release包所有网络请求返回空数据。排查三天才发现是ProGuard把
ApiResponse的code、msg字段混淆了,Gson无法映射。从此所有网络模型类都加@Keep注解,并在proguard-rules.pro中显式保留。
6. 后续演进:从RxJava到Kotlin协程的平滑过渡
虽然本文聚焦RxJava,但必须承认:Kotlin协程已成为Android新项目的首选。好消息是,Retrofit 2.6+原生支持协程,迁移成本极低。现有RxJava代码可逐步替换:
// RxJava写法
apiService.getData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ data -> updateUI(data) }, { error -> showError(error) })
// 协程写法(只需改调用方式)
lifecycleScope.launch {
try {
val data = apiService.getData().await() // suspend函数
updateUI(data)
} catch (e: Exception) {
showError(e.message ?: "未知错误")
}
}
关键迁移步骤:
-
将Retrofit升级到2.9.0+,添加
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'; -
将API接口的返回类型从
Observable<ApiResponse<T>>改为suspend fun getData(): ApiResponse<T>; -
在Activity/Fragment中用
lifecycleScope启动协程,自动绑定生命周期。
这样做的好处是: 无需重写网络层,只需修改调用点 。我们团队用两周时间完成了30+个网络请求的迁移,崩溃率下降22%,代码行数减少35%。RxJava不会一夜消失,但它正安静地退场——就像当年我们淘汰AsyncTask一样自然。
最后分享一个小技巧:如果你的项目还在用Java,但想体验协程,可以用
RxJava2Interop
库桥接:
// Java中调用协程函数(需Kotlin模块)
Single.fromCallable(() -> {
// 在IO线程执行
return suspendFunction(); // Kotlin suspend函数
}).subscribeOn(Schedulers.io()).subscribe(...);
这为团队技术栈升级提供了缓冲期。技术选型没有银弹,只有在正确的时间做正确的事。

3万+

被折叠的 条评论
为什么被折叠?



