网络请求巧用

用到的库

  • Retrofit
  • RxJava2
1
2
3
4
5
6
7
8
9
10
11
def rxandroidVersion = '2.0.1'
implementation "io.reactivex.rxjava2:rxandroid:$rxandroidVersion"
def retrofitVersion = '2.8.0'
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation 'com.google.code.gson:gson:2.8.5'

implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

服务器统一返回格式

error_code 不为 0 时,就不用解析 data(其值为 null),根据相应的错误码提示给用户;

1
2
3
4
{
"err_code": 1101,
"err_msg": "参数name不能为空"
}

error_code0 时,就可以解析 data,这个返回值通过实体类或列表来映射得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"err_code": 0,
"err_msg": "ok",
"data": {
"id": 1,
"name": "lyloou",
"email": "lyloou@qq.com",
"personal_signature": "多么美好的太阳",
"gmt_create": "2020-01-16T09:38:18.000+0000",
"gmt_modified": "2020-03-14T11:27:58.000+0000",
"is_disabled": false
}
}

定义返回类

通过 CResult 来接收返回的数据,data 的通过泛型来映射成对应实体类(用泛型的好处,是不用为每一个返回都建立一个类);

1
2
3
// https://medium.com/@stustirling/responses-errors-with-retrofit-2-rxjava2-6d55eafecf5a
data class CResult<T>(var err_code: Int, var err_msg: String, var data: T?)

Retrofit 简单封装

具体用法参考: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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Network.kt
val gson: Gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.setLenient()
.create()
fun Any.toJsonString(): String {
return gson.toJson(this)
}

object Network {
private var headerPairs: (() -> List<Pair<String, String>>)? = null
private val builder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());

fun withHeader(pairList: (() -> List<Pair<String, String>>)): Network {
this.headerPairs = pairList
return this
}

fun <T> get(baseUrl: String, clazz: Class<T>): T {
val okHttpBuilder = OkHttpClient.Builder()
headerPairs?.invoke()?.let {
okHttpBuilder.addInterceptor(interceptor(it))
}
return builder.baseUrl(baseUrl)
.client(okHttpBuilder.build())
.build().create(clazz)
}

// 如果是需要授权才能获取信息,统一用这个。
// 把用户信息和授权信息通过 header 的方式发到服务器
// [Retrofit — Add Custom Request Header](https://futurestud.io/tutorials/retrofit-add-custom-request-header)
fun auth(userPassword: UserPassword?): List<Pair<String, String>> {
userPassword?.let {
return listOf(
"Content-Type" to "application/json",
"Authorization" to it.password,
"UserId" to it.userId.toString()
)
}
return emptyList()
}
}

fun interceptor(headers: List<Pair<String, String>>): (Interceptor.Chain) -> Response {
return {
val newBuilder = it.request().newBuilder()
headers.forEach { header ->
newBuilder.addHeader(header.first, header.second)
}
it.proceed(newBuilder.build())
}
}

fun <T> Observable<T>.defaultScheduler(): Observable<T> {
return this.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}

准备用户的类和 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
27
28
29
30
31
32
33
// user.kt
data class User(
val id: Long,
val name: String,
val email: String,
@SerializedName("personal_signature")
val personalSignature: String,
@SerializedName("gmt_create")
val gmtCreate: Date
)

data class UserPassword(
val userId: Long,
val name: String,
val password: String
)

enum class Url(val url: String) : Str {
UserApi("http://127.0.0.1:8888/api/v1/user/"),
;
}


fun Network.userApi(): UserApi {
return get(Url.UserApi.url, UserApi::class.java)
}

// 创建带授权的 userApi
fun Network.userWithAuthApi(userPassword: UserPassword? = UserPasswordHelper.getUserPassword()): UserApi {
return withHeader { auth(userPassword) }
.get(Url.UserApi.url, UserApi::class.java);
}

使用方法 1 ,基于 Observable

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

interface UserApi {
@POST("login")
fun login(@Query("name") name: String, @Query("password") password: String): Observable<CResult<User?>>

@POST("update")
fun update(@Body user: User): Observable<CResult<String?>>
}

// 用户登录
Network.userApi()
.login(name, encodedPassword)
.defaultScheduler()
.subscribe({
if (it.err_code == 0) {
// save user info to localStorage
} else {
toast("错误代码:${it.err_code},错误信息:${it.err_msg}")
}
}, {
toast("网络异常:${it.message}")
})

// 更新用户
Network.userApi()
.update(user)
.defaultScheduler()
.subscribe({
if (it.err_code == 0) {
// doSuccess
} else {
// do error
}
}, {
toast("网络异常:${it.message}")
})

使用方法 2,基于 kotlin 的 coroutine

首先给 ViewModel 添加一个 apiForCResult 扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun <T> ViewModel.apiForCResult(
block: suspend CoroutineScope.() -> CResult<T?>,
okFun: (T?) -> Unit = {},
failFun: (String) -> Unit = {}
) {
viewModelScope.launch {
try {
val result = withContext(Dispatchers.IO, block)
if (result.err_code == 0) {
okFun(result.data)
} else {
failFun(result.err_msg)
}
} catch (e: Exception) {
failFun("${e.message}")
}
}
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
interface UserApi {
@POST("login")
suspend fun login(@Query("name") name: String, @Query("password") password: String): CResult<User?>

@POST("update")
suspend fun update(@Body user: User): CResult<String?>
}

// 在 viewModel 中
class LoginViewModel : ViewModel() {
fun login(
name: String,
password: String,
okFun: (User?) -> Unit,
failFun: (String) -> Unit
) {
apiForCResult(
{ Network.userApi().login(name, password) },
okFun,
failFun
)
}

fun update(
user: User,
okFun: (String?) -> Unit,
failFun: (String) -> Unit
) {
apiForCResult(
{ Network.userWithAuthApi().update(user) },
okFun,
failFun
)
}
}

class UserActivity{
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.login -> {
viewModel.login(name, password, ::doSuccess, ::toast)
}
R.id.updaete -> {
viewModel.update(
user,
{toast("更新成功")},
{toast(it)}
)
}
}
return super.onOptionsItemSelected(item)
}
// 在 activity 中
}

总结

结合使用 kotlin 的扩展功能,可以简化许多冗余的代码。