基于Kotlin和携程的一种Retrofit封装

Free network image

一、封装步骤

直接上代码:

首先引入相关依赖:

api 'com.squareup.retrofit2:retrofit:2.9.0'  
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'  
implementation("com.squareup.okhttp3:okhttp:4.10.0")  
api 'com.github.JessYanCoding:RetrofitUrlManager:v1.4.0'  
api 'com.google.code.gson:gson:2.10.1'
  1. 新建一个单例的RetrofitClient简单封装
package com.kim.jetpackmvi.network  
  
import android.util.Log.INFO  
import com.kim.jetpackmvi.network.logging.Level  
import com.kim.jetpackmvi.network.logging.LoggingInterceptor  
import me.jessyan.retrofiturlmanager.RetrofitUrlManager  
import okhttp3.ConnectionPool  
import okhttp3.OkHttpClient  
import org.greenrobot.eventbus.android.BuildConfig  
import retrofit2.Retrofit  
import retrofit2.converter.gson.GsonConverterFactory  
import java.util.concurrent.TimeUnit  
  
  
/**  
 * @ClassName: RetrofitClient  
 * @Description: Retrofit Kotlin 封装  
 * @Author: kim  
 * @Date: 2/18/23 5:53 PM  
 */class RetrofitClient  {  
    companion object {  
        val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {  
            RetrofitClient()  
        }  
    }  
  
    private lateinit var retrofit: Retrofit  
    private lateinit var okHttpClient: OkHttpClient  
  
    private constructor() {  
        //禁用缓存  
//        if (httpCacheDirectory == null) {  
//            httpCacheDirectory = File(mContext.getCacheDir(), "goldze_cache")  
//        }  
//        try {  
//            if (cache == null) {  
//                cache = Cache(httpCacheDirectory, CACHE_TIMEOUT)  
//            }  
//        } catch (e: Exception) {  
//            KLog.e("Could not create http cache", e)  
//        }  
        val sslParams: HttpsUtils.SSLParams = HttpsUtils.getSslSocketFactory()  
        okHttpClient = RetrofitUrlManager.getInstance().with(OkHttpClient.Builder())  
            //.cookieJar(CookieJarImpl(PersistentCookieStore(mContext))) //                .cache(cache)  
            //.addInterceptor(BaseInterceptor(headers))            //.addInterceptor(CacheInterceptor(mContext))            .sslSocketFactory(sslParams.sSLSocketFactory!!, sslParams.trustManager!!)  
            .addInterceptor(  
                LoggingInterceptor.Builder() //构建者模式  
                    .loggable(true/*BuildConfig.DEBUG*/) //是否开启日志打印  
                    .setLevel(Level.BASIC) //打印的等级  
                    .log(INFO) // 打印类型  
                    .request("Request") // request的Tag  
                    .response("Response") // Response的Tag  
                    //.addHeader("log-header", "I am the log request header.") // 添加打印头, 注意 key 和 value 都不能是中文                    .build()  
            )  
            .connectTimeout(10, TimeUnit.SECONDS)  
            .writeTimeout(10, TimeUnit.SECONDS)  
            .readTimeout(10, TimeUnit.SECONDS)  
            .connectionPool(  
                ConnectionPool(  
                    8,  
                    15,  
                    TimeUnit.SECONDS  
                )  
            ) // 这里你可以根据自己的机型设置同时连接的个数和时间,我这里8个,和每个保持时间为10s  
            .build()  
//        retrofit = Retrofit.Builder()  
//            .client(okHttpClient)  
//            .addConverterFactory(GsonConverterFactory.create())  
//            //.addCallAdapterFactory(RxJava2CallAdapterFactory.create())  
//            .baseUrl(url)  
//            .build()  
    }  
  
    /**  
     * create you ApiService     * Create an implementation of the API endpoints defined by the `service` interface.     */    
     fun <T> create(url: String, service: Class<T>?): T {  
        if (service == null) {  
            throw RuntimeException("Api service is null!")  
        }  
        retrofit = Retrofit.Builder()  
            .client(okHttpClient)  
            .addConverterFactory(GsonConverterFactory.create())  
            //.addCallAdapterFactory(RxJava2CallAdapterFactory.create())  
            .baseUrl(url)  
            .build()  
        return retrofit.create(service)  
    }  
}

在以上代码中,结合了日志拦截器等自定义的拦截器(会单独写文章列出,与本文无关),可以使用create创建Retrofit对象。

  1. 建立一个文件,名为ApiCall,有如下内容:
package com.kim.jetpackmvi.network  
  
import android.util.Log  
import com.google.gson.JsonParseException  
import kotlinx.coroutines.CoroutineScope  
import kotlinx.coroutines.Dispatchers  
import kotlinx.coroutines.withContext  
import org.apache.http.conn.ConnectTimeoutException  
import org.json.JSONException  
import retrofit2.HttpException  
import java.io.IOException  
import java.net.SocketTimeoutException  
import java.net.UnknownHostException  
  
/**  
 * @ClassName: ApiCall  
 * @Description: java类作用描述  
 * @Author: kim  
 * @Date: 2/19/23 12:38 AM  
 */suspend inline fun <T> apiCall(crossinline call: suspend CoroutineScope.() -> T, crossinline onSuccess: ((T) -> Unit), crossinline onError: ((Exception) -> Unit)) {  
    withContext(Dispatchers.IO) {  
        var res: T? = null  
        try {  
            res = call()  
            onSuccess(res)  
        } catch (e: Throwable) {  
            Log.e("ApiCaller", "request error", e)  
            // 请求出错
            onError(ApiException.build(e))  
        }
    }  
}  
  
// 网络、数据解析错误处理  
class ApiException(val code: Int, override val message: String?, override val cause: Throwable? = null)  
    : RuntimeException(message, cause) {  
    companion object {  
        // 网络状态码  
        const val CODE_NET_ERROR = 4000  
        const val CODE_TIMEOUT = 4080  
        const val CODE_JSON_PARSE_ERROR = 4010  
        const val CODE_SERVER_ERROR = 5000  
        // 业务状态码  
        const val CODE_AUTH_INVALID = 401  
  
        fun build(e: Throwable): ApiException {  
            return if (e is HttpException) {  
                ApiException(CODE_NET_ERROR, "网络异常(${e.code()},${e.message()})")  
            } else if (e is UnknownHostException) {  
                ApiException(CODE_NET_ERROR, "网络连接失败,请检查后再试")  
            } else if (e is ConnectTimeoutException || e is SocketTimeoutException) {  
                ApiException(CODE_TIMEOUT, "请求超时,请稍后再试")  
            } else if (e is IOException) {  
                ApiException(CODE_NET_ERROR, "网络异常(${e.message})")  
            } else if (e is JsonParseException || e is JSONException) {  
                // Json解析失败  
                ApiException(CODE_JSON_PARSE_ERROR, "数据解析错误,请稍后再试")  
            } else {  
                ApiException(CODE_SERVER_ERROR, "系统错误(${e.message})")  
            }  
        }  
    }  
}
  1. 建立一个Service
/**  
 * @ClassName: TestApiService  
 * @Description: java类作用描述  
 * @Author: kim  
 * @Date: 2/18/23 5:50 PM  
 */interface TestApiService {  
    /**  
     * 测试post接口,发给他啥它返回啥     */    
    @Headers(DOMAIN_NAME+ DOMAIN_NAME_postTest)  
    @POST("post")  
    // 注意这里的suspend
    suspend fun postTest(@Body json: String): ResponseBody  
}  
  
lateinit var apiService: TestApiService
  1. 调用
//使用retrofit url manager初始化
private fun initRetrofit() {  
    apiService = RetrofitClient.instance.create(  
        testUrl,  
        TestApiService::class.java  
    )  
    //RetrofitClient.getInstance().apiService = apiService;  
    RetrofitUrlManager.getInstance().putDomain(DOMAIN_NAME_postTest, testUrl)  
    RetrofitUrlManager.getInstance().setGlobalDomain(testUrl) 
}
//在view model中调用协程
viewModelScope.launch {  
    delay(2000)  
    apiCall({  
        apiService.postTest("{\"aaa\":123}")  
    }, {  
        Logger.i(it.string())  
    }, {  
        Logger.i(it.message + "")  
    })  
}

结束。

二、简单解释

Retrofit已经支持Kotlin的协程了,在Api Service中加入suspend关键字即可。

使用ApiCall函数传入三个参数,要运行的协程、成功回调、返回回调。 直接在withContext(Dispatchers.IO)中使用即可。

参考链接

Retrofit+协程,网络请求封装实战 – 知乎 (zhihu.com) 优雅的封装网络请求,协程 + Retrofit – 掘金 (juejin.cn)

参考了1的状态回调,参考了2的协程封装