天天看点

Retrofit+Kotlin Coroutine重构Android网络框架前言Kotlin Coroutine 简介Retrofit + Coroutine开始搭建Retrofit + Coroutine网络框架异常情况处理重构异常处理改造后的效果Demo最后

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

  • 前言

本文会讲解Coroutine的优点,以及一步步的从零开始改造 Retrofit+Coroutine,对改造中的关键问题进行讲解,给出详细可运行的示例代码。最后会给出Demo,Demo经过简单修改可以直接运用在自己的实际项目中。

  • Kotlin Coroutine 简介

Kotlin coroutines let you convert callback-based code to sequential code. Code written sequentially is typically easier to read, and can even use language features such as exceptions.

Coroutine可以让你把基于callback的代码转换成顺序代码.

使用Coroutine会有以下几个好处:

  1. 顺序编写的代码更易读
  2. 使用Coroutine可以与组件的生命周期相关联,在生命周期结束后自动取消Coroutine,而且官方对Android开发中常用的场景都提供了CoroutineScope,你可以直接使用,不用再关心生命周期问题,比如在Activity和Fragment中使用的 viewModelScope
  3. Coroutine相对于Rxjava等技术会更易读,更易用,更易调试,而且是官方支持,所以无论是学习资料,后期维护,还是Android Studio的支持都会更好。
  4. 使用Coroutine可以catch异步执行过程中的exception,使用callback形式不可以。

详细的Coroutine介绍,参考官方教程 Coroutines in Kotlin

  • Retrofit + Coroutine

如果将Coroutine与Retrofit结合起来,就能将Coroutine的优点用于网络访问代码。

  • Retrofit对Coroutine的支持

Retrofit 从 2.6.0版本开始支持Coroutine

Retrofit+Kotlin Coroutine重构Android网络框架前言Kotlin Coroutine 简介Retrofit + Coroutine开始搭建Retrofit + Coroutine网络框架异常情况处理重构异常处理改造后的效果Demo最后

Retrofit Change Log

  • 开始搭建Retrofit + Coroutine网络框架

我们用Retrofit + Coroutine来写一个API的示例,其中会用到Jetpack的ViewModel,LiveData等组件。

我们来访问有道词典的API,来翻译一个单词。

http://fanyi.youdao.com/translate?doctype=json&i=Hello%20world

此时会返回:

{
    "type": "EN2ZH_CN",
    "errorCode": 0,
    "elapsedTime": 1,
    "translateResult": [
        [
            {
                "src": "Hello world",
                "tgt": "你好,世界"
            }
        ]
    ]
}
           

我们来实现这个API。

1. 初始化retrofit

NetworkBase.kt

package com.jst.network

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

private const val BASE_URL = "http://fanyi.youdao.com/"

val retrofit = Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl(BASE_URL)
    .build()
           

将retrofit实例对象直接定义在包下面,这样其他类在用的时候可以直接用"retrofit",不用使用 “类名.retrofit”,十分方便

2. 定义Translate接口 相关的类

TranslateApiService.kt

interface TranslateApiService {
    @FormUrlEncoded
    @POST("translate?doctype=json")
    suspend fun translate(@Field("i")i:String):Result
}

data class Result(
    val type: String,
    val elapsedTime: Int,
    val translateResult: List<List<TranslateResult>>
) {
    data class TranslateResult(
        val src:String,
        val tgt:String
    )
}

object TranslateApi{
    val retrofitService: TranslateApiService by lazy { retrofit.create(TranslateApiService::class.java) }
}
           

kotlin里可以将多个类定义在一个文件里,因为一个API接口会包含若干个相关的类,所以我们把这些类定义在一个文件里会方便管理,也使得我们的工程里没有那么多碎的文件,看起来会很简洁。

我们之所以会定义一个object TranslateApi,是因为

retrofit.create(TranslateApiService::class.java)

是一个比较重的操作,所以我们将结果放在object里保存,这样下次再用的时候就不会重复调用retrofit.create

by lazy{} 可以实现延迟初始化,当property retrofitService 被第一次访问时执行lazy{} 块里的代码返回一个TranslateApiService实例赋值给retrofitService,下次再访问时会复用之前返回的TranslateApiService实例,详见kotlin里的Delegated Properties

3. 在ViewModel里访问Translate接口

MainViewModel

class MainViewModel : ViewModel() {
    private val _translateResult: MutableLiveData<String> = MutableLiveData()

    val translateResult: LiveData<String>
        get() = _translateResult

    fun translate(word: String) {
        viewModelScope.launch {
            val result = TranslateApi.retrofitService.translate(word)
            _translateResult.value = result.translateResult[0][0].tgt
        }
    }

}
           

我们可以看网络访问的代码,就一行,使用Coroutine以后这里没有callback,代码是顺序代码,就像你想要表达的逻辑那样顺序写下来。这代码可读性是不是好到没朋友。

4. 在Fragment里调用ViewModel

MainFragment:

class MainFragment : Fragment() {

    companion object {
        fun newInstance() = MainFragment()
    }

    private val viewModel: MainViewModel by viewModels()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.main_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        val word = "Hello world"
        textview.text = "正在翻译……"

        viewModel.translateResult.observe(viewLifecycleOwner){
            textview.text = "原词:    $word \n翻译:    $it"
        }

        viewModel.translate(word)
    }

}
           

使用ViewModel+LiveData,我们的代码逻辑也变得很清晰。

5. 测试运行

我们的示例代码已经搭建完了,运行一下

Retrofit+Kotlin Coroutine重构Android网络框架前言Kotlin Coroutine 简介Retrofit + Coroutine开始搭建Retrofit + Coroutine网络框架异常情况处理重构异常处理改造后的效果Demo最后

能够正确显示翻译结果,说明我们的网络请求从请求发出,到json数据反序列化成对象这整套流程是正常的。

代码逻辑清晰,测试结果正常,是不是一切都ok了呢?

显然不是的,我们少考虑了一种情况,如果没有网络,或者服务器异常,那么

val result = TranslateApi.retrofitService.translate(word)

这里的result会返回什么呢?

我们将网络断开,再测试一下:

发现程序崩溃了!

我们肯定不能在异常情况下直接让程序崩溃,所以我们要处理异常情况。

  • 异常情况处理

1. 原因分析

之所以会崩溃是因为

val result = TranslateApi.retrofitService.translate(word)

在异常情况下会throw Exception,而我们的程序没有处理Exception,所以程序崩溃了。

2. 解决方案1:try catch Exception

很容易我们就想到,可以try catch Exception

修改后的 MainViewModel:

class MainViewModel : ViewModel() {
    private val _translateResult: MutableLiveData<String> = MutableLiveData()

    val translateResult: LiveData<String>
        get() = _translateResult

    fun translate(word: String) {
        viewModelScope.launch {
            try {
                val result = TranslateApi.retrofitService.translate(word)
                _translateResult.value = result.translateResult[0][0].tgt
            } catch (e: Exception) {
                _translateResult.value = e.message
            }
        }
    }

}
           

再次运行,发现程序不崩溃了。

这样是不是就ok了呢?

崩溃确实是不崩溃了,但是考虑我们在开发过程中的实际需求,这种方案存在几个问题:

  1. 我们的网络框架依赖于try catch的话就会很容易出错

比如,开发人员很容易忘了try catch,而且这种情况下也没有编译提示。他测试的时候网络很好,所以一切正常,但是到用户那一旦网络不好就崩溃了

  1. 此时的e.message里没有我们想展示给用户的提示信息

我们通常的网络框架在异常的时候,需要提供errorCode 和errorMsg,目前这种try catch的方案无法满足我们的需求。

基于上面两个原因,我们需要重构异常处理。

  • 重构异常处理

我们不想依赖于try catch,但是又想处理网络请求过程中的异常情况,而且要增加errorCode和errorMsg异常信息,我们如何来实现呢?

我们可以自定义Retrofit的CallAdapter来满足我们的需求。

  • 自定义CallAdapter

CallAdapter是由CallAdapterFactory生成的,我们先来看一下CallAdapterFactory的定义

CallAdapter.Factory:

/**
   * Creates {@link CallAdapter} instances based on the return type of {@linkplain
   * Retrofit#create(Class) the service interface} methods.
   */
  abstract class Factory {
    /**
     * Returns a call adapter for interface methods that return {@code returnType}, or null if it
     * cannot be handled by this factory.
     */
     
    public abstract @Nullable CallAdapter<?, ?> get(
        Type returnType, Annotation[] annotations, Retrofit retrofit);

    /**
     * Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
     * example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
     */
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    /**
     * Extract the raw class type from {@code type}. For example, the type representing {@code
     * List<? extends Runnable>} returns {@code List.class}.
     */
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
           

源码注释已经写的很清楚了,这里面每个方法的注释都要读,后面写的时候都会用到。

总结:

Factory的作用就是根据你定义的接口(上例中的TranslateApiService)的返回值类型来判断是否是该CallAdapter需要处理的返回值类型,如果是,则返回一个处理该返回值类型的CallAdapter,如果不是,则返回null

CallAdapter:

/**
 * Adapts a {@link Call} with response type {@code R} into the type of {@code T}. Instances are
 * created by {@linkplain Factory a factory} which is {@linkplain
 * Retrofit.Builder#addCallAdapterFactory(Factory) installed} into the {@link Retrofit} instance.
 */
public interface CallAdapter<R, T> {
  /**
   * Returns the value type that this adapter uses when converting the HTTP response body to a Java
   * object. For example, the response type for {@code Call<Repo>} is {@code Repo}. This type is
   * used to prepare the {@code call} passed to {@code #adapt}.
   *
   * <p>Note: This is typically not the same type as the {@code returnType} provided to this call
   * adapter's factory.
   */
  Type responseType();

  /**
   * Returns an instance of {@code T} which delegates to {@code call}.
   *
   * <p>For example, given an instance for a hypothetical utility, {@code Async}, this instance
   * would return a new {@code Async<R>} which invoked {@code call} when run.
   *
   * <pre><code>
   * &#64;Override
   * public &lt;R&gt; Async&lt;R&gt; adapt(final Call&lt;R&gt; call) {
   *   return Async.create(new Callable&lt;Response&lt;R&gt;&gt;() {
   *     &#64;Override
   *     public Response&lt;R&gt; call() throws Exception {
   *       return call.execute();
   *     }
   *   });
   * }
   * </code></pre>
   */
  T adapt(Call<R> call);
  
}
           

这里面的每个注释也都要看,这里的方法后面写的时候也都会用到。

总结:

T adapt(Call<R> call);

CallAdapter顾名思义就是Call的Adapter,Call指的是retrofit里的Call对象retrofit2.Call

Call:

/**
 * An invocation of a Retrofit method that sends a request to a webserver and returns a response.
 * Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple
 * calls with the same parameters to the same webserver; this may be used to implement polling or to
 * retry a failed call.
 *
 * <p>Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link
 * #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that
 * is busy writing its request or reading its response may receive a {@link IOException}; this is
 * working as designed.
 *
 * @param <T> Successful response body type.
 */
public interface Call<T> extends Cloneable {
  /**
   * Synchronously send the request and return its response.
   *
   * @throws IOException if a problem occurred talking to the server.
   * @throws RuntimeException (and subclasses) if an unexpected error occurs creating the request or
   *     decoding the response.
   */
  Response<T> execute() throws IOException;

  /**
   * Asynchronously send the request and notify {@code callback} of its response or if an error
   * occurred talking to the server, creating the request, or processing the response.
   */
  void enqueue(Callback<T> callback);

  /**
   * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
   * #enqueue(Callback) enqueued}. It is an error to execute or enqueue a call more than once.
   */
  boolean isExecuted();

  /**
   * Cancel this call. An attempt will be made to cancel in-flight calls, and if the call has not
   * yet been executed it never will be.
   */
  void cancel();

  /** True if {@link #cancel()} was called. */
  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call<T> clone();

  /** The original HTTP request. */
  Request request();

  /**
   * Returns a timeout that spans the entire call: resolving DNS, connecting, writing the request
   * body, server processing, and reading the response body. If the call requires redirects or
   * retries all must complete within one timeout period.
   */
  Timeout timeout();
}
           

这个Call对象可以完成同步或异步的网络请求。

Retrofit把这样的一个Call对象传递给adapt方法,然后不同的CallAdapter可以将这个Call对象包装成你需要返回给用户使用的T对象,T对象的内部是使用Call对象的网络请求的能力。

比如像Rxjava,我们需要让用户在定义网络接口的时候直接返回一个Observable对象

interface MyService {
  @GET("/user")
  Observable<User> getUser();
}
           

那么我们就可以定义一个RxJavaCallAdapter来将Call对象包装成Observable对象。当然对于RxJava的这种情况,官方已经提供了CallAdapter。

针对我们目前的需求,我们需要在网络请求成功的时候返回数据Bean对象,在网络请求失败的时候返回一个包含了errorCode和errorMsg的对象,Kotlin的sealed class比较适合我们的场景。

ApiResult:

sealed class ApiResult<out T> {
    data class Success<out T>(val data: T?):ApiResult<T>()
    data class Failure(val errorCode:Int,val errorMsg:String):ApiResult<Nothing>()
}
           

使用ApiResult后的TranslateApiService:

interface TranslateApiService {
    @FormUrlEncoded
    @POST("translate?doctype=json")
    suspend fun translate(@Field("i")i:String):ApiResult<Result>
}
           

所以接下来我们的CallAdapter的任务就是将Call对象包装成ApiResult对象。

但这里面还有一个很关键的问题。

  • Retrofit 对suspend方法的内部处理
Retrofit+Kotlin Coroutine重构Android网络框架前言Kotlin Coroutine 简介Retrofit + Coroutine开始搭建Retrofit + Coroutine网络框架异常情况处理重构异常处理改造后的效果Demo最后

从官方描述中我们可以看到:

  1. Retrofit对于suspend方法,相当于定义了一个非suspend方法,并且suspend方法后面的返回值对象T,实际上相当于非suspend方法的Call<T>对象
  2. suspend方法的执行相当于执行了非suspend方法的Call<T>对象的Call.enqueue(callback)
call.enqueue(new Callback<T>() {
            //请求成功时候的回调
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                
                // 将 response.body() 作为suspend方法成功时的返回值
            }

            //请求失败时候的回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                // 将 throwable 作为suspend方法失败时的异常抛出
            }
        });
           

所以我们对于suspend方法自定义CallAdapter时要将它作为非suspend方法来看待。

我们定义的

interface TranslateApiService {
    @FormUrlEncoded
    @POST("translate?doctype=json")
    suspend fun translate(@Field("i")i:String):ApiResult<Result>
}
           

相当于定义了

interface TranslateApiService {
    @FormUrlEncoded
    @POST("translate?doctype=json")
    fun translate(@Field("i")i:String):Call<ApiResult<Result>>
}
           

所以我们的CallAdapter的作用就是处理返回值类型为Call<ApiResult<T>>形式的接口,对于CallAdapter的adapt方法的作用就是将retorfit传给我们的Call<T> 转换成Call<ApiResult<T>>

下面看一下具体实现

ApiResultCallAdapter.kt:

package com.jst.network.calladapter


import com.jst.network.ApiError
import com.jst.network.ApiResult
import com.jst.network.exception.ApiException
import okhttp3.Request
import okio.Timeout
import retrofit2.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type


class ApiResultCallAdapterFactory : CallAdapter.Factory() {
    override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
        /*凡是检测不通过的,直接抛异常,提示使用者返回值类型格式不对
        因为ApiResultCallAdapterFactory是使用者显式设置使用的*/


        //以下是检查是否是 Call<ApiResult<T>> 类型的returnType

        //检查returnType是否是Call<T>类型的
        check(getRawType(returnType) == Call::class.java) { "$returnType must be retrofit2.Call." }
        check(returnType is ParameterizedType) { "$returnType must be parameterized. Raw types are not supported" }

        //取出Call<T> 里的T,检查是否是ApiResult<T>
        val apiResultType = getParameterUpperBound(0, returnType)
        check(getRawType(apiResultType) == ApiResult::class.java) { "$apiResultType must be ApiResult." }
        check(apiResultType is ParameterizedType) { "$apiResultType must be parameterized. Raw types are not supported" }

        //取出ApiResult<T>中的T 也就是API返回数据对应的数据类型
        val dataType = getParameterUpperBound(0, apiResultType)

        return ApiResultCallAdapter<Any>(dataType)


    }

}

class ApiResultCallAdapter<T>(private val type: Type) : CallAdapter<T, Call<ApiResult<T>>> {
    override fun responseType(): Type = type

    override fun adapt(call: Call<T>): Call<ApiResult<T>> {
        return ApiResultCall(call)
    }
}

class ApiResultCall<T>(private val delegate: Call<T>) : Call<ApiResult<T>> {
    /**
     * 该方法会被Retrofit处理suspend方法的代码调用,并传进来一个callback,如果你回调了callback.onResponse,那么suspend方法就会成功返回
     * 如果你回调了callback.onFailure那么suspend方法就会抛异常
     *
     * 所以我们这里的实现是永远回调callback.onResponse,只不过在请求成功的时候返回的是ApiResult.Success对象,
     * 在失败的时候返回的是ApiResult.Failure对象,这样外面在调用suspend方法的时候就不会抛异常,一定会返回ApiResult.Success 或 ApiResult.Failure
     */
    override fun enqueue(callback: Callback<ApiResult<T>>) {
        //delegate 是用来做实际的网络请求的Call<T>对象,网络请求的成功失败会回调不同的方法
        delegate.enqueue(object : Callback<T> {

            /**
             * 网络请求成功返回,会回调该方法(无论status code是不是200)
             */
            override fun onResponse(call: Call<T>, response: Response<T>) {
                if (response.isSuccessful) {//http status 是200+
                    //这里担心response.body()可能会为null(还没有测到过这种情况),所以做了一下这种情况的处理,
                    // 处理了这种情况后还有一个好处是我们就能保证我们传给ApiResult.Success的对象就不是null,这样外面用的时候就不用判空了
                    val apiResult = if (response.body() == null) {
                        ApiResult.Failure(ApiError.dataIsNull.errorCode, ApiError.dataIsNull.errorMsg)
                    } else {
                        ApiResult.Success(response.body()!!)
                    }
                    callback.onResponse(this@ApiResultCall, Response.success(apiResult))
                } else {//http status错误
                    val failureApiResult = ApiResult.Failure(ApiError.httpStatusCodeError.errorCode, ApiError.httpStatusCodeError.errorMsg)
                    callback.onResponse(this@ApiResultCall, Response.success(failureApiResult))
                }

            }

            /**
             * 在网络请求中发生了异常,会回调该方法
             *
             * 对于网络请求成功,但是业务失败的情况,我们也会在对应的Interceptor中抛出异常,这种情况也会回调该方法
             */
            override fun onFailure(call: Call<T>, t: Throwable) {
                val failureApiResult = if (t is ApiException) {//Interceptor里会通过throw ApiException 来直接结束请求 同时ApiException里会包含错误信息
                    ApiResult.Failure(t.errorCode, t.errorMsg)
                } else {
                    ApiResult.Failure(ApiError.unknownException.errorCode, ApiError.unknownException.errorMsg)
                }

                callback.onResponse(this@ApiResultCall, Response.success(failureApiResult))
            }

        })
    }

    override fun clone(): Call<ApiResult<T>> = ApiResultCall(delegate.clone())

    override fun execute(): Response<ApiResult<T>> {
        throw UnsupportedOperationException("ApiResultCall does not support synchronous execution")
    }


    override fun isExecuted(): Boolean {
        return delegate.isExecuted
    }

    override fun cancel() {
        delegate.cancel()
    }

    override fun isCanceled(): Boolean {
        return delegate.isCanceled
    }

    override fun request(): Request {
        return delegate.request()
    }

    override fun timeout(): Timeout {
        return delegate.timeout()
    }
}
           

在关键的地方都做了注释,看注释逻辑还是比较清晰的。

看完代码后你会发现这里面还有一个未实现的部分,就是处理业务异常的Interceptor:

业务异常指的是服务器正常返回了数据,但是errorCode不是成功,而是代表了某种业务错误,此时有可能不会返回你定义的业务成功的数据Bean。

以翻译API为例

业务成功时返回的json格式:

{
    "type": "EN2ZH_CN",
    "errorCode": 0,
    "elapsedTime": 1,
    "translateResult": [
        [
            {
                "src": "Hello world",
                "tgt": "你好,世界"
            }
        ]
    ]
}
           

业务失败时有可能返回的json格式:

{
    "errorCode": 50,
    "errorMsg": "xxx",
}
           

我们可以定义一个Interceptor来处理这种情况:

在errorCode不是0的情况下,抛出异常,异常信息里包含errorCode和errorMsg,如果你在Interceptor里抛出了异常,OkHttp会终止本次请求,回调onFailure方法。

  • 自定义Interceptor

BusinessErrorInterceptor:

/**
 * 业务错误 Interceptor
 * 对于request: 无
 * 对于response:负责解析业务错误(在http status 成功的前提下)
 */
class BusinessErrorInterceptor :Interceptor{
    override fun intercept(chain: Interceptor.Chain): Response {

        var response = chain.proceed(chain.request())
        //http status不是成功的情况下,我们不处理
        if (!response.isSuccessful){
            return response
        }

        //因为response.body().string() 只能调用一次,所以这里读取responseBody不使用response.body().string(),原因:https://juejin.im/post/6844903545628524551
        //以下读取resultString的代码节选自
        //https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt

        val responseBody = response.body()!!
        val source = responseBody.source()
        source.request(Long.MAX_VALUE) // Buffer the entire body.
        var buffer = source.buffer
        val contentType = responseBody.contentType()
        val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8
        val resultString = buffer.clone().readString(charset)


        val jsonObject = JSONObject(resultString)
        if (!jsonObject.has("errorCode")) {
            return response
        }

        val errorCode = jsonObject.optInt("errorCode")
        //对于业务成功的情况不做处理
        if (errorCode == 0) {
            return response
        }
        //我们的示例里服务器没有返回errorMsg,一般实际应用中服务器都会有errorMsg
        throw ApiException(errorCode, "some error msg")
    }
}
           

这里需要注意response.body().string() 只能调用一次的问题,所以这里我们不能调用response.body().string(),具体原因:

https://juejin.im/post/6844903545628524551

我们读取resultString的代码节选自okhttp官方的HttpLoggingInterceptor.kt

ApiException:

这里需要注意的是我们自定义的ApiException必须继承自IOException,因为只有IOException才会被OkHttp处理,然后回调到onFailure方法,其他类型的异常是直接就崩溃了。

回到前面的ApiResultCallAdapter.kt,我们可以看到对ApiException的处理

ApiResultCallAdapter.kt:

/**
             * 在网络请求中发生了异常,会回调该方法
             *
             * 对于网络请求成功,但是业务失败的情况,我们也会在对应的Interceptor中抛出异常,这种情况也会回调该方法
             */
            override fun onFailure(call: Call<T>, t: Throwable) {
                val failureApiResult = if (t is ApiException) {//Interceptor里会通过throw ApiException 来直接结束请求 同时ApiException里会包含错误信息
                    ApiResult.Failure(t.errorCode, t.errorMsg)
                } else {
                    ApiResult.Failure(ApiError.unknownException.errorCode, ApiError.unknownException.errorMsg)
                }

                callback.onResponse(this@ApiResultCall, Response.success(failureApiResult))
            }
           

最后我们添加ApiResultCallAdapter和BusinessErrorInterceptor

NetworkBase.kt:

private const val BASE_URL = "http://fanyi.youdao.com/"

private val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(BusinessErrorInterceptor())
    .build()

val retrofit = Retrofit.Builder()
    .addCallAdapterFactory(ApiResultCallAdapterFactory())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl(BASE_URL)
    .client(okHttpClient)
    .build()
           
  • 改造后的效果

此时调用接口时的代码就变成了:

MainViewModel:

class MainViewModel : ViewModel() {
    private val _translateResult: MutableLiveData<String> = MutableLiveData()

    val translateResult: LiveData<String>
        get() = _translateResult

    fun translate(word: String) {
        viewModelScope.launch {
            when (val result = TranslateApi.retrofitService.translate(word)) {
                is ApiResult.Success -> {
                    _translateResult.value = result.data.translateResult1[0][0].tgt
                }
                is ApiResult.Failure -> {
                    _translateResult.value = "errorCode: ${result.errorCode} errorMsg: ${result.errorMsg}"
                }
            }
        }
    }

}
           

没有callback,我们的代码还是顺序代码,同时也保证了在成功时拿到数据对象,在失败时拿到errorCode和errorMsg。

我们来验证一下失败时的情况:

翻译API在单词过长的情况下,会返回错误。

比如我们翻译"IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"

此时返回的json:

{
    "type": "UNSUPPORTED",
    "errorCode": 40,
    "elapsedTime": 0,
    "translateResult": [
        [
            {
                "src": "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII",
                "tgt": "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"
            }
        ]
    ]
}
           

此时我们的程序运行的情况:

Retrofit+Kotlin Coroutine重构Android网络框架前言Kotlin Coroutine 简介Retrofit + Coroutine开始搭建Retrofit + Coroutine网络框架异常情况处理重构异常处理改造后的效果Demo最后

正确的解析出了errorCode和errorMsg信息

  • Demo

https://github.com/ShuangtaoJia/RetrofitWithCoroutineDemo

  • 最后

Demo中的代码将参数名按照项目的实际情况简单修改,就可以放在实际项目中使用。

Retrofit+Coroutine 会是以后的主流形式,因为有官方的支持再加上自身的诸多优点,会逐步的取代RxJava等其他技术,大家可以尽早体验使用。

有问题欢迎留言。