前面我们说到,目前网络框架选择基本都为retrofit,目前算是最好用的android网络框架之一了。
今天我们来封装一下retorit,让他更加好用。
以下代码使用的lifecycleScope,均可使用viewModelScope。本文发布时均在activity中进行代码测试,正常开发过程中应使用viewmodel
先看效果
代码语言:text复制 //最简单的get请求
repo {
api { "https://www.baidu.com/" }
}.request<StringBaseResponse>(lifecycleScope) { result ->
//以下方法不使用可忽略
result.onSuccess {
//do something
}
result.onFailure {
//do something
}
}
代码语言:text复制//带参数的请求
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
}
}
代码语言:text复制//并发请求,一个失败全失败
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创建就不多说了,直接看代码
代码语言:text复制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属性类封装
代码语言:text复制//这里的属性可能不全,需要的自行添加
class BaseRequest {
var api: String? = null
var 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进行请求。
代码语言:text复制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创建
代码语言:text复制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,便于操作
代码语言:text复制//这里我们不处理异常,异常正常抛出即可,接下来通过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操作符进行操作,并处理异常。
代码语言:text复制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方法关联,便可达到文章开头的使用效果
代码语言:text复制inline fun <reified T : BaseResponse> CoroutineScope.request(
repo: Repo, crossinline callback: (r: Result<T>) -> Unit
) {
repo.toFlow<T>().completedIn(this, callback)
}
如此,我们便完成了retrofit的基础封装,本封装较为基础,如有高级操作请自行添加。
完整代码:项目链接