0%

Android中使用Kotlin协程代替RxJava封装网络请求

现在的Android项目普遍使用Retrofit+RxJava的组合实现网络接口请求与数据的展现。这一功能通过Kotlin语言的协程功能也可以很方便的实现。

相比较而言,RxJava功能过于强大,如果仅用于封装网络请求,有些杀鸡用牛刀的感觉。使用Kotlin的协程实现这个需求代码更精简,逻辑也更清晰一些。

以下是一个完整的例子。使用Retrofit结合Kotlin协程,实现网络请求。
点击Activity中的按钮,请求V2ex网站的openAPI,成功后在界面中显示结果字段。

Activity中的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class MainActivity : AppCompatActivity() {

var loadDataJob: Job? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

test_button.setOnClickListener {
showResult("")
loadDataJob?.cancel() // 取消之前的加载任务
loadDataJob = loadData("Livid") // “http://www.v2ex.com/api/members/show.json?username=Livid”
}
}

override fun onDestroy() {
super.onDestroy()
loadDataJob?.cancel() // 取消加载任务
}

private fun showResult(resStr: String) {
test_result.text = resStr
}

private fun loadData(username: String): Job {
return executeRequest<UserInfo>(
// 请求调用
request = {
userApiManager.getUserInfo(username)
},
// 成功回调
onSuccess = {
showResult(it.bio)
},
// 失败回调
onFail = {
it.printStackTrace()
})
}
}

loadData函数中,通过execeteRequest方法返回Job对象,executeRequest通过lambda参数分别指定了网络请求,成功与失败时的逻辑操作。

UserApiManager类中实现了Retrofit调用的简单封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Nullable
public UserInfo getUserInfo(String name) throws Exception {
// 类初始化时创建Retrofit以及接口api的实例
/*
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5000, TimeUnit.MILLISECONDS)
.readTimeout(5000, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true).build();
retrofitBuilder =
new Retrofit.Builder().baseUrl("http://www.v2ex.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient);

retrofit = retrofitBuilder.build();
mApiService = retrofit.create(UserApiService.class);
*/

Response<UserInfo> result = mApiService.getUserInfo(name).execute(); // 同步请求
if (result.isSuccessful()) {
return result.body();
} else if (result.code() == 404) {
throw new Exception("404 Not Found");
}
return null;
}

由于我们要通过协程实现异步加载,因此在网络请求中,使用了execute同步方法。

在点击按钮时,先取消前一次加载请求,再发起新请求;同时在Activity的onDestroy生命周期中,也进行了取消加载请求的处理。

executeRequest利用Kotlin协程实现了网络请求封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun <T> executeRequest(request: suspend () -> T?, onSuccess: (T) -> Unit = {}, onFail: (Throwable) -> Unit = {}): Job {
val uiScope = CoroutineScope(Dispatchers.Main) // UI主线程的CoroutineScope
return uiScope.launch {
try {
val res: T? = withContext(Dispatchers.IO) { request() } // IO线程中执行网络请求,成功后返回这里继续执行
res?.let {
onSuccess(it)
}
} catch (e: CancellationException) {
Log.e("executeRequest", "job cancelled")
} catch (e: Exception) {
Log.e("executeRequest", "request caused exception")
onFail(e)
}
}
}

首先在UI线程中启动协程,当执行到withContext后,request代码块将切换到调度器分配的IO线程上执行,同时executeRequest函数让出控制权,因此UI线程不会阻塞。当request请求完成后,结果赋值给 res 变量,UI线程再次回到executeRequest中,继续执行后续部分。

多次点击界面按钮,将打印

job cancelled

更改一个错误的url地址,将看到

request caused exception

通过协程,在顺序执行的函数中,实现了回调函数的效果,代码逻辑更清晰了。在调用时相对RxJava也更加简洁。

参考文章
Android Coroutine Recipes