前面我们说到,目前网络框架选择基本都为retrofit,目前算是最好用的android网络框架之一了。
今天我们来封装一下retorit,让他更加好用。
以下代码使用的lifecycleScope,均可使用viewModelScope。本文发布时均在activity中进行代码测试,正常开发过程中应使用viewmodel
先看效果
//最简单的get请求
repo {
api { "https://www.baidu.com/" }
}.request<StringBaseResponse>(lifecycleScope) { result ->
//以下方法不使用可忽略
result.onSuccess {
//do something
}
result.onFailure {
//do something
}
}
//带参数的请求
repo {
api { "https://www.baidu.com/" }
params { "a" to "b" }
params { "c" to "d" }
requestMode { RequestMode.GET }
}.request<StringBaseResponse>(lifecycleScope) { result ->
//以下方法不使用可忽略
result.onSuccess {
//do something
}
result.onCancel {
//do something
}
result.onCompletion {
//do something
}
result.onFailure {
//do something
}
}
//并发请求,一个失败全失败
val repo1 = repo {
api { "https://www.baidu.com/" }
params { "a" to "b" }
params { "c" to "d" }
requestMode { RequestMode.GET }
}
val repo2 = repo {
api { "https://www.github.com/" }
params { "a" to "b" }
params { "c" to "d" }
requestMode { RequestMode.GET }
}
lifecycleScope.request<StringBaseResponse, StringBaseResponse>(repo1, repo2) { result ->
//以下方法不使用可忽略
result.onSuccess {
val r1 = it.first
val r2 = it.second
//do something
}
result.onCancel {
//do something
}
result.onCompletion {
//do something
}
result.onFailure {
//do something
}
}
下面开始封装思路,retrofit创建就不多说了,直接看代码
object RetrofitClient { //这里我们填写自己需要的主域名,当然后期可更换,因此按规则填一个就行,否则创建retrofit会报错。 private const val BASE_URL = "http://www.baidu.com"
private fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .build() } fun provideRetrofit(): Retrofit { return Retrofit.Builder() .client(provideOkHttpClient()) .baseUrl(BASE_URL)
//这里我们不使用gsonconvertadapter,我们先都取String,后续我们再说为什么
.addConverterFactory(StringConverterFactory())
.build()
}
}
class StringConverterFactory : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *> {
return Converter<ResponseBody, String> { value -> value.string() }
}
}
request属性类封装
//这里的属性可能不全,需要的自行添加
class BaseRequest {
var api: String? = nullvar requestMode: Int = RequestMode.GET var file: File? = null private var params = mutableMapOf<String, Any>() var contentType: MediaType? = null fun api(init: () -> String) { api = init() } fun requestMode(init: () -> Int) { requestMode = init() } fun file(init: () -> File) { file = init() } fun params(init: () -> Pair<String, Any>) { val p = init() params[p.first] = p.second } fun contentType(init: () -> MediaType?) { contentType = init() } override fun toString(): String { return "api:$api \n params :$params" } fun reflectParameters(): MutableMap<String, Any> { return params }
}
repo请求类封装,这里的思路是将请求包裹成一个类,然后利用请求的各种参数进包裹后,使用固定的apiService进行请求。
class Repo {
var req: BaseRequest = object : BaseRequest() {}private fun injectApiService(): Api { return RetrofitClient.provideRetrofit().create(Api::class.java) } suspend fun execute() = withContext(Dispatchers.IO) { val request = req val params = request.reflectParameters() val apiService = injectApiService() val api = request.api ?: throw RuntimeException("repo没有传入地址") when (request.requestMode) { RequestMode.MULTIPART -> { val requestBody: RequestBody = request.file?.asRequestBody(request.contentType) ?: throw RuntimeException("execute MULTIPART 时,file 不能为空") apiService.uploadFile( api, MultipartBody.Part.createFormData( "uploadFile", request.file?.name, requestBody ), ) } RequestMode.POST -> { apiService.post( api, buildBody(params) ) } RequestMode.GET -> { apiService.get( api, buildBody(params) ) } RequestMode.PUT -> { apiService.put( api, buildBody(params) ) } RequestMode.DELETE -> { apiService.delete( api, buildBody(params) ) } RequestMode.DELETE_BODY -> { apiService.deleteBody( api, buildBody(params) ) } else -> { throw UnsupportedOperationException("不支持的requestMode") } } } private fun buildBody(body: MutableMap<String, Any>): MutableMap<String, Any> { return body }
}
上述常规封装完成,下面开始优化,我们使用kotlin的扩展方法让repo创建支持dsl,并且能够简化调用。
首先来看repo创建
inline fun repo(init: BaseRequest.() -> Unit): Repo {
val repo = Repo()
val req = BaseRequest()
req.init()
repo.req = req
return repo
}
//通过扩展方法可以在任意类中创建repo,并且让repo初始化baseRequest属性类时支持dsl
接下来,将retrofit请求结果转换为flow,便于操作
//这里我们不处理异常,异常正常抛出即可,接下来通过flow的操作符进行异常处理
inline fun <reified T : BaseResponse> Repo.toFlow() = flow {
val jsonResult = execute()
//接收String类型返回值,特殊处理
if (T::class.java == StringBaseResponse::class.java) {
emit(StringBaseResponse(jsonResult) as T)
} else {
val rsp: T = jsonResult.toObject<T>()
?: throw JsonSyntaxException("请求网络执行结果转化为object失败")
emit(rsp)
}
}.flowOn(Dispatchers.IO)
拿到flow后,我们通过flow操作符进行操作,并处理异常。
inline fun <reified T : Any> Flow<T>.completedIn(
scope: CoroutineScope,
crossinline callback: (r: Result<T>) -> Unit
) {
this@completedIn.catch {
val pair = it.toPair()
if (pair.first == NetworkExceptionConstantCode.CANCEL) {
callback(Result.Cancel())
} else {
callback(Result.Failure(pair))
}
}.onEach {
callback(Result.Success(it))
}.catch {
val pair = it.toPair()
if (pair.first == NetworkExceptionConstantCode.CANCEL) {
callback(Result.Cancel())
} else {
callback(Result.Failure(pair))
}
}.onCompletion {
callback(Result.Completion())
}.launchIn(scope).start()
}
将上述两个方法通过一个request方法关联,便可达到文章开头的使用效果
inline fun <reified T : BaseResponse> CoroutineScope.request(
repo: Repo, crossinline callback: (r: Result<T>) -> Unit
) {
repo.toFlow<T>().completedIn(this, callback)
}
如此,我们便完成了retrofit的基础封装,本封装较为基础,如有高级操作请自行添加。
完整代码:项目链接