传统的网络请求写法
val builder = OkHttpClient.Builder()
builder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.cookieJar(...)
.addInterceptor(...)
val retrofit = retrofit.Builder()
.baseUrl(baseUrl)
.client(builder.build())
.addCallAdapterFactory(callAdapterFactory)
.addConverterFactory(converterFactory)
.build()
...
...
复制代码
dialog.show()
HttpClientManager.getInstance()
.getHttpClient()
.createRetrofitApi(
URL.ROOT_URL,
HttpApi::class.java).login("phone","phoneCode").enqueue(MyCallback() {
override fun success()
override fun onfailed()
})
复制代码
优雅实现
// 包含dialog,请求失败统一处理,一次发送单个、多个请求,看起来是不是很舒服!
http<LoginBean> {
request { model.login("phone","phoneCode").waitT() }
success { codeSuccess.postValue(this) }
}
复制代码
让我们开始吧 ↓↓↓
0. 构建HttpClient
class HttpClient() {
private object Client{
val builder = HttpClient()
}
companion object {
fun getInstance() = Client.builder
}
/**
* 缓存retrofit针对同一个域名下相同的ApiService不会重复创建retrofit对象
*/
private val apiMap by lazy {
ArrayMap<String, Any>()
}
private val API_KEY = "apiKey"
private var interceptors = arrayListOf<Interceptor>()
private var converterFactorys = arrayListOf<Converter.Factory>()
/**
* 拦截器
*/
fun setInterceptors(list: MutableList<Interceptor>?) : HttpClient {
interceptors.clear()
if (!list.isNullOrEmpty()) {
interceptors.addAll(list)
}
return this
}
/**
* 解析器
*/
fun setConverterFactorys(list: MutableList<Converter.Factory>?) : HttpClient {
converterFactorys.clear()
// 保证有一个默认的解析器
converterFactorys.add(GsonConverterFactory.create())
if (!list.isNullOrEmpty()) {
converterFactorys.addAll(list)
}
return this
}
/**
* 根据 apiClass 与 baseUrl 创建 不同的Api
* @param baseUrl String 根目录
* @param clazz Class<T> 具体的api
* @param needAddHeader Boolean 是否需要添加公共的头
* @param showLog Boolean 是否需要显示log
*/
fun <T> createRetrofitApi(
baseUrl: String = URL.ROOT_URL,
clazz: Class<T> = HttpApi::class.java,
needAddHeader: Boolean = true,
showLog: Boolean = true
): T {
val key = getApiKey(baseUrl, clazz)
val api = apiMap[key] as T
if (api == null) {
L.e(API_KEY, "RetrofitApi --->>> \"$key\"不存在,需要创建新的")
val builder = OkHttpClient.Builder()
builder
.connectTimeout(Content.HTTP_TIME, TimeUnit.SECONDS)
.readTimeout(Content.HTTP_TIME, TimeUnit.SECONDS)
if (needAddHeader) {
builder.addInterceptor(MyHttpInterceptor()) // 头部拦截器
}
if (interceptors.isNotEmpty()) {
interceptors.forEach {
builder.addInterceptor(it)
}
}
if (showLog) {
builder.addInterceptor(LogInterceptor {
L.i(Content.HTTP_TAG, it)
}.apply {
level = LogInterceptor.Level.BODY
})
}
val rBuilder = Retrofit.Builder()
.baseUrl(baseUrl)
.client(builder.build())
if (converterFactorys.isEmpty()) { // 保证有一个默认的解析器
converterFactorys.add(GsonConverterFactory.create())
}
converterFactorys.forEach {
rBuilder.addConverterFactory(it)
}
val newAapi = rBuilder.build().create(clazz)
apiMap[key] = newAapi
return newAapi
}
return api
}
override fun <K> getApiKey(baseUrl: String, apiClass: Class<K>) =
"apiKey_${baseUrl}_${apiClass.name}"
/**
* 清空所有拦截器
*/
fun clearInterceptor() : HttpClient {
interceptors.clear()
return this
}
/**
* 清空所有解析器
*/
fun clearConverterFactory() : HttpClient {
converterFactorys.clear()
return this
}
/**
* 清空所有api缓存
*/
fun clearAllApi() : HttpClient {
L.e(Content.HTTP_TAG, "清空所有api缓存")
apiMap.clear()
return this
}
}
/**
* HttpApi
*/
interface HttpApi {
// 不建议这样操作,因为每个项目的请求业务逻辑不完全一样,自己定制高度可控(具体往下看)
suspend fun sth(): CommonBean<String>
// 这种方式即使不用协程也可以使用,推荐此方式
fun login(@Body body: RequestBody): Call<CommonBean<LoginBean>>
}
// 在 HttpApi中 编写 默认的获取 HttpApi的方式
val defaultApi: HttpApi
get() {
return HttpClient.getInstance().createRetrofitApi()
}
复制代码
1. waitT 扩展函数
// 异常格式化
class ApiException : Exception {
var msg: String? = "网络不给力啊,再试一次吧!"
var errorCode = Content.REQUEST_SUCCESS_CODE
private constructor(code: Int, throwable: Throwable) : super(throwable) {
msg = throwable.message
errorCode = code
}
constructor(message: String?, code: Int = _500_SERVICE) : super(message) {
msg = message
errorCode = code
}
companion object {
const val _500_SERVICE = 500
const val UNKNOW_ERROR = -1
fun formatException(e: Throwable): ApiException {
val apiException: ApiException
when (e) {
is HttpException -> {
apiException = ApiException(e.code(), e)
apiException.msg = "网络不给力啊,再试一次吧!"
}
is SocketTimeoutException -> {
apiException = ApiException(_500_SERVICE, "网络不给力啊,再试一次吧!")
}
... 其他异常处理
is ApiException -> {
apiException = e
}
else -> {
apiException = ApiException(UNKNOW_ERROR, e)
apiException.msg = "未知异常,请联系管理员"
}
}
return apiException
}
}
/**
* 请求基类
*/
@Parcelize
class CommonBean<T>(
var success: Boolean = false,
var result: @RawValue T? = null,
var error: ErrorBean? = null,
var message: String? = "",
) : Parcelable, Serializable
/**
* 错误 返回实体类
* @property errorCode Int
* @property errorKey String?
* @property errorMessage String
* @constructor
*/
@Parcelize
data class ErrorBean(
var errorCode: Int = -1,
var errorKey: String? = "",
var errorMessage: String = ""
) : Parcelable, Serializable
// 这种方式 根据自己项目的需求定制,100% 自主可控
suspend fun <T> Call<CommonBean<T>>.waitT(): CommonBean<T> {
return suspendCoroutine {
enqueue(object : Callback<CommonBean<T>> {
override fun onResponse(
call: Call<CommonBean<T>>,
response: Response<CommonBean<T>>
) {
val body = response.body()
if (body is ResponseBody) { // ResponseBody情况
if (response.isSuccessful) {
it.resume(body)
} else {
it.resumeWithException(ApiException("网络不给力啊,再试一次吧"))
}
} else { // 其他实体类的情况
if (response.isSuccessful) { // 请求成功
val isSuccess = body?.success ?: false // 业务逻辑OK
if (body != null && isSuccess) { // 业务逻辑OK
it.resume(body)
} else { // 请求成功 但是业务逻辑是不对的
val errorBean = body?.error
it.resumeWithException(ApiException(errorBean?.errorMessage))
}
} else { // 服务器抛出异常的情况,具体根据业务逻辑进行判定,如我这里必须需要单独处理401的异常
if (response.code() == 401) {
// 服务器直接抛出 401异常,手动处理
it.resumeWithException(ApiException("当前账号在其他设备登录", 401))
} else {
it.resumeWithException(ApiException("网络不给力啊,再试一次吧"))
}
}
}
}
override fun onFailure(call: Call<T_CommonBean<T>>, t: Throwable) {
t.printStackTrace()
L.e(Content.HTTP_TAG, "onFailure 接口异常 ---> ${call.request().url()}")
it.resumeWithException(ApiException.formatException(t))
}
})
}
}
复制代码
2. BaseViewModel
// 在 BaseActivity 或 BaseFragment中 register之后 统一处理即可
class BaseViewModel : ViewModel() {
/**
* Dialog 网络请求
*/
data class DialogRespBean(
var title: String? = "加载中",
var isCancelable: Boolean = true
)
/**
* 网络请求失败,响应到UI上的 Bean
* @property state Int 区分刷新(加载)或加载更多
* @property message String 错误描述
* @constructor
*/
data class NetErrorRespBean(
var state: Int = Content.REFRESH,
var message: String? = "网络不给力啊,再试一次吧"
)
/**
* 显示dialog
*/
val showDialog by lazy {
MutableLiveData<DialogRespBean>()
}
/**
* 销毁dialog
*/
val dismissDialog by lazy {
MutableLiveData<Void>()
}
/**
* 网络请求错误
*/
val networkError by lazy {
MutableLiveData<NetErrorRespBean>()
}
/**
* 停止所有操作
*/
val stopAll by lazy {
MutableLiveData<Void>()
}
/**
* 当前账号在其他设备登录
*/
val loginOut by lazy {
MutableLiveData<String?>()
}
/**
* Token 失效 或 登录时间已过期
*/
val tokenError by lazy {
MutableLiveData<String?>()
}
// IO 往下看
open fun lunchByIO(
context: CoroutineContext = IO,
block: suspend CoroutineScope.() -> Unit
) = viewModelScope.launch(context) { block() }
}
复制代码
3. HttpExtend 与 DSL
准备工作
val IO: CoroutineContext
get() {
return Dispatchers.IO
}
val Main: CoroutineContext
get() {
return Dispatchers.Main
}
/**
* 切换到 IO 线程
*/
suspend fun IO(block: suspend CoroutineScope.() -> Unit) {
withContext(IO) {
block()
}
}
/**
* 切换到 UI 线程
*/
suspend fun UI(block: suspend CoroutineScope.() -> Unit) {
withContext(Main) {
block()
}
}
@Parcelize
data class HttpLoader(
var state: Int = Content.REFRESH, // 用来区分是刷新还是加载更多
var showDialog: Boolean = true, // 请求是否显示 dialog
var autoDismissDialog: Boolean = true, // 请求成功后是否自动销毁dialog
var dialogTitle: String = "加载中", // dialog title
var dialogCancel: Boolean = true // dialog 是否可以取消
) : Parcelable
// 这里 是 单事件 MutableLiveData 的处理,这不是本文的重点,所以简单略过,可以直接 postValue
fun <T> MutableLiveData<T>.clear() {
value = null
}
复制代码
HttpExtend
internal fun <T> BaseViewModel.http(block: HttpExtend<T>.() -> Unit) {
val httpExtend = HttpExtend<T>(this)
block.invoke(httpExtend)
}
internal fun BaseViewModel.http2(block: HttpExtend<Nothing>.() -> Unit) {
http(block)
}
/** 一次发送一个 */
internal fun <T> HttpExtend<T>.request(startBlock: suspend CoroutineScope.() -> CommonBean<T>?) {
start(startBlock)
}
/** 一次发送多个 */
/** 不能直接更新UI,先切换到UI线程再操作UI --->>> UI { } */
internal fun HttpExtend<Nothing>.request2(startBlock: suspend CoroutineScope.() -> Unit) {
start2(startBlock)
}
internal fun <T> HttpExtend<T>.loading(loaderBlock: () -> HttpLoader) {
dialog(loaderBlock)
}
internal fun <T> HttpExtend<T>.success(resultBlock: T?.() -> Unit) {
callback(resultBlock)
}
internal fun <T> HttpExtend<T>.failed(errorBlock: Exception?.() -> Unit) {
error(errorBlock)
}
internal fun <T> HttpExtend<T>.finally(finaly: () -> Unit) {
end(finaly)
}
class HttpExtend<T>(var viewModel: BaseViewModel) {
private var httpLoader = HttpLoader()
// 请求成功回调
private var httpCallBack: (T?.() -> Unit)? = null
private var httpError: (Exception?.() -> Unit)? = null
private var httpFinally: (() -> Unit)? = null
infix fun dialog(httpLoader: () -> HttpLoader) {
this.httpLoader = httpLoader()
}
private fun showDialog() {
if (httpLoader.showDialog) viewModel.showDialog.postValue(
DialogRespBean(
httpLoader.dialogTitle,
httpLoader.dialogCancel
)
)
}
// 一次请求一个
infix fun start(startBlock: suspend CoroutineScope.() -> T_CommonBean<T>?) {
showDialog()
viewModel.lunchByIO {
try {
val request = startBlock()
UI {
httpCallBack?.invoke(request?.result)
}
} catch (e: Exception) {
callError(e)
} finally {
callFinally()
}
}
}
// 一次请求多个
infix fun start2(startBlock: suspend CoroutineScope.() -> Unit) {
showDialog()
viewModel.lunchByIO {
try {
startBlock()
} catch (e: Exception) {
callError(e)
} finally {
callFinally()
}
}
}
// 请求 回调
infix fun callback(resultBlock: T?.() -> Unit) {
httpCallBack = resultBlock
}
// 请求 失败 处理
infix fun error(errorBlock: Exception?.() -> Unit) {
httpError = errorBlock
}
// 不管请求是否成功或失败,都会调用
infix fun end(end: () -> Unit) {
httpFinally = end
}
// 处理异常 当然你也可以使用密封类
private suspend fun callError(e: Exception) {
e.printStackTrace()
UI {
// waitT 扩展函数抛出的异常关联
val apiException = ApiException.formatException(e)
// 具体根据业务逻辑而定
when (apiException.errorCode) {
401 -> {
L.e(Content.HTTP_TAG, "callError ---> Token失效 或 登录时间已过期")
viewModel.tokenError.postValue(apiException.msg)
}
888 -> {
// 当前账号在其他设备登录
L.e(Content.HTTP_TAG, "callError ---> 当前账号在其他设备登录")
viewModel.loginOut.postValue(apiException.msg)
}
else -> { // 一般的服务器请求失败处理
L.e(Content.HTTP_TAG, "callError ---> 请求失败")
viewModel.networkError.postValue(
NetErrorRespBean(
httpLoader.state,
apiException.msg
)
)
httpError?.invoke(apiException)
}
}
viewModel.dismissDialog.clear() // 出现崩溃,不管如何都将dialog销毁
}
}
// 最终执行
private suspend fun callFinally() {
UI {
if (httpLoader.autoDismissDialog && httpLoader.showDialog) {
viewModel.dismissDialog.clear()
}
httpFinally?.invoke()
}
}
}
复制代码
4. 最终调用
class LoginModel() {
fun login(phone:String,phoneCode:String) = defaultApi.login(phone,phoneCode)
fun registrId(id:String) = defaultApi.registrId(id)
fun getUserInfo(id:String) = defaultApi.getUserInfo(id)
}
class LoginViewModel : BaseViewModel() {
val loginSuccess by lazy {
MutableLiveData<UserInfoBean>()
}
private val model by lazy {
LoginModel()
}
// 一次发送一个
fun login(phone:String,phoneCode:String) {
http<LoginBean> {
request { model.login("phone","phoneCode").waitT() }
success { loginSuccess.postValue(this) }
}
}
// 一次发送多个
fun login2(phone:String,phoneCode:String) {
http2 {
loading { HttpLoader(showDialog = false, autoDismissDialog = false) }
request2 {
val loginBean = model.login("phone","phoneCode").waitT()
val registrBean = model.registrId(loginBean.id).waitT()
val userBean = model.getUserInfo(registrBean.id).waitT()
// 请求成功
UI {
loginSuccess.postValue(userBean)
dismissDialog.clear()
}
}
failed {
dismissDialog.clear()
}
finally {
// do sth
}
}
}
}
复制代码
思路就是酱紫,最重要的是 waitT 以及 HttpExtend异常处理, 搞定!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END