将回调地狱转换为延迟对象
Convert callback hell to deferred object
背景: 所以,我有一个相当大的项目,有很多 API 功能。我正在考虑完全转向协程,但由于它们是作为 Callback
而不是 Deferred
实现的,所以我无法有效地使用它们。例如:我想做 apiCallOne()
、apiCallTwo()
和 apiCallThree()
异步并调用 .await()
等到最后一个请求完成后再更改 UI。
现在项目结构如下:
最底部(或顶部)是 ApiService.java
:
interface ApiService {
@GET("...")
Call<Object> getData();
...
}
然后我有一个ClientBase.java
:
function 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(...);
}
});
}
ApiCallback
和 ApiResponse
看起来像这样:
public interface ApiCallback<T> {
void onResult(T response);
}
public class ApiResponse<T> {
private T mResult;
private ServiceError mError;
...
}
所以,在所有这些之前,我还有 ApiClient.java
,它使用 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
函数 return Deferred
对象? (我愿意为此创建另一个包装器 class)
所以一般来说,一个简单的方法是从挂起函数 return 一个 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)
}
});
}
}
基本上 return 等同于 SettableFuture
,然后在成功或失败时将其标记为完成。如果你想通过异常处理来处理错误,还有continueWithException(Throwable)
。
也就是说:
由于您使用的是 Retrofit,我建议您只添加 retrofit2-kotlin-coroutines-adapter 依赖项,这会为您提供本机支持。
你可以先在Kotlin中将ApiService.java
转换成ApiService.kt
:
interface ApiService {
@GET("…")
fun getData ( … : Call<Object>)
}
要将服务方法的 return 类型从 Call
更改为 Deferred
,您可以将以上行修改为:
fun getData ( … : Deferred<Object>)
在Kotlin中设置解析retrofit响应的请求,在Kotlin中可以减少到几行。
在你的 onCreate()
在 override fun onCreate(savedInstanceState: Bundle?){
在 MainActivity.kt
:
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
我不知道你的API的用例,但如果你的API要return一个长文本块,你也可以考虑使用此 post 底部的建议方法。
我包含了一种将文本计算传递给 PrecomputedTextCompat.getTextFuture
的方法,根据 Android 文档,它是 PrecomputedText
的助手,return 是未来与 AppCompatTextView.setTextFuture(Future)
.
一起使用
同样,在您的 MainActivity.kt
中:
// 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
}
Deferred + Await = to suspend to await result, does not block main UI thread
- 对于您的
initializeApiTwo()
& initializeApiThree()
,您可以对它们使用 private suspend fun
,使用类似的 GlobalScope.launch(Dispatchers.Main){
... & val createRequestTwo = initializeApiTwo()
,其中: private suspend fun initializeApiTwo() = withContext(Dispatchers.Default) {
// 协程范围,并遵循讨论点 2 中概述的相同方法。
当我使用上述方法时,我的实现花费了 1863ms。
为了进一步简化此方法(从顺序到并发),您可以添加以下黄色修改,以使用 Async[=103 移动到 Concurrent =](来自点讨论 4. 的相同代码),就我而言,它提供了 50% 的时间改进 并将持续时间缩短到 901ms。
According to Kotlin documentation, Async returns a Deferred – a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result.
在你的 MainActivity.kt
里面:
// Set up a Coroutine Scope
GlobalScope.launch(Dispatchers.Main){
val time = measureTimeMillis{
// important to always check that you are on the right track
try {
val apiTwoAsync = async { initialiseApiTwo() }
val apiThreeAsync = async { initialiseApiThree() }
val createRequest = async { service.getData(your_params_here) }
val dataResponse = createRequest.await()
apiOneTextView.text=”Your implementation here with api details using ${dataResponse.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
}
要在本节中了解有关组合挂起函数的更多信息,您可以访问 Kotlin 文档 here.[=40= 提供的 使用 Async 并发的本节]
建议的处理方法PrecomputedTextCompat.getTextFuture
:
if (true) {
(apiOneTextView as AppCompatTextView).setTextFuture(
PrecomputedTextCompat.getTextFuture(
apiOneTextView.text,
TextViewCompat.getTextMetricsParams(apiOneTextView), null)
)
}
希望这对您有所帮助。
背景: 所以,我有一个相当大的项目,有很多 API 功能。我正在考虑完全转向协程,但由于它们是作为 Callback
而不是 Deferred
实现的,所以我无法有效地使用它们。例如:我想做 apiCallOne()
、apiCallTwo()
和 apiCallThree()
异步并调用 .await()
等到最后一个请求完成后再更改 UI。
现在项目结构如下:
最底部(或顶部)是 ApiService.java
:
interface ApiService {
@GET("...")
Call<Object> getData();
...
}
然后我有一个ClientBase.java
:
function 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(...);
}
});
}
ApiCallback
和 ApiResponse
看起来像这样:
public interface ApiCallback<T> {
void onResult(T response);
}
public class ApiResponse<T> {
private T mResult;
private ServiceError mError;
...
}
所以,在所有这些之前,我还有 ApiClient.java
,它使用 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
函数 return Deferred
对象? (我愿意为此创建另一个包装器 class)
所以一般来说,一个简单的方法是从挂起函数 return 一个 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)
}
});
}
}
基本上 return 等同于 SettableFuture
,然后在成功或失败时将其标记为完成。如果你想通过异常处理来处理错误,还有continueWithException(Throwable)
。
也就是说:
由于您使用的是 Retrofit,我建议您只添加 retrofit2-kotlin-coroutines-adapter 依赖项,这会为您提供本机支持。
你可以先在Kotlin中将
ApiService.java
转换成ApiService.kt
:interface ApiService { @GET("…") fun getData ( … : Call<Object>) }
要将服务方法的 return 类型从
Call
更改为Deferred
,您可以将以上行修改为:fun getData ( … : Deferred<Object>)
在Kotlin中设置解析retrofit响应的请求,在Kotlin中可以减少到几行。
在你的
onCreate()
在override fun onCreate(savedInstanceState: Bundle?){
在MainActivity.kt
: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
我不知道你的API的用例,但如果你的API要return一个长文本块,你也可以考虑使用此 post 底部的建议方法。
我包含了一种将文本计算传递给
PrecomputedTextCompat.getTextFuture
的方法,根据 Android 文档,它是PrecomputedText
的助手,return 是未来与AppCompatTextView.setTextFuture(Future)
. 一起使用
同样,在您的
MainActivity.kt
中:// 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 }
Deferred + Await = to suspend to await result, does not block main UI thread
- 对于您的
initializeApiTwo()
&initializeApiThree()
,您可以对它们使用private suspend fun
,使用类似的GlobalScope.launch(Dispatchers.Main){
... &val createRequestTwo = initializeApiTwo()
,其中:private suspend fun initializeApiTwo() = withContext(Dispatchers.Default) {
// 协程范围,并遵循讨论点 2 中概述的相同方法。
当我使用上述方法时,我的实现花费了 1863ms。
为了进一步简化此方法(从顺序到并发),您可以添加以下黄色修改,以使用 Async[=103 移动到 Concurrent =](来自点讨论 4. 的相同代码),就我而言,它提供了 50% 的时间改进 并将持续时间缩短到 901ms。
According to Kotlin documentation, Async returns a Deferred – a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result.
在你的
MainActivity.kt
里面:// Set up a Coroutine Scope GlobalScope.launch(Dispatchers.Main){ val time = measureTimeMillis{ // important to always check that you are on the right track try {
val apiTwoAsync = async { initialiseApiTwo() }
val apiThreeAsync = async { initialiseApiThree() }
val createRequest = async { service.getData(your_params_here) }
val dataResponse = createRequest.await()
apiOneTextView.text=”Your implementation here with api details using ${dataResponse.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 }
要在本节中了解有关组合挂起函数的更多信息,您可以访问 Kotlin 文档 here.[=40= 提供的 使用 Async 并发的本节]
建议的处理方法
PrecomputedTextCompat.getTextFuture
:if (true) { (apiOneTextView as AppCompatTextView).setTextFuture( PrecomputedTextCompat.getTextFuture( apiOneTextView.text, TextViewCompat.getTextMetricsParams(apiOneTextView), null) ) }
希望这对您有所帮助。