借助DSL优雅实现网络请求

传统的网络请求写法

val builder = OkHttpClient.Builder()
        builder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
                .cookieJar(...)
                .addInterceptor(...) 
                .retryOnConnectionFailure(true)
                
val retrofit = etrofit.Builder()
                .baseUrl(baseUrl)
                .client(builder.build())
                .addCallAdapterFactory(callAdapterFactory)
                .addConverterFactory(converterFactory)
                .build()

val api = retrofit.create(clazz)
api.login("phone","phoneCode").enqueue(object: Callback{
	....
})

	 dialog.show()
	 HttpClientManager.getInstance()
            .getHttpClient()
            .createRetrofitApi(
                URL.ROOT_URL,
                HttpApi::class.java).login("phone","phoneCode").enqueue(MyCallback() {
					override fun success()
					override fun onfailed()
				})

优雅实现


http<LoginBean> {
    request { model.login("phone","phoneCode").waitT() }
    success { codeSuccess.postValue(this) }
}

上述操作,包含dialog,请求失败统一处理,一次发送单个、多个请求,是不是很舒服!

让我们开始吧 ^ _ ^

构建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()
	}

waitT 扩展函数

// ApiException 用来格式化异常的,直接抛出RuntimeException 也可以

/**
 * 请求基类
 */
@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

// 这种方式 根据自己项目的需求定制,自主可控
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))
            }
        })
    }
}

构建ViewModel

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?>()
    }

    open fun lunchByIO(
        context: CoroutineContext = IO,
        block: suspend CoroutineScope.() -> Unit
    ) = viewModelScope.launch(context) { block() }
    
}

构建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

// 请求不需要dialog
fun httpLoaderNoDialog() = HttpLoader(showDialog = false)

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()
        }
    }

}

最终调用


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 { codeSuccess.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
			}
		}
	}

}

搞定! —>>> demo

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值