Предыстория: Итак, у меня довольно большой проект с множеством API-функций. Я думаю о том, чтобы полностью перейти к сопрограммам, но так как они реализованы как Callback
а не Deferred
, я не могу использовать их эффективно. Например: я хотел бы сделать apiCallOne()
, apiCallTwo()
и apiCallThree()
async и вызвать .await()
чтобы дождаться завершения последнего запроса перед изменением пользовательского интерфейса.
Теперь проект структурирован так:
В самом низу (или вверху) находится 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(...);
}
});
}
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
возвращала Deferred
объекты? (Я готов создать для этого еще один класс-обертку)
Сначала вы можете конвертировать ApiService.java
в ApiService.kt
в Котлине:
interface ApiService {
@GET("…")
fun getData ( … : Call<Object>)
}
Чтобы изменить тип возврата методов обслуживания с Call
на Deferred
, вы можете изменить приведенную выше строку на:
fun getData ( … : Deferred<Object>)
Чтобы настроить запрос на разбор ответа о модификации в Kotlin, вы можете уменьшить его до нескольких строк в 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 будет возвращать длинный текстовый блок, вы также можете рассмотреть возможность использования предложенного подхода в нижней части этого поста.
Я включил подход к передаче текстовых вычислений в PrecomputedTextCompat.getTextFuture
, который, согласно документации Android, является помощником для PrecomputedText
который возвращает будущее, которое будет использоваться с 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
}
Отложено + Await= приостановить для ожидания результата, не блокирует основной поток пользовательского интерфейса
initializeApiTwo()
и initializeApiThree()
вы можете использовать для них private suspend fun
в режиме GlobalScope.launch(Dispatchers.Main){
, используя аналогичный GlobalScope.launch(Dispatchers.Main){
... & val createRequestTwo = initializeApiTwo()
, где: private suspend fun initializeApiTwo() = withContext(Dispatchers.Default) {
//область действия сопрограммы, следуя тому же подходу, который описан в пункте 2 обсуждения.Когда я использовал описанный выше метод, моя реализация заняла 1863 мс.
Чтобы еще больше упростить этот метод (от последовательного к параллельному), вы можете добавить следующие модификации в желтом цвете, чтобы перейти к Параллельному, используя Async (тот же код из пункта 4), что в моем случае дало улучшение в 50% времени и сокращение продолжительность до 901мс.
Согласно документации Kotlin, Async возвращает Deferred - легкое неблокирующее будущее, которое представляет собой обещание предоставить результат позже. Вы можете использовать .await() для отложенного значения, чтобы получить его конечный результат.
Внутри вашего 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 = "Ваша реализация здесь с деталями API, используя $ {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
}
Чтобы узнать больше о создании функций приостановки в этом разделе, вы можете посетить этот раздел по параллельному использованию Async, предоставленному документацией Kotlin здесь.
Предлагаемый подход для обработки PrecomputedTextCompat.getTextFuture
:
if (true) {
(apiOneTextView as AppCompatTextView).setTextFuture(
PrecomputedTextCompat.getTextFuture(
apiOneTextView.text,
TextViewCompat.getTextMetricsParams(apiOneTextView), null)
)
}
Надеюсь, это полезно.
В общем, простой способ сделать это - вернуть 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, которая изначально добавляет вам эту поддержку.