网上基于Kotlin协程+Retrofit封装网络请求框架的文章非常多,站在巨人肩膀上,今天来自己简单的封装一版,便于今后使用复习。
一、依赖相关库文件
/*retrofit*/
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:logging-interceptor:3.14.9"
/*kotlin*/
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.3.72'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30"
二、api接口定义及Retrofit注解
1)定义接口
interface ApiService {
/**
* 登录
*/
@POST("api/appLogin")
suspend fun appLogin(@Body map: HashMap<String, String>): ResultData<LoginData>
}
2)GET和POST请求方法类注解
@GET
GET网络请求方式,通常后面括号 + 接口地址
// 接口:https://www.baidu.com/api/appLogin?account=12345&password=12345"
@GET("api/appLogin")
suspend fun appLogin(@Query("account") account: String, @Query("password") password: String): ResultData<LoginData>
@POST
POST网络请求方式,通常后面括号 + 接口地址
@POST("api/appLogin")
suspend fun appLogin(@Body map: HashMap<String, String>): ResultData<LoginData>
3)参数类注解
@Path
路径参数,用于url中的变量字符替换,也就是替换url中的{}中的部分
@Path主要用于Get请求
// goodsId:123jfidjf34;接口:https://www.baidu.com/api/123jfidjf34/detail"
@GET("api/{goodsId}/detail")
suspend fun appGoodsDetail(@Path("goodsId") goodsId: String): ResultData<GoodsDate>
@Query
查询参数,在url末尾拼接@Query修饰的字符串
@Query主要用于Get请求
单个查询参数
// 接口:https://www.baidu.com/api/appLogin?account=12345"
@GET("api/appLogin")
suspend fun appLogin(@Query("account") account: String): ResultData<LoginData>
多个查询参数
// 接口:https://www.baidu.com/api/appLogin?account=12345&password=12345"
@GET("api/appLogin")
suspend fun appLogin(@Query("account") account: String, @Query("password") password: String): ResultData<LoginData>
@QueryMap
查询参数集合,效果等同于多个@Query,在url末尾拼接@QueryMap修饰的字符串集合
// map:[{"account":"12345"},{"password":"12345"}];接口:https://www.baidu.com/api/appLogin?account=12345&password=12345"
@GET("api/appLogin")
suspend fun appLogin(@QueryMap map: HashMap<String, String>): ResultData<LoginData>
@Field
指定form表单中域中每个控件name以及相应数值
@Field的用法类似于@Query,不同的是@Field主要用于Post请求
@FieldMap
表单域集合
用于Post请求数据,@FieldMap的用法类似于@QueryMap
@Part
Post提交分块请求,适合文件上传
@Multipart
@POST("api/appFileUpload")
suspend fun appFileUpload(@Part("token") token: RequestBody, @Part file: MultipartBody.Part): ResultData<BaseData>
@Body
指定一个对象作为 request body,非表单请求体
@POST("api/appLogin")
suspend fun appLogin(@Body map: HashMap<String, String>): ResultData<LoginData>
三、Retrofit网络请求客户端封装
object RetrofitClient {
private const val BASE_URL = "https://test.baidu.com"
const val IMG_URL = "$BASE_URL/image/"
private val okHttpClient = OkHttpClient.Builder()
.callTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)// 重连
.followRedirects(false)// 重定向
.addInterceptor(RequestInterceptor())
.addInterceptor(ResponseInterceptor())
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
四、API客户端封装
object ApiClient {
@JvmStatic
private var apiService: ApiService? = null
@JvmStatic
val appApi: ApiService = apiService ?: synchronized(this) {
apiService ?: retrofit.create(ApiService::class.java).also { apiService = it }
}
}
五、响应数据模型
class ResultData<T> {
var code: Int = 0
var message: String = ""
var result: T? = null
override fun toString(): String {
return "ResultData{code:$code,message:$message,result:${result.toString()}}"
}
}
六、异步请求协程封装
fun <T> CoroutineScope.loadHttp(
// 默认实现{},即参数可空
start: () -> Unit = {},
// suspend 修饰的函数,返回值ResultData<T>,没有默认实现,即参数不可空
request: suspend CoroutineScope.() -> ResultData<T>,
// 函数的参数为T,没有默认实现,即参数不可空
response: (T) -> Unit,
error: (String, String) -> Unit = { code: String, msg: String -> },
end: () -> Unit = {}
) {
// 在主线程(Dispatchers.Main)执行
launch(Dispatchers.Main) {
try {
// 1.函数开始执行,先调用start()方法,给View做准备工作,如:显示loading
start()
// 2.发起网络请求
val data = request()
if (data.code == 200) {
// 3.请求成功,返回响应
if (data.result == null){
data.result = "" as T
}
data.result?.let { response(it) }
} else {
// 4.请求失败,调用error()
val msg = if (TextUtils.isEmpty(data.message)) "Server connection failed!" else data.message
error(data.code.toString(), msg)
}
} catch (e: Exception) {
// 可根据具体异常显示具体错误提示
when (e) {
is UnknownHostException -> error("400", "Server connection failed!")
else -> {
val msg = if (TextUtils.isEmpty(e.message)) "Server connection failed!" else e.message
error("400", msg!!)
}
}
} finally {
end()
}
}
}
七、API客户端用于java代码调用封装
object ApiJavaClient {
const val TAG = "ApiJavaClient"
@JvmStatic
fun appLogin(account: String, password: String, countryCode: String, callback: ICallback<LoginData>?) {
val map = HashMap<String, String>()
map["password"] = password
map["countryCode"] = countryCode
map["account"] = account
GlobalScope.loadHttp(
request = { ApiClient.appApi.appLogin(map) },
response = {
i(TAG, "appLogin res: $it")
callback?.onSuccess(it)
},
error = { code: String, msg: String -> callback?.onFailed(code, msg) })
}
}
八、Http统一接口回调
interface ICallback<T> {
fun onSuccess(obj: T)
fun onFailed(code: String, msg: String)
}
九、Java代码调用
ApiJavaClient.appLogin(userName, password, countryCode, new ICallback<LoginData>() {
@Override
public void onSuccess(LoginData obj) {
}
@Override
public void onFailed(@NotNull String code, @NotNull String msg) {
}
});
十、请求拦截器封装
class RequestInterceptor : Interceptor {
private val TAG = "RequestInterceptor"
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val requestBuilder = request.newBuilder()
val httpUrl = request.url().newBuilder().build()
// 请求头map
var headerMap = HashMap<String, String>()
when (request.method()) {
"GET" -> {
// 解析url传递参数
val paramKeys = httpUrl.queryParameterNames()
// TreeMap 默认元素迭代升序
val map = TreeMap<String, String>()
for (key: String in paramKeys) {
map[key] = httpUrl.queryParameter(key).toString()
}
// 制作带签名的请求头map
headerMap = RequestHeader.makeRequestHeaderMap(map, httpUrl)
LogUtil.i(TAG, "GET headerMap $headerMap")
}
"POST" -> {
// TreeMap 默认元素迭代升序
val map = TreeMap<String, String>()
// 解析body传递参数
when (val body = request.body()) {
is FormBody -> {
for (i in 0 until body.size()) {
map[body.encodedName(i)] = body.encodedValue(i)
}
}
is MultipartBody -> {
}
else -> {
val buffer = Buffer()
body?.writeTo(buffer)
var charset = Charset.forName("UTF-8")
val contentType = body?.contentType()
if (contentType != null) {
charset = contentType.charset(charset)
}
if (StringUtil.isPlaintext(buffer)) {
val content = buffer.readString(charset)
val jsonObject = JSONObject(content)
for (i in jsonObject.keys()) {
map[i] = jsonObject.get(i).toString()
}
}
}
}
// 制作带签名的请求头map
headerMap = RequestHeader.makeRequestHeaderMap(map, httpUrl)
LogUtil.i(TAG, "POST headerMap $headerMap")
}
}
// 添加请求头map 到请求头
for (item in headerMap) {
requestBuilder.addHeader(item.key, item.value)
}
return chain.proceed(requestBuilder.build())
}
}
十一、响应拦截器封装
class ResponseInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
return if (response.body() != null) {
var body = response.body()?.string()
try {
val model = Gson().fromJson(body, ResultData::class.java)
LogUtil.i("ResponseInterceptor", "intercept model: $model")
// token失效,统一处理
} catch (e: Exception) {
LogUtil.i("ResponseInterceptor", "intercept body: $body")
// body数据异常时,重新构建ResultData
val res = ResultData<String>()
res.code = 600
res.message = "服务器异常!"
body = res.toString()
}
val responseBody = ResponseBody.create(null, body)
response.newBuilder().body(responseBody).build()
} else {
response
}
}
}
十二、请求头封装
将接口公共参数,token,sign,添加到请求头。