最近在新开的项目中,全面使用了Android的全新姿势——Kotlin、Jetpack等等,之后也对协程进行了一段时间的学习,体验了协程带来的编程快乐,我也忍不住对RxJava下了狠手。新项目中,我完全移除了RxJava,彻底的转入了协程时代。上周也是对原本的网络层进行了一次全新的封装,在这里对此进行一次总结。
和Google官宣的一样,Kotlin作为官方语言将会在新特性上得到优先支持,Retrofit2.6.0发布更新后,内置了对Kotlin Coroutines的支持,极大的简化了使用Retrofit和协程进行网络请求的过程。在这里我就不过多的去介绍协程的概念了,我们试着去使用Retrofit+协程来完成一次网络请求封装吧。
本篇基于Retrofit2.6.0进行,与之前版本的请求方式略有差别,请注意。
一、创建Retrofit
第一步,我们当然是要创建Retrofit,这里先封装一个BaseRetrofitClient
.
abstract class BaseRetrofitClient {
companion object {
private const val TIME_OUT = 6
}
private val client: OkHttpClient
get() {
val builder = OkHttpClient.Builder()
val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
logging.level = HttpLoggingInterceptor.Level.BODY
} else {
logging.level = HttpLoggingInterceptor.Level.BASIC
}
builder.addInterceptor(logging)
.connectTimeout(TIME_OUT.toLong(), TimeUnit.SECONDS)
handleBuilder(builder)
return builder.build()
}
protected abstract fun handleBuilder(builder: OkHttpClient.Builder)
fun getService(clazz: Class, baseUrl: String): S {
return Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl)
.build().create(clazz)
}
}
之后我们可以针对自己的项目需要去重写handleBuilder方法,来创建我们需要的retrofit.
object MyRetrofitClient : BaseRetrofitClient() {
val service by lazy {
getService(MyApiService::class.java, MyApiService.BASE_URL)
}
override fun handleBuilder(builder: OkHttpClient.Builder){
...
}
}
二、创建Service接口
interface MyApiService {
@Post("/users")
suspend fun getUsers(): MyResponse
}
三、返回数据类封装
假定现在服务器返回接口数据的格式为:
{
success: true
returnCode: 10000,
message: "请求成功",
returnTime: 15000901291001,
data: {
...
}
}
其中data
是我们想要获取的实体类数据,这时我们需要对外层的json进行一次封装,并根据returnCode处理不同的数据请求情况。
data class MyResponse (
val success: Boolean,
val returnCode: Int,
val message: String,
val returnTime: Long,
val data: T
)
四、统一处理请求
依照我们公司的内部约定,returnCode为10000时是正常的成功请求,如果请求是其它值则分别有对应的处理,例如:80000代表token失效。那么我们就想到,要对不同的情况进行统一处理,以此来避免在每次网络请求的时候对returnCode进行重复判断。
4.1 BaseRepository
open class BaseRepository {
suspend fun apiCall(call: suspend() -> MyResponse) : MyRespnse {
return withContext(IO) { call.invoke() }.apply {
// 特殊处理
when (returnCode) {
99999 -> throw HttpException()
80000 -> throw TokenInvalidException()
}
}
}
class TokenInvalidException(msg : String = "token invalid"): Exception(msg)
}
4.2 BaseViewModel
open class BaseViewModel : ViewModel() {
val error by lazy { MutableLiveData() }
// UI
fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.lauch {
try {
block()
} catch (e: Exception){
error.value = e
}
}
}
五、完成一次请求
做完以上的几件事之后,我们可以开始做一次简单的网络请求啦。
class UserRepository : BaseRepository() {
suspend fun getUser(): MyResponse {
return apiCall(MyRetrofitClient.service.getUser())
}
}
直接在UserViewModel中进行。
class UserViewModel : BaseViewModel() {
val user by lazy { MutableLiveData() }
val repository = UserRepository()
fun getUser(){
launchUI{
val result = repository.getUser()
user.value = result
}
}
}