Преобразовать ад обратного вызова в отложенный объект - PullRequest
2 голосов
/ 11 апреля 2019

Фон : Итак, у меня довольно большой проект с большим количеством 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 объектов? (Я готов создать для этого еще один класс-обертку)

Ответы [ 2 ]

1 голос
/ 13 апреля 2019
  1. Вы можете сначала преобразовать ApiService.java в ApiService.kt в Kotlin:

    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 

}

Отложено + Ожидание = приостановить до ожидания результата, неблокировать основной поток пользовательского интерфейса


Для ваших initializeApiTwo() & initializeApiThree() вы можете использовать для них private suspend fun, используя аналогичные GlobalScope.launch(Dispatchers.Main){ ... & val createRequestTwo = initializeApiTwo(), где: private suspend fun initializeApiTwo() = withContext(Dispatchers.Default) { // объем сопрограммы и следуя тому жеподход, изложенный в пункте 2 обсуждения.

Когда я использовал описанный выше метод, моя реализация заняла 1863ms .

Для дальнейшей оптимизации этого метода (от последовательного к параллельному), вы можете добавить следующие модификации желтым цветом, чтобы перейти к Одновременный , используя Асинхронный (тот же код из точкиобсуждение 4.), которое в моем случае дало 50% улучшения времени и длительность сокращения до 901мс .

Согласно документации Kotlin, Async возвращает Отложено - легкое неблокирующее будущее, которое представляет собой обещание предоставить результат позже.Вы можете использовать .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 , предоставленнойДокументация Котлина здесь .


Предлагаемый подход для обработки PrecomputedTextCompat.getTextFuture:

if (true) {
   (apiOneTextView as AppCompatTextView).setTextFuture(
      PrecomputedTextCompat.getTextFuture(
          apiOneTextView.text,
          TextViewCompat.getTextMetricsParams(apiOneTextView), null)
 )
}

Надеюсь, что это полезно.

1 голос
/ 11 апреля 2019

Итак, в общем, простой способ сделать это - вернуть 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 , которая изначально добавляет вам эту поддержку.

...