《Kotlin系列》之协程搭配Retrofit+OkHttp3网络请求封装(kotlin+retrofit+okhttp3)

上一篇:《Kotlin系列》之MVVM架构封装

前言

上一篇关于MVVM 架构的基类封装,这篇会在MVVM 的基础上示范使用 kotlin+retrofit+okhttp 封装的网络框架,里面会涉及到协程的使用,
协程异常处理包装。

导入相关包

kotlin 相关配置见上一篇,这里主要是Retrofit 和okHttp3 的包

    api 'com.squareup.okhttp3:logging-interceptor:3.6.0'
    api 'com.squareup.retrofit2:adapter-rxjava2:2.6.0'
    api 'com.squareup.retrofit2:adapter-rxjava:2.6.0'
    api 'com.squareup.okhttp3:okhttp:3.12.0'
    api 'com.squareup.retrofit2:retrofit:2.6.0'
    api 'com.squareup.retrofit2:converter-gson:2.6.0'
    api 'com.squareup.retrofit2:adapter-rxjava2:2.6.0'

封装

先看看图中基本的结构
在这里插入图片描述

  • RetrofitClient: client 管理类
    这里是整个业务封装的核心,其中包括https 证书校验、baseUrl 切换
class RetrofitClient {

    /**
     * retrofit 初始化build
     */
    private fun RetrofitClient() {}


   //做成单例
    companion object {
        private var retrofitClient: RetrofitClient? = null
        private const val DEFAULT_TIME_OUT = 15
        private val sRetrofitManager: MutableMap<Int, Retrofit> = HashMap()
        fun getInstance(): RetrofitClient {
            if (retrofitClient == null) {
                synchronized(RetrofitClient::class.java) {
                    retrofitClient = RetrofitClient()
                    return retrofitClient as RetrofitClient
                }
            }
            return retrofitClient as RetrofitClient
        }
    }


    /**
     * 创建连接客户端
     */
    private fun createOkHttpClient(): OkHttpClient {

        //设置请求头拦截器
        //设置日志拦截器
        val httpLoggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT)
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY


        //根据需求添加不同的拦截器

        return OkHttpClient.Builder()
            .connectTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
            .writeTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
            .readTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
            .connectionPool(ConnectionPool(8, 10, TimeUnit.SECONDS)) //添加这两行代码
            .sslSocketFactory(TrustAllCerts.createSSLSocketFactory()!!, TrustAllCerts())
            .hostnameVerifier(TrustAllCerts.TrustAllHostnameVerifier())
            .addInterceptor(httpLoggingInterceptor)
            .build()
    }


    /**
     * 根据host 类型判断是否需要重新创建Client,因为一个app 有不同的BaseUrl,切换BaseUrl 就需要重新创建Client
     * 所以,就根据类型来从map中取出对应的client
     */
    fun <T> getDefault(interfaceServer: Class<T>?, hostType: Int): T {
        val retrofitManager = sRetrofitManager[hostType]
        return if (retrofitManager == null) {
            create(interfaceServer, hostType)
        } else retrofitManager.create(interfaceServer!!)
    }


    /**
     *
     */
    private fun <T> create(interfaceServer: Class<T>?, hostType: Int): T {
        val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(BaseUrlConstants.getHost(hostType))
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(createOkHttpClient())
            .build()
        sRetrofitManager[hostType] = retrofit
        if (interfaceServer == null) {
            throw RuntimeException("The Api InterfaceServer is null!")
        }
        return retrofit.create(interfaceServer)
    }

}

很多时候,我们app 的请求的域名不可能只有一个,所以,在不同情况下需要去切换baseUrl,那么在Retrofit 中,一个Url 只能对应一个Retrofit Client ,所以我们就可以用map 将retrofit的实例对象通过key -value的形式存储起来,而key就是用的BaseUrl,
当我们在传入不同BaseUrl时,会从内存中取出是否有对应创建过的Client,如没有,则创建并保存。从而达到了实时切换BaseUrl的目的。

    * 根据host 类型判断是否需要重新创建Client,因为一个app 有不同的BaseUrl,切换BaseUrl 就需要重新创建Client
     * 所以,就根据类型来从map中取出对应的client
     */
    fun <T> getDefault(interfaceServer: Class<T>?, hostType: Int): T {
        val retrofitManager = sRetrofitManager[hostType]
        return if (retrofitManager == null) {
            create(interfaceServer, hostType)
        } else retrofitManager.create(interfaceServer!!)
    }

  • ApiAddress: 地址管理类
class ApiAddress {

    companion object{
        /**
         * 登录
         */
        const val LOGIN = "api/login"

    }
}
  • ApiException: 异常封包处理类


class ApiException : Throwable {

    //这些字段根据后台定义去修改
    private var code = 0
    private var displayMessage: String? = null


    constructor(code: Int, displayMessage: String?) {
        this.code = code
        this.displayMessage = displayMessage
    }

    constructor(code: Int, message: String?, displayMessage: String?) : super(message) {
        this.code = code
        this.displayMessage = displayMessage
    }

    fun getCode(): Int {
        return code
    }

    fun setCode(code: Int) {
        this.code = code
    }

    fun getDisplayMessage(): String? {
        return displayMessage
    }

    fun setDisplayMessage(displayMessage: String?) {
        this.displayMessage = displayMessage
    }

    fun getUMessage(): String? {
        return displayMessage
    }
}
  • ApiService: Retrofit 接口类

interface ApiService {


    /**
     * get 
     */
    @GET(ApiAddress.LOGIN)
    suspend fun login(
        @Query("account") account: String,
        @Query("password") password: String
    ): BaseResponse<Any>


    /**
     * post body
     */
    @POST(ApiAddress.LOGIN)
    suspend fun loginBody(@Body requestBody: RequestBody): BaseResponse<Any>
}


  • BaseResponse: 数据封装基类

基类字段都是根据后台定义来更改

class BaseResponse<T>:Serializable {

    private var message: String? = null
    private var code: Int? = null
    private var data: T? = null
    private var result = false


    fun isResult(): Boolean {
        return result
    }

    fun setResult(result: Boolean) {
        this.result = result
    }


    fun getMessage(): String? {
        return message
    }

    fun setMessage(message: String?) {
        this.message = message
    }

    fun getData(): T? {
        return data
    }

    fun setData(data: T) {
        this.data = data
    }

    fun getErrCode(): Int? {
        return code
    }

    fun setErroCode(erroCode: Int?) {
        this.code = erroCode
    }

    override fun toString(): String {
        return "BaseResponse{" +
                ", message='" + message + '\'' +
                ", code=" + code +
                ", data=" + data +
                ", result=" + result +
                '}'
    }
}

  • BaseUrlConstants: BaseUrl 管理类
    companion object {
        private const val baseUrl1: String = "http://test1/"
        private const val baseUrl2: String = "http://test2/"

        fun getHost(host: Int): String {
            when (host) {
                1 -> return baseUrl1
                2 -> return baseUrl2
            }
            return baseUrl1;
        }
    }
  • ExceptionUtil: 异常处理工具类

object ExceptionUtil {
    /**
     * 未知错误
     */
    const val UNKNOWN = 1000

    /**
     * 解析错误
     */
    const val PARSE_ERROR = 1001

    /**
     * 网络错误
     */
    const val NETWORK_ERROR = 1002

    /**
     * 协议错误
     */
    const val HTTP_ERROR = 1003

    /**
     * 处理异常,toast提示错误信息
     */
    fun catchException(e: Throwable) {
        e.printStackTrace()



        when (e) {
            is HttpException -> {
                catchHttpException(e.code())
            }
            is SocketTimeoutException -> {
//                showToast(R.string.common_error_net_time_out)
            }
            is UnknownHostException, is NetworkErrorException -> {
//                showToast(R.string.common_error_net)
            }
            is MalformedJsonException, is JsonSyntaxException -> {
//                showToast(R.string.common_error_server_json)
            }
            is InterruptedIOException -> {
                showToast("服务器连接失败,请稍后重试")
            }
            // 自定义接口异常
            is ApiException -> {
                showToast(e.message?:"", e.getCode())
            }
            is ConnectException -> {
                showToast( "连接服务器失败" )
            }
            else -> {
//                showToast("${MyApplication.instance.getString(
//                    R.string.common_error_do_something_fail
//                )}:${e::class.java.name}")
            }
        }
    }


    /**
     * 服务器异常 或 网络通道异常
     *
     * @param e
     * @return
     */
    private fun handleException(e: Throwable): ApiException {
        val ex: ApiException
        return if (e is JsonParseException
            || e is JSONException
            || e is ParseException
        ) {
            //解析错误
            ex = ApiException(PARSE_ERROR, e.message)
            ex
        } else if (e is ConnectException) {
            //网络错误
            ex = ApiException(
                NETWORK_ERROR,
                e.message
            )
            ex
        } else if (e is UnknownHostException || e is SocketTimeoutException) {
            //连接错误
            ex = ApiException(
                NETWORK_ERROR,
                e.message
            )
            ex
        } else {
            //未知错误
            ex = ApiException(
                UNKNOWN,
                e.message
            )
            ex
        }
    }

    /**
     * 处理网络异常
     */
    private fun catchHttpException(errorCode: Int) {
        if (errorCode in 200 until 300) return// 成功code则不处理
//        showToast(
//            catchHttpExceptionCode(
//                errorCode
//            ), errorCode
//        )
    }

    /**
     * toast提示
     */
    private fun showToast(@StringRes errorMsg: Int, errorCode: Int = -1) {
//        showToast(MyApplication.instance.getString(
//                errorMsg
//            ), errorCode
//        )
    }

    /**
     * toast提示
     */
    private fun showToast(errorMsg: String, errorCode: Int = -1) {
//        if (errorCode == -1) {
//            ToastUtils.showShort(errorMsg)
//        } else {
//            ToastUtils.showShort("$errorCode:$errorMsg")
//        }
    }

    /**
     * 处理网络异常
     */
//    private fun catchHttpExceptionCode(errorCode: Int): Int = when (errorCode) {
//        in 500..600 -> R.string.common_error_server
//        in 400 until 500 -> R.string.common_error_request
//        else -> R.string.common_error_request
//    }
}
  • TrustAllCerts: 签名证书校验,这里默认都通过
class TrustAllCerts : X509TrustManager {
    override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {

    }

    override fun checkServerTrusted(chain: Array<out X509Certificate>?, p1: String?) {
        requireNotNull(chain) { "  Check Server x509Certificates is null" }
    }

    override fun getAcceptedIssuers(): Array<X509Certificate?> {
        return arrayOfNulls(0)
    }


    companion object {
        fun createSSLSocketFactory(): SSLSocketFactory? {
            var ssfFactory: SSLSocketFactory? = null
            try {
                val sc = SSLContext.getInstance("TLS")
                sc.init(
                    null, arrayOf(TrustAllCerts()), SecureRandom()
                )
                ssfFactory = sc.socketFactory
            } catch (e: Exception) {
            }
            return ssfFactory
        }
    }


    class TrustAllHostnameVerifier : HostnameVerifier {
        override fun verify(hostname: String, session: SSLSession): Boolean {
            return true
        }
    }
}

以上就是整个结构的封装代码,很多场景可以根据业务情况去调整。下面看看在ViewModel中如何使用

通过协程请求实例

两种情况:1.在viewmodel 中,2.其他地方

一.在viewmodel 中

  • 1.首先在BaseViewModel 中使用viewModelScope 封装好请求体。

    fun ViewModel.launch(
        block: suspend CoroutineScope.() -> Unit,
        onError: (e: Throwable) -> Unit = { _: Throwable -> },
        onComplete: () -> Unit = {}
    ) {
        viewModelScope.launch(
            CoroutineExceptionHandler { _, throwable ->
                run {
                    // 这里统一处理错误
                    ExceptionUtil.catchException(throwable)
                    onError(throwable)
                }
            }
        ) {
            try {
                block.invoke(this)
            } finally {
                onComplete()
            }
        }
    }


  • 2.在子viewmodel 也就是业务层中调用如下:

    /**
     * 登录测试
     */
    open fun login() {


        launch({
            val login = DataService.login(1, "admin", "admin")

            if (login.getErrCode() == 200) {
                var data = login.getData()
            } else {
                ApiException(-1, "返回结果出错")
            }
        }, onError = {

            Log.d(TAG, "the error is" + it.message)
        })

    }

二、在任意地方调用

  • 1 在一个类中封装好请求体

    companion object {

      


        fun launch(
            block: suspend CoroutineScope.() -> Unit,
            onError: (e: Throwable) -> Unit = { _: Throwable -> },
            onComplete: () -> Unit = {}
        ) {
            GlobalScope.launch(
                CoroutineExceptionHandler { _, throwable ->
                    run {
                        // 这里统一处理错误
                        ExceptionUtil.catchException(throwable)
                        onError(throwable)
                    }
                }
            ) {
                try {
                    block.invoke(this)
                } finally {
                    onComplete()
                }
            }
        }
    }
  • 2 调用示例
    /**
     * 任意地方调用
     */
    open fun notViewModelLogin() {
        DataService.launch({
            val login = DataService.login(1, "admin", "admin")

            if (login.getErrCode() == 200) {
                var data = login.getData()
            } else {
                ApiException(-1, "返回结果出错")
            }
        }, onError = {

            Log.d(TAG, "the error is" + it.message)
        })
    }

以上就是全部的封装过程以及调用示例,Retrofit 的接口通过suspend 后,在协程调用就无需手动切换线程,所以会比Rx订阅方式方便很多。配合整个mvvm 架构去做的话,是非常简洁和方便维护的。代码以及上传到github
https://github.com/ljlstudio/KtMvvm/tree/master/ktmvvm/src/main/java/com/kt/ktmvvm/net

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值