前言
在现代 Android 开发中,响应式编程和网络请求是两个至关重要的技术领域。OkHttp 作为强大的 HTTP 客户端,与 RxJava/RxAndroid 的响应式编程范式相结合,可以构建出高效、可维护且易于测试的网络请求架构。本文将深入探讨如何将这两者完美结合,并提供实用的代码示例和最佳实践。
一、为什么选择 OkHttp + RxJava 组合?
1. 优势对比
特性 | 传统回调方式 | OkHttp + RxJava 组合 |
---|---|---|
代码可读性 | 嵌套回调,难以维护 | 链式调用,逻辑清晰 |
错误处理 | 分散在各回调中 | 集中统一处理 |
线程切换 | 需要手动管理 | 通过操作符自动切换 |
请求组合 | 实现复杂 | 通过操作符轻松组合 |
取消请求 | 需要手动维护 | 通过 Disposable 统一管理 |
2. 适用场景
-
需要处理多个连续网络请求的场景
-
需要将网络请求与其他异步操作组合的场景
-
需要精细控制线程调度的场景
-
需要实现复杂响应式数据流的场景
二、基础集成与配置
1. 添加依赖
// OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
// RxJava
implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
// OkHttp 与 RxJava 适配器
implementation 'com.github.akarnokd:rxjava3-extensions:3.1.1'
2. 创建 RxJava 适配的 OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
三、基本使用模式
1. 将 Call 转换为 Observable
fun request(url: String): Observable<Response> {
val request = Request.Builder()
.url(url)
.build()
return Observable.fromCallable {
okHttpClient.newCall(request).execute()
}
}
2. 更完善的封装(带错误处理)
fun getJson(url: String): Observable<String> {
val request = Request.Builder()
.url(url)
.build()
return Observable.create { emitter ->
val call = okHttpClient.newCall(request)
emitter.setCancellable { call.cancel() }
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (!emitter.isDisposed) {
emitter.onError(e)
}
}
override fun onResponse(call: Call, response: Response) {
try {
if (!emitter.isDisposed) {
val body = response.body?.string()
?: throw IOException("Empty response")
emitter.onNext(body)
emitter.onComplete()
}
} catch (e: Exception) {
if (!emitter.isDisposed) {
emitter.onError(e)
}
}
}
})
}
}
3. 结合 Retrofit 使用(推荐方式)
interface ApiService {
@GET("users/{id}")
fun getUser(@Path("id") userId: String): Single<User>
@GET("posts")
fun getPostsByUser(@Query("userId") userId: String): Observable<List<Post>>
}
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.client(okHttpClient)
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)
四、高级应用场景
1. 多个请求顺序执行
apiService.getUser("123")
.flatMap { user ->
apiService.getPostsByUser(user.id)
.map { posts ->
UserWithPosts(user, posts)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ userWithPosts ->
// 处理结果
}, { error ->
// 处理错误
})
2. 多个请求并行执行
Observable.zip(
apiService.getUser("123").subscribeOn(Schedulers.io()),
apiService.getPostsByUser("123").subscribeOn(Schedulers.io()),
BiFunction { user: User, posts: List<Post> ->
UserWithPosts(user, posts)
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ userWithPosts ->
// 处理结果
}, { error ->
// 处理错误
})
3. 请求重试机制
apiService.getUser("123")
.retryWhen { errors ->
errors.zipWith(Observable.range(1, 3), { error, retryCount ->
if (retryCount < 3 && error is IOException) {
Observable.timer(retryCount.toLong(), TimeUnit.SECONDS)
} else {
Observable.error(error)
}
}).flatMap { it }
}
.subscribe({ user ->
// 处理结果
}, { error ->
// 处理错误
})
4. 超时控制
apiService.getUser("123")
.timeout(5, TimeUnit.SECONDS)
.subscribe({ user ->
// 处理结果
}, { error ->
when (error) {
is TimeoutException -> // 处理超时
else -> // 处理其他错误
}
})
五、线程调度最佳实践
1. 标准线程调度模式
apiService.getUser("123")
.subscribeOn(Schedulers.io()) // 在IO线程执行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 在主线程处理结果
.subscribe({ user ->
// 更新UI
}, { error ->
// 显示错误
})
2. 复杂线程调度
apiService.getUser("123")
.subscribeOn(Schedulers.io())
.map { user ->
// 在IO线程执行耗时转换
transformUser(user)
}
.observeOn(Schedulers.computation())
.filter { user ->
// 在计算线程执行过滤
isValidUser(user)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user ->
// 在主线程更新UI
}, { error ->
// 在主线程显示错误
})
六、错误处理与状态管理
1. 统一错误处理
fun <T> Observable<T>.handleErrors(): Observable<T> {
return this.onErrorResumeNext { error ->
when (error) {
is IOException -> Observable.error(NetworkException(error))
is HttpException -> Observable.error(ApiException(error))
else -> Observable.error(UnexpectedException(error))
}
}
}
apiService.getUser("123")
.handleErrors()
.subscribe({ user ->
// 处理成功
}, { error ->
when (error) {
is NetworkException -> // 处理网络错误
is ApiException -> // 处理API错误
else -> // 处理未知错误
}
})
2. 加载状态管理
sealed class Resource<T> {
data class Loading<T>(val data: T? = null) : Resource<T>()
data class Success<T>(val data: T) : Resource<T>()
data class Error<T>(val error: Throwable, val data: T? = null) : Resource<T>()
}
fun <T> Observable<T>.asResource(): Observable<Resource<T>> {
return this.map { data -> Resource.Success(data) as Resource<T> }
.startWithItem(Resource.Loading())
.onErrorReturn { error -> Resource.Error(error) }
}
apiService.getUser("123")
.asResource()
.subscribe { resource ->
when (resource) {
is Resource.Loading -> showLoading()
is Resource.Success -> showUser(resource.data)
is Resource.Error -> showError(resource.error)
}
}
七、生命周期管理与内存泄漏预防
1. 使用 CompositeDisposable
class UserViewModel : ViewModel() {
private val disposables = CompositeDisposable()
fun loadUser(userId: String) {
apiService.getUser(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user ->
// 更新LiveData或状态
}, { error ->
// 处理错误
})
.addTo(disposables)
}
override fun onCleared() {
super.onCleared()
disposables.clear()
}
}
2. 在 Android 组件中使用
class UserActivity : AppCompatActivity() {
private val disposables = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
loadUser()
}
private fun loadUser() {
apiService.getUser("123")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user ->
// 更新UI
}, { error ->
// 显示错误
})
.addTo(disposables)
}
override fun onDestroy() {
super.onDestroy()
disposables.clear()
}
}
八、性能优化技巧
1. 请求缓存
val okHttpClient = OkHttpClient.Builder()
.cache(Cache(File(context.cacheDir, "http_cache"), 10 * 1024 * 1024L))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
val cacheControl = CacheControl.Builder()
.maxAge(1, TimeUnit.HOURS)
.build()
response.newBuilder()
.header("Cache-Control", cacheControl.toString())
.build()
}
.build()
2. 请求合并
fun getUsersWithPosts(userIds: List<String>): Observable<List<UserWithPosts>> {
return Observable.fromIterable(userIds)
.flatMap { userId ->
Observable.zip(
apiService.getUser(userId).subscribeOn(Schedulers.io()),
apiService.getPostsByUser(userId).subscribeOn(Schedulers.io()),
BiFunction { user: User, posts: List<Post> ->
UserWithPosts(user, posts)
}
)
}
.toList()
.toObservable()
}
3. 背压策略
apiService.getLargeDataStream()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation(), false, 128) // 设置缓冲区大小
.toFlowable(BackpressureStrategy.BUFFER) // 转换为Flowable处理背压
.subscribe({ item ->
// 处理数据项
}, { error ->
// 处理错误
})
九、测试策略
1. 单元测试
@Test
fun testGetUserSuccess() {
// 准备Mock响应
val mockResponse = MockResponse()
.setBody("""{"id":"123","name":"John Doe"}""")
.setResponseCode(200)
mockWebServer.enqueue(mockResponse)
// 执行测试
apiService.getUser("123")
.test()
.assertNoErrors()
.assertValue { user -> user.id == "123" && user.name == "John Doe" }
}
2. 错误测试
@Test
fun testGetUserNetworkError() {
// 模拟网络错误
mockWebServer.shutdown()
apiService.getUser("123")
.test()
.assertError(IOException::class.java)
}
3. 多线程测试
@Test
fun testThreadScheduling() {
val testScheduler = TestScheduler()
val testObserver = apiService.getUser("123")
.subscribeOn(testScheduler)
.observeOn(testScheduler)
.test()
testScheduler.triggerActions()
testObserver.assertNoErrors()
.assertComplete()
}
十、总结与最佳实践
OkHttp 与 RxJava/RxAndroid 的结合为 Android 网络编程提供了强大的解决方案。通过本文的介绍,我们了解到:
-
基本集成:如何将 OkHttp Call 转换为 Observable 以及更完善的封装方式
-
高级应用:包括多个请求的顺序/并行执行、请求重试、超时控制等场景
-
线程调度:合理的线程调度策略对性能至关重要
-
错误处理:统一的错误处理机制可以大大提高代码可维护性
-
生命周期管理:正确管理 Disposable 避免内存泄漏
-
性能优化:通过缓存、请求合并等技术优化网络性能
-
测试策略:确保网络层的可靠性和稳定性
最佳实践建议:
-
优先使用 Retrofit + RxJava 适配器:比手动封装 OkHttp Call 更简洁可靠
-
合理使用操作符:避免过度复杂的 Observable 链
-
统一错误处理:建立应用级的错误处理机制
-
注意线程切换:避免不必要的线程切换开销
-
及时释放资源:在适当的生命周期回调中清理 Disposable
-
编写测试用例:确保网络层的正确性和健壮性
通过合理应用这些技术和最佳实践,您可以构建出高效、可靠且易于维护的 Android 网络请求架构。