将回调地狱转换为延迟对象

时间:2019-04-11 07:25:33

标签: android kotlin coroutine kotlin-coroutines

背景 :因此,我有一个很大的项目,具有许多API函数。我正在考虑完全使用协程,但是由于它们是用Callback而不是Deferred来实现的,所以我不能高效地使用它们。例如:我想执行apiCallOne()apiCallTwo()apiCallThree()异步并调用.await()以等到最后一个请求完成,然后再更改UI。

现在该项目的结构如下:

最底端(或顶部)是ApiService.java

interface ApiService {
    @GET("...")
    Call<Object> getData();
    ...
}

然后我有一个ClientBase.java: 函数createRequest()是解析改造响应的主要函数。

void getUserName(String name, ApiCallback<ApiResponse<...>> callback) {
    createRequest(ApiService.getData(...), new ApiCallback<ApiResponse<?>>() {
        @Override
        public void onResult(ServiceResponse response) {
            callback.onResult(response);
        }
    });
}

private void createRequest(Call call, final ApiCallback<ApiResponse<?>> callback) {

    call.enqueue(new Callback() {
        @Override
        public void onResponse(Call call, retrofit2.Response response) {
                //heavy parsing
            }

            // return request results wrapped into ApiResponse object
            callback.onResult(new ApiResponse<>(...));
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            // return request results wrapped into ApiResponse object
            callback.onResult(...);
        }
    });
}

ApiCallbackApiResponse看起来像这样:

public interface ApiCallback<T> {
    void onResult(T response);
}

public class ApiResponse<T> {
    private T mResult;
    private ServiceError mError;
    ...
}

因此,在所有这些之前,我还使用了ApiClient.java的{​​{1}}:

ClientBase.createRequest()

如您所见,这非常非常糟糕。如何至少传输这些代码中的一部分,以确保public void getUserName(String name, ApiCallback<ApiResponse<..>> callback) { ClientBase.getUserName(secret, username, new ServiceCallback<ServiceResponse<RegistrationInvite>>() { @Override public void onResult(ServiceResponse<RegistrationInvite> response) { ... callback.onResult(response); } }); } 函数返回ApiClient.java对象? (我愿意为此创建另一个包装器类)

2 个答案:

答案 0 :(得分:1)

因此,通常,执行此操作的一种简单方法是从挂起函数返回suspendCancellableCoroutine,然后可以异步完成该函数。因此,就您而言,您可能会编写类似以下内容的

suspend fun getUserName(name: String): ApiResponse<...> {
    return suspendCancellableCoroutine { continuation ->
        createRequest(ApiService.getData(...), new ApiCallback<ApiResponse<...>>() {
            @Override
            public void onResult(ApiResponse<...> response) {
                continuation.resume(response)
            }
        });
    }
}

基本上,您返回与SettableFuture等效的内容,然后在成功或失败时将其标记为完成。如果您想通过异常处理来处理错误,则还有continueWithException(Throwable)

说:

由于您使用的是Retrofit,因此建议您仅添加retrofit2-kotlin-coroutines-adapter依赖项,以自然地为您添加此支持。

答案 1 :(得分:1)

  1. 您可以先在Kotlin中将ApiService.java转换为ApiService.kt

    interface ApiService {
        @GET("…")
        fun getData ( … : Call<Object>)
    }
    

    要将服务方法的返回类型从Call更改为Deferred,可以将上面的行修改为:

    fun getData ( … : Deferred<Object>)
    

  1. 要在Kotlin中设置解析更新请求的请求,可以在Kotlin中将其减少到几行。

    onCreate()override fun onCreate(savedInstanceState: Bundle?){的{​​{1}}中:

    MainActivity.kt

  1. 我不知道您的API的用例,但是如果您的API要返回一个长文本块,您还可以考虑在本文底部使用建议的方法。

    我介绍了一种将文本计算传递给val retrofit = Retrofit.Builder() // Below to add Retrofit 2 ‘s Kotlin Coroutine Adapter for Deferred .addCallAdapterFactory(CoroutineCallAdapterFactory()) .baseUrl(“YOUR_URL”) .build() val service = retrofit.create(ApiService::class.java) // Above using :: in Kotlin to create a class reference/ member reference val apiOneTextView = findViewById<TextView>(R.id.api_one_text_view) // to convert cast to findViewById with type parameters 的方法,根据Android文档,该方法是PrecomputedTextCompat.getTextFuture的助手,它返回将来与PrecomputedText一起使用。 / p>


  1. 再次在您的AppCompatTextView.setTextFuture(Future)内:

    MainActivity.kt
      

    推迟 + 等待 =暂停以等待结果,不阻止主UI线程


  1. 对于您的// Set up a Coroutine Scope GlobalScope.launch(Dispatchers.Main){ val time = measureTimeMillis{ // important to always check that you are on the right track try { initialiseApiTwo() initialiseApiThree() val createRequest = service.getData(your_params_here) apiOneTextView.text=”Your implementation here with api details using ${createRequest.await().your_params_here}” } catch (exception: IOException) { apiOneTextView.text=”Your network is not available.” } } println(“$time”) // to log onto the console, the total time taken in milliseconds taken to execute } initializeApiTwo(),您可以将initializeApiThree()用于private suspend funGlobalScope.launch(Dispatchers.Main){,其中:{{ 1}} //协程范围,并采用与讨论点2所述相同的方法。

  1. 当我使用上面概述的方法时,我的实现花费了 1863ms

    要进一步简化此方法(从顺序到并发),您可以用黄色添加以下修改,以使用 Async 移至 Concurrent (与本章讨论相同的代码) 4.),在我的案例中,它使时间缩短了 50% ,并且将持续时间缩短为 901ms

      

    根据Kotlin文档, 异步 会返回 递延 –一种轻量,无阻塞的未来表示以后会提供结果的承诺。您可以对延迟值使用 .await() 以获得最终结果。

    在您的val createRequestTwo = initializeApiTwo()内:

    private suspend fun initializeApiTwo() = withContext(Dispatchers.Default) {
      

    val apiTwoAsync = 异步 {initialiseApiTwo()}

         

    val apiThreeAsync = 异步 {initialiseApiThree()}

         

    val createRequest = 异步 {service.getData(your_params_here)}

         

    val dataResponse = createRequest.await()

         

    apiOneTextView.text =”使用$ {dataResponse.await()。your_params_here}在此处使用api详细信息的实现”

    MainActivity.kt

    要在本节中了解有关构成暂停功能的更多信息,可以访问Kotlin文档here提供的 Concurrent using Async 上的本节。


  1. 处理// Set up a Coroutine Scope GlobalScope.launch(Dispatchers.Main){ val time = measureTimeMillis{ // important to always check that you are on the right track try { 的建议方法:

     } catch (exception: IOException) {
    
           apiOneTextView.text=”Your network is not available.”
        }
      }
        println(“$time”) 
        // to log onto the console, the total time taken in milliseconds taken to execute 
    
    }
    

希望这会有所帮助。