封装点
- 网络Log设置(OKHTTP拦截器)
- url统一追加参数(OKHTTP拦截器)
- header统一追加参数(OKHTTP拦截器)
- body统一追加参数(OKHTTP拦截器)
- 错误处理,统一错误处理(flag/error)
- 返回值统一剥离外层包装
- 缓存设置
- 超时错误重连机制
- 不同的BaseUrl使用不同的Retrofit实例,同一个BaseUrl下使用同一个Retrofit实例
- 所有Retrofit实例公用一个OkhttpClient实例
引入gradle
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.7.2'
header、日志、url
client.addInterceptor(AppendHeaderParamInterceptor())
client.addInterceptor(AppendUrlParamInterceptor())
if (BuildConfig.DEBUG) {
val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
client.addInterceptor(logger)
}
class AppendUrlParamInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.url.newBuilder()
val newUrl = builder.addQueryParameter("deviceId", "123")
.addQueryParameter("token", "456")
.build()
val newRequest = request.newBuilder()
.url(newUrl)
.build()
return chain.proceed(newRequest)
}
}
class AppendHeaderParamInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.headers.newBuilder()
val newHeader = builder.add("deviceId", "123")
.add("token", "456")
.build()
val newRequest = request.newBuilder()
.headers(newHeader)
.build()
return chain.proceed(newRequest)
}
}
缓存的统一设置
@Headers("Cache-Control: public, max-age=" + 24*60*60)
@GET("api/random/data/{type}/{size}")
suspend fun getRandomUsers(@Path("type") type: String?, @Path("size") size: String?): ApiResponse<User>
class OnlineInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val onlineCacheTime = 0
return response.newBuilder()
.header("Cache-Control", "public, max-age=$onlineCacheTime")
.removeHeader("Pragma")
.build()
}
}
class OfflineInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (!NetworkUtils.isConnected()) {
val newRequest = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
val maxTime = 60 * 60 * 24
val response = chain.proceed(newRequest)
return response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=$maxTime")
.build()
}
return chain.proceed(request)
}
}
val cacheFile = File(PathUtils.getCachePathExternalFirst(), "cache_retrofit")
val cache = Cache(cacheFile, 1024 * 1024 * 10)
client.cache(cache)
.addInterceptor(OfflineInterceptor())
.addNetworkInterceptor(OnlineInterceptor())
返回结果的统一处理
- 异常的统一处理
- 返回结果去除外层的包装,只保留客户端关心的数据
data class ApiResponse<T>(
val code: String,
val msg: String,
val data: T?
) : Serializable {
fun isOK(): Boolean {
return "000000" == code
}
}
object ApiResult {
inline fun <T> create(request: () -> ApiResponse<T>): T? {
return try {
val response = request.invoke()
response.data
} catch (e: Exception) {
if (e is ConnectException) {
ToastUtils.showShort("连接服务器失败")
} else if (e is UnknownHostException) {
ToastUtils.showShort("未知服务器异常")
} else if (e is InterruptedIOException) {
ToastUtils.showShort("连接服务器超时")
} else if (e is JSONException || e is JsonParseException || e is ParseException) {
ToastUtils.showShort("数据解析出错")
} else {
ToastUtils.showShort("未知的网络错误")
}
Log.e("ApiResult", "create: " + e.message)
null
}
}
}
package com.zhangyu.myjetpack.model
import com.zhangyu.myjetpack.api.UserService
import com.zhangyu.myjetpack.bean.User
import com.zhangyu.myjetpack.utils.ApiResult
import com.zhangyu.myjetpack.utils.RetrofitUtil
interface UserRepository {
suspend fun getRandomUsers(): List<User>
}
private class UserRepositoryImpl(private val service: UserService) : UserRepository {
override suspend fun getRandomUsers(): List<User> {
return ApiResult.create {
service.getRandomUsers("福利", "10")
}
}
}
object UserRepositoryProvider {
fun getInstance(): UserRepository {
return UserRepositoryImpl(RetrofitUtil.provide(UserService::class.java))
}
}
完整的代码
package com.zhangyu.myjetpack.utils
import android.util.ArrayMap
import android.util.Log
import com.blankj.utilcode.util.NetworkUtils
import com.blankj.utilcode.util.PathUtils
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.JsonParseException
import com.zhangyu.myjetpack.BuildConfig
import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONException
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.io.InterruptedIOException
import java.net.UnknownHostException
import java.text.ParseException
object RetrofitUtil {
private const val BASE_URL = "https://gank.io/"
private const val maxCacheSize = 1024 * 1024 * 10L
private var okHttpClient: OkHttpClient
init {
val client = OkHttpClient.Builder()
client.addInterceptor(AppendHeaderParamInterceptor())
client.addInterceptor(AppendUrlParamInterceptor())
val cacheFile = File(PathUtils.getCachePathExternalFirst(), "cache_retrofit")
val cache = Cache(cacheFile, maxCacheSize)
client.cache(cache)
.addInterceptor(OfflineInterceptor())
.addNetworkInterceptor(OnlineInterceptor())
if (BuildConfig.DEBUG) {
val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
client.addInterceptor(logger)
}
okHttpClient = client.build()
}
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private val mApis = ArrayMap<String, Any>()
@Suppress("UNCHECKED_CAST")
fun <T> provide(apiInterfaceClass: Class<T>): T {
val api = mApis[apiInterfaceClass.name] as T ?: getRetrofit().create(apiInterfaceClass)
mApis[apiInterfaceClass.name] = api
return api
}
}
class OnlineInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val onlineCacheTime = 0
return response.newBuilder()
.header("Cache-Control", "public, max-age=$onlineCacheTime")
.removeHeader("Pragma")
.build()
}
}
class OfflineInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (!NetworkUtils.isConnected()) {
val newRequest = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
val maxTime = 60 * 60 * 24
val response = chain.proceed(newRequest)
return response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=$maxTime")
.build()
}
return chain.proceed(request)
}
}
class AppendUrlParamInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.url.newBuilder()
val newUrl = builder.addQueryParameter("deviceId", "123")
.addQueryParameter("token", "456")
.build()
val newRequest = request.newBuilder()
.url(newUrl)
.build()
return chain.proceed(newRequest)
}
}
class AppendHeaderParamInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.headers.newBuilder()
val newHeader = builder.add("deviceId", "123")
.add("token", "456")
.build()
val newRequest = request.newBuilder()
.headers(newHeader)
.build()
return chain.proceed(newRequest)
}
}
data class ApiResponse<T>(
val error: Boolean,
val results: List<T>
)
object ApiResult {
inline fun <T> create(request: () -> ApiResponse<T>): List<T> {
return try {
val response = request.invoke()
response.results
} catch (e: Exception) {
if (e is ConnectException) {
ToastUtils.showShort("连接服务器失败")
} else if (e is UnknownHostException) {
ToastUtils.showShort("未知服务器异常")
} else if (e is InterruptedIOException) {
ToastUtils.showShort("连接服务器超时")
} else if (e is JSONException || e is JsonParseException || e is ParseException) {
ToastUtils.showShort("数据解析出错")
} else {
ToastUtils.showShort("未知的网络错误")
}
Log.e("ApiResult", "create: " + e.message)
emptyList()
}
}
}
okhttp.OkHttpClient: --> GET https://gank.io/api/random/data/%E7%A6%8F%E5%88%A9/10?deviceId=123&token=456
okhttp.OkHttpClient: deviceId: 123
okhttp.OkHttpClient: token: 456
okhttp.OkHttpClient: --> END GET
okhttp.OkHttpClient: <-- 200 OK https://gank.io/api/random/data/%E7%A6%8F%E5%88%A9/10?deviceId=123&token=456 (450ms)
okhttp.OkHttpClient: Server: nginx
okhttp.OkHttpClient: Date: Sat, 23 Jan 2021 09:31:13 GMT
okhttp.OkHttpClient: Content-Type: application/json
okhttp.OkHttpClient: Content-Length: 2777
okhttp.OkHttpClient: Connection: keep-alive
okhttp.OkHttpClient: Expires: Sat, 23 Jan 2021 21:31:13 GMT
okhttp.OkHttpClient: Cache-Control: max-age=43200
okhttp.OkHttpClient: X-Cache: MISS
okhttp.OkHttpClient: Cache-Control: no-cache