当前位置: 代码迷 >> 综合 >> Android:玩转Retrofit+OkHttp+Kotlin协程 网络请求架构
  详细解决方案

Android:玩转Retrofit+OkHttp+Kotlin协程 网络请求架构

热度:93   发布时间:2024-01-13 04:40:27.0

文章目录

  • 引言
  • 实战
    • 1、引入开源库
    • 2、简单封装
    • 3、开始使用
      • MVP架构模式
      • MVVM架构模式 看[这里](https://blog.csdn.net/sange77/article/details/103959389)

引言

目前做APP网络API请求Retrofit+OkHttp+Kotlin协程应该是比较流行的,相比之前Retrofit+RxJava 有了太多的优势,Rx可以做的事情,协程一样可以做,而且可以做到更方便,更简洁。还不会用协程的童鞋可以看下这篇[Kotlin:玩转协程],接下来我们进行网络请求框架的实战。

实战

1、引入开源库

在app moudle 下build.gradle引入下列库。

dependencies {//OkHttp3implementation "com.squareup.okhttp3:okhttp:3.12.0"//Retrofit网络请求api "com.squareup.retrofit2:retrofit:2.6.2"api "com.squareup.retrofit2:converter-gson:2.6.2"implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"//Kotlin Coroutines 协程api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
}

最新版本,请查阅官方地址:

  • OkHttp3:https://github.com/square/okhttp
  • Retrofit:https://github.com/square/retrofit
  • Kotlin Coroutines 协程:https://github.com/Kotlin/kotlinx.coroutines

2、简单封装

接口请求工厂类 ApiFactory.kt

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit/*** 接口请求工厂* @author ssq*/
object ApiFactory {
    // 日志拦截器private val mLoggingInterceptor: Interceptor by lazy {
     LoggingInterceptor() }// OkHttpClient客户端private val mClient: OkHttpClient by lazy {
     newClient() }/*** 创建API Service接口实例*/fun <T> create(baseUrl: String, clazz: Class<T>): T = Retrofit.Builder().baseUrl(baseUrl).client(mClient).addConverterFactory(GsonConverterFactory.create(MGson.getInstance())).addCallAdapterFactory(CoroutineCallAdapterFactory()).build().create(clazz)/*** OkHttpClient客户端*/private fun newClient(): OkHttpClient = OkHttpClient.Builder().apply {
    connectTimeout(30, TimeUnit.SECONDS)// 连接时间:30s超时readTimeout(10, TimeUnit.SECONDS)// 读取时间:10s超时writeTimeout(10, TimeUnit.SECONDS)// 写入时间:10s超时if (BaseApplication.isDebugMode) addInterceptor(mLoggingInterceptor)// 仅debug模式启用日志过滤器}.build()/*** 日志拦截器*/private class LoggingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
    val builder = StringBuilder()val startTime = System.nanoTime()val response: Response = with(chain.request()) {
    builder.append(method() + "\n")builder.append("Sending request\n" + url())if (method() == "POST") {
    builder.append("?")when (val body = body()) {
    is FormBody -> {
    for (j in 0 until body.size()) {
    builder.append(body.name(j) + "=" + body.value(j))if (j != body.size() - 1) {
    builder.append("&")}}}
// is MultipartBody -> {}}}builder.append("\n").append(headers())chain.proceed(this)}builder.append("Received response in " + (System.nanoTime() - startTime) / 1e6 + "ms\n")builder.append("code" + response.code() + "\n")LogUtil.v(builder.toString())return response}}
}

api接口 ApiServiceKt.kt

/*** api接口* @author ssq* @JvmSuppressWildcards 用来注解类和方法,使得被标记元素的泛型参数不会被编译成通配符?*/
@JvmSuppressWildcards
interface ApiServiceKt {
    /*** 下载文件* @param fileUrl 文件地址 (这里的url可以是全名,也可以是基于baseUrl拼接的后缀url)* @return*/@Streaming@GETfun downloadFileAsync(@Url fileUrl: String): Deferred<ResponseBody>/*** 上传图片* @param url 可选,不传则使用默认值* @param imgPath 图片路径* @param map 参数*/@Multipart@POSTfun uploadImgAsync(@Url url: String = "${
      ApiConstant.UPLOAD_IMAGE_URL}Upload.php", @PartMap imgPath: Map<String, RequestBody>, @QueryMap map: Map<String, Any>): Deferred<Response<UploadImgEntity>>/*** 通用异步请求 只需要解析BaseBean*/@FormUrlEncoded@POST("Interfaces/index")fun requestAsync(@FieldMap map: Map<String, Any>): Deferred<Response<BaseBean>>
}

Retrofit 管理类 RetrofitManagerKt.kt

import android.content.Context
import android.webkit.MimeTypeMap
import com.blankj.utilcode.util.SPUtils
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Response
import java.io.File
import java.net.URLEncoder
import java.util.*/*** Retrofit 管理类* @author ssq*/
object RetrofitManagerKt {
    // 接口API服务val apiService by lazy {
     ApiFactory.create(ApiConstant.BASE_URL, ApiServiceKt::class.java) }/*** 执行网络请求(结合kotlin 协程使用)* @param deferred 请求的接口* @param isValidateCode 是否验证code,如:登录是否过期* @param context 为null时,登录过期不跳登录页*/suspend fun <T : BaseBean> request(deferred: Deferred<Response<T>>, isValidateCode: Boolean = false, context: Context? = null): T? = withContext(Dispatchers.Default) {
    try {
    val response = deferred.await()if (response.isSuccessful) {
    // 成功val body = response.body()if (isValidateCode && body != null) {
    validateCode(context, body.code)}body ?: throw NullBodyException()} else {
    // 处理Http异常ExceptionUtil.catchHttpException(response.code())null}} catch (e: Exception) {
    // 这里统一处理错误ExceptionUtil.catchException(e)null}}/*** 下载文件*/suspend fun downloadFile(fileUrl: String): ResponseBody? = withContext(Dispatchers.IO) {
    try {
    apiService.downloadFileAsync(fileUrl).await()} catch (e: Exception) {
    // 这里统一处理错误ExceptionUtil.catchException(e)null}}/*** 上传图片文件* @param file 图片文件* @param type 用途类型*/suspend fun uploadImage(file: File, type: Int): UploadImgEntity? =request(apiService.uploadImgAsync(imgPath = getUploadImgBodyMap(file), map = getUploadImgMap(type)))/*** 生成上传图片请求的文件参数* @param file 上传文件*/fun getUploadImgBodyMap(file: File): HashMap<String, RequestBody> {
    val requestBodyMap = hashMapOf<String, RequestBody>()val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(file.path)) ?: "image/jpeg"val fileBody = RequestBody.create(MediaType.parse(mimeType), file)// (注意:okhttp3 请求头不能为中文)如果url参数值含有中文、特殊字符时,需要使用 url 编码。requestBodyMap["myfiles\"; filename=\"${
      URLEncoder.encode(file.name, "utf-8")}"] = fileBodyreturn requestBodyMap}/*** 生成上传图片请求参数* @param type 用途类型*/fun getUploadImgMap(type: Int): HashMap<String, Any> {
    val map = hashMapOf<String, Any>()map["type"] = typemap["time"] = System.currentTimeMillis()return map}
}

异常工具类 ExceptionUtil.kt

import android.accounts.NetworkErrorException
import android.content.res.Resources
import androidx.annotation.StringRes
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.stream.MalformedJsonException
import retrofit2.HttpException
import java.net.SocketTimeoutException
import java.net.UnknownHostException/*** 异常工具类* @author ssq*/
object ExceptionUtil {
    /*** 处理异常*/fun catchException(e: Exception) {
    e.printStackTrace()val msg = when (e) {
    is HttpException -> {
    catchHttpException(e.code())return}is SocketTimeoutException -> R.string.common_error_net_time_outis UnknownHostException, is NetworkErrorException -> R.string.common_error_netis NullPointerException, is ClassCastException, is Resources.NotFoundException,is MalformedJsonException -> R.string.common_error_do_something_failis NullBodyException -> R.string.common_error_server_body_nullelse -> R.string.common_error_do_something_fail}ToastUtils.showShort(msg)}/*** 处理网络异常*/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) {
    ToastUtils.showShort("${
      BaseApplication.instance.getString(errorMsg)}$errorCode ")}/*** 处理网络异常*/private fun catchHttpExceptionCode(errorCode: Int): Int = when (errorCode) {
    in 500..600 -> R.string.common_error_serverin 400 until 500 -> R.string.common_error_requestelse -> R.string.common_error_request}
}

提示文字 string.xml

	<string name="common_error_net">网络异常,请检查网络连接!</string><string name="common_error_net_time_out">网络超时</string><string name="common_error_do_something_fail">操作异常</string><string name="common_error_request">请求错误</string><string name="common_error_server">服务器错误</string><string name="common_error_server_body_null">服务器错误:body为空</string>

3、开始使用

MVP架构模式

  • Activity文件,Activity为主协程域,在界面销毁时取消协程。
/*** 地址列表页* "CoroutineScope by MainScope()" 协程生命周期管理。在onDestroy()中调用cancel()取消协程。调用launch{}启动协程* @author ssq*/
class AddressListActivity : BaseListActivity<AddressListPresenter>(), CoroutineScope by MainScope(), AddressListContract.View {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)mPresenter = AddressListPresenter(this)mPresenter?.deleteAddress(data.id, position)}override fun onDestroy() {
    super.onDestroy()cancel()// 取消协程}
}
  • Presenter文件,从Activity 把协程域传递给Presenter
class AddressListPresenter(scope: CoroutineScope) : BasePresenterKt<AddressListContract.View>(), CoroutineScope by scope, AddressListContract.Presenter {
    override fun deleteAddress(id: String, position: Int) = launch {
    val map = HashMap<String, Any>()map["id"] = idRetrofitManagerKt.request(RetrofitManagerKt.apiService.requestAsync(map), true, mContext)?.also {
    if (it.code == 0) {
    mView?.deleteAddressSuccess(position)} else {
    ToastUtils.showShort(it.message)}}}
}

-------2020年4月25日更新----------
有需要Demo源码的朋友看这里,第三方库版本是基于4月25日最新的
可以将源码下载下来参考,或是直接使用。加快你的项目开发!如果可以顺便点个Star 非常感谢了?
MVP架构源码GitHub地址

MVVM架构模式 看这里

至此,主要逻辑就是这些!

  相关解决方案