手把手教你搭建android模块化项目框架(四)network网络库封装

前面我们说到,目前网络框架选择基本都为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&lt;String, Any&gt;()

var contentType: MediaType? = null

fun api(init: () -&gt; String) {
    api = init()
}

fun requestMode(init: () -&gt; Int) {
    requestMode = init()
}

fun file(init: () -&gt; File) {
    file = init()
}

fun params(init: () -&gt; Pair&lt;String, Any&gt;) {
    val p = init()
    params[p.first] = p.second
}

fun contentType(init: () -&gt; MediaType?) {
    contentType = init()
}

override fun toString(): String {
    return &#34;api:$api \n params :$params&#34;
}

fun reflectParameters(): MutableMap&lt;String, Any&gt; {
    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(&#34;repo没有传入地址&#34;)
    when (request.requestMode) {
        RequestMode.MULTIPART -&gt; {
            val requestBody: RequestBody =
                request.file?.asRequestBody(request.contentType)
                    ?: throw RuntimeException(&#34;execute MULTIPART 时,file 不能为空&#34;)
            apiService.uploadFile(
                api,
                MultipartBody.Part.createFormData(
                    &#34;uploadFile&#34;,
                    request.file?.name,
                    requestBody
                ),
            )
        }

        RequestMode.POST -&gt; {
            apiService.post(
                api,
                buildBody(params)
            )
        }

        RequestMode.GET -&gt; {
            apiService.get(
                api,
                buildBody(params)
            )
        }

        RequestMode.PUT -&gt; {
            apiService.put(
                api,
                buildBody(params)
            )
        }

        RequestMode.DELETE -&gt; {
            apiService.delete(
                api,
                buildBody(params)
            )
        }

        RequestMode.DELETE_BODY -&gt; {
            apiService.deleteBody(
                api,
                buildBody(params)
            )
        }

        else -&gt; {
            throw UnsupportedOperationException(&#34;不支持的requestMode&#34;)
        }
    }
}

private fun buildBody(body: MutableMap&lt;String, Any&gt;): MutableMap&lt;String, Any&gt; {
    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的基础封装,本封装较为基础,如有高级操作请自行添加。

完整代码:项目链接