万字好文!使用协程,让网络世界更加美好

点击上方 "编程技术圈"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

每日英文

Smile and stop complaining about the things you can't change. Time keeps ticking whether you're happy or sad. 

保持微笑,停止抱怨那些改变不了的事。无论你开心与否,时间总是不等人的。

每日掏心话

人生路艰难,但是如果你有笑对人生的能力,你就有享受人生的能力。

来自:1个懒人 | 责编:乐乐

链接:blog.csdn.net/taotao110120119

编程技术圈(ID:study_tech)第 1124 次推文

往日回顾:某程序员动了公司的祖传代码“屎山”,半年后怒交辞职报告!

     

   正文   

/   前言   /

本文会讲解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介绍,参考官方教程。

/   Retrofit + Coroutine   /

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

Retrofit对Coroutine的支持

Retrofit 从 2.6.0版本开始支持Coroutine。

万字好文!使用协程,让网络世界更加美好_第1张图片

/   开始搭建   /

我们用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。

初始化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”,十分方便。

定义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>
) {
    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。

在ViewModel里访问Translate接口

MainViewModel

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

    val translateResult: LiveData
        get() = _translateResult

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

}

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

在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,我们的代码逻辑也变得很清晰。

测试运行

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

万字好文!使用协程,让网络世界更加美好_第2张图片

能够正确显示翻译结果,说明我们的网络请求从请求发出,到json数据反序列化成对象这整套流程是正常的。代码逻辑清晰,测试结果正常,是不是一切都ok了呢?显然不是的,我们少考虑了一种情况,如果没有网络,或者服务器异常,那么:

val result = TranslateApi.retrofitService.translate(word)

这里的result会返回什么呢?我们将网络断开,再测试一下。

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

/   异常情况处理   /

原因分析

之所以会崩溃是因为:

val result = TranslateApi.retrofitService.translate(word)

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

解决方案1:try catch Exception

很容易我们就想到,可以try catch Exception。修改后的 MainViewModel:

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

    val translateResult: LiveData
        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,而且这种情况下也没有编译提示。他测试的时候网络很好,所以一切正常,但是到用户那一旦网络不好就崩溃了。

  2. 此时的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} 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} 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 {
  /**
   * 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} is {@code Repo}. This type is
   * used to prepare the {@code call} passed to {@code #adapt}.
   *
   * 

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}.    *    * 

For example, given an instance for a hypothetical utility, {@code Async}, this instance    * would return a new {@code Async} which invoked {@code call} when run.    *    * 


   * @Override
   * public <R> Async<R> adapt(final Call<R> call) {
   *   return Async.create(new Callable<Response<R>>() {
   *     @Override
   *     public Response<R> call() throws Exception {
   *       return call.execute();
   *     }
   *   });
   * }
   * 
   */   T adapt(Call call); }

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

总结

T adapt(Call 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.
 *
 * 

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  Successful response body type.  */ public interface Call 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 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 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 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 getUser();
}

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

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

ApiResult

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

使用ApiResult后的TranslateApiService:

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

所以接下来我们的CallAdapter的任务就是将Call对象包装成ApiResult对象。但这里面还有一个很关键的问题。

Retrofit 对suspend方法的内部处理

万字好文!使用协程,让网络世界更加美好_第3张图片

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

  1. Retrofit对于suspend方法,相当于定义了一个非suspend方法,并且suspend方法后面的返回值对象T,实际上相当于非suspend方法的Call对象

  2. suspend方法的执行相当于执行了非suspend方法的Call对象的Call.enqueue(callback)

call.enqueue(new Callback() {
            //请求成功时候的回调
            @Override
            public void onResponse(Call call, Response response) {

                // 将 response.body() 作为suspend方法成功时的返回值
            }

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

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

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

相当于定义了:

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

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

fun adapt(call: Call): Call>

下面看一下具体实现。

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, retrofit: Retrofit): CallAdapter<*, *>? {
        /*凡是检测不通过的,直接抛异常,提示使用者返回值类型格式不对
        因为ApiResultCallAdapterFactory是使用者显式设置使用的*/


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

        //检查returnType是否是Call类型的
        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,检查是否是ApiResult
        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 也就是API返回数据对应的数据类型
        val dataType = getParameterUpperBound(0, apiResultType)

        return ApiResultCallAdapter(dataType)


    }

}

class ApiResultCallAdapter(private val type: Type) : CallAdapter>> {
    override fun responseType(): Type = type

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

class ApiResultCall(private val delegate: Call) : Call> {
    /**
     * 该方法会被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>) {
        //delegate 是用来做实际的网络请求的Call对象,网络请求的成功失败会回调不同的方法
        delegate.enqueue(object : Callback {

            /**
             * 网络请求成功返回,会回调该方法(无论status code是不是200)
             */
            override fun onResponse(call: Call, response: Response) {
                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: 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> = ApiResultCall(delegate.clone())

    override fun execute(): Response> {
        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方法。在公众号编程技术圈后台回复“Java”,获取一份惊喜礼包。

自定义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。地址为:

https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt

ApiException

class ApiException(val errorCode:Int,val errorMsg:String): IOException()

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

ApiResultCallAdapter.kt

 /**
             * 在网络请求中发生了异常,会回调该方法
             *
             * 对于网络请求成功,但是业务失败的情况,我们也会在对应的Interceptor中抛出异常,这种情况也会回调该方法
             */
            override fun onFailure(call: Call, 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 = MutableLiveData()

    val translateResult: LiveData
        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"
            }
        ]
    ]
}

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

万字好文!使用协程,让网络世界更加美好_第4张图片

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

Demo地址如下:

https://github.com/ShuangtaoJia/RetrofitWithCoroutineDemo

/   最后   /

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

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

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

欢迎加入后端架构师交流群,在后台回复“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

Java 身份证号码识别系统

重磅,12306抢票小助手开源了!

看看人家那后端API接口写得,那叫一个优雅!

BAT等大厂Java面试经验总结

别找了,想获取 Java大厂面试题学习资料

扫下方二维码回复「手册」就好了



嘿,你在看吗

你可能感兴趣的:(网络,java,编程语言,junit,mvvm)