Лучшая практика для внедрения обратного вызова Retrofit для воссозданной деятельности?

Я перехожу к Retrofit и пытаюсь понять правильную архитектуру для использования с асинхронными обратными вызовами.

Например, у меня есть интерфейс:

interface RESTService{
    @GET("/api/getusername")
    void getUserName(@Query("user_id") String userId, 
                     Callback<Response> callback);
}

И я запускаю это из основного действия:

RestAdapter restAdapter = new RestAdapter.Builder()
        .setServer("WEBSITE_URL")     
        .build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});

Затем пользователь поворачивает устройство, и у меня есть новая активность... Что здесь произошло? Как я могу получить ответ на новую активность (я предполагаю, что вызов api в фоновом режиме будет выполняться дольше, чем первый жизненный цикл). Может быть, я должен использовать статический экземпляр обратного вызова или что? Пожалуйста, покажите мне правильный путь...

Ответ 2

Для потенциальных длительных вызовов сервера я использую AsyncTaskLoader. Для меня главным преимуществом Loaders является обработка жизненного цикла активности. onLoadFinished вызывается только в том случае, если ваша активность видна пользователю. Погрузчики также разделяются между изменением активности/фрагмента и ориентации.

Итак, я создал ApiLoader, который использует модифицированные синхронные вызовы в loadInBackground.

abstract public class ApiLoader<Type> extends AsyncTaskLoader<ApiResponse<Type>> {

    protected ApiService service;
    protected ApiResponse<Type> response;

    public ApiLoader(Context context) {
        super(context);
        Vibes app = (Vibes) context.getApplicationContext();
        service = app.getApiService();
    }

    @Override
    public ApiResponse<Type> loadInBackground() {
        ApiResponse<Type> localResponse = new ApiResponse<Type>();

        try {
            localResponse.setResult(callServerInBackground(service));
        } catch(Exception e) {
            localResponse.setError(e);
        }

        response = localResponse;
        return response;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if(response != null) {
            deliverResult(response);
        }

        if(takeContentChanged() || response == null) {
            forceLoad();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();
        response = null;
    }


    abstract protected Type callServerInBackground(SecondLevelApiService api) throws Exception;

}

В своей деятельности вы запускаете этот загрузчик следующим образом:

getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<ApiResponse<DAO>>() {
        @Override
        public Loader<ApiResponse<DAO>> onCreateLoader(int id, Bundle args) {
            spbProgress.setVisibility(View.VISIBLE);

            return new ApiLoader<DAO>(getApplicationContext()) {
                @Override
                protected DAO callServerInBackground(ApiService api) throws Exception {
                    return api.requestDAO();
                }
            };
        }

        @Override
        public void onLoadFinished(Loader<ApiResponse<DAO>> loader, ApiResponse<DAO> data) {
            if (!data.hasError()) {
                DAO dao = data.getResult();
                //handle data
            } else {
                Exception error = data.getError();
                //handle error
            }
        }

        @Override
        public void onLoaderReset(Loader<ApiResponse<DAO>> loader) {}
    });

Если вы хотите запросить данные несколько раз, используйте restartLoader вместо initLoader.

Ответ 3

В моих приложениях для Android я использовал своего рода MVP (ModelViewPresenter). Для запроса "Модификация" я сделал операцию "Назначение" соответствующей ей Presenter, которая, в свою очередь, делает запрос на повторную установку, и в качестве параметра я отправляю обратный вызов с прикрепленным к нему пользовательским прослушивателем (реализованным ведущим). Когда обратные вызовы достигают методов onSuccess или onFailure, я вызываю соответствующие методы Listener, вызывающие Presenter, а затем методы Activity: P

Теперь, если экран повернут, когда моя активность будет воссоздана, он присоединяется к Ведущему. Это делается с использованием пользовательской реализации приложения для Android, где он хранит экземпляр докладчиков и использует карту для восстановления правильного презентатора в соответствии с классом Activity.

Я не знаю, если это лучший способ, возможно, ответ @pareshgoel лучше, но он работал у меня: D

Примеры:

public abstract interface RequestListener<T> {

    void onSuccess(T response);

    void onFailure(RetrofitError error);
}

...

public class RequestCallback<T> implements Callback<T> {

    protected RequestListener<T> listener;

    public RequestCallback(RequestListener<T> listener){
        this.listener = listener;
    }

    @Override
    public void failure(RetrofitError arg0){
        this.listener.onFailure(arg0);
    }

    @Override
    public void success(T arg0, Response arg1){
        this.listener.onSuccess(arg0);
    }

}

Реализовать прослушиватель где-нибудь на презентаторе, а в методах overrode вызвать метод презентатора, который сделает вызов Activity. И звоните туда, где вы хотите, чтобы ведущий начал все: P

Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));

Надеюсь, это поможет вам.

Ответ 4

Во-первых, ваша активность протекает здесь, потому что эта строка:   api.getUserName(userId, new Callback {...}) создает анонимный класс обратного вызова, который содержит ссылки на MainActivity. Когда устройство поворачивается до вызова Callback, тогда MainActivity не будет собираться мусором. В зависимости от того, что вы делаете в Callback.call(), ваше приложение может выполнять поведение undefined.

Общая идея для обработки таких сценариев:

  • Никогда не создавайте нестатический внутренний класс (или анонимный класс, как указано в проблеме).
  • Вместо этого создайте статический класс, содержащий значение WeakReference < > для Activity/Fragment.

Вышеуказанное просто предотвращает утечки. Он по-прежнему не поможет вам вернуть запрос "Восстановить" в свою активность.

Теперь, чтобы вернуть результаты к вашему компоненту (активность в вашем случае) даже после изменения конфигурации, вы можете захотеть использовать безгласный сохраненный фрагмент, прикрепленный к вашей активности, что делает звонок для переоснащения. Подробнее о Retained фрагменте - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

Общая идея заключается в том, что Fragment автоматически присоединяется к Activity при изменении конфигурации.

Ответ 5

Я настоятельно рекомендую вам посмотреть это видео, указанное в Google I/O.

В нем говорится о том, как создавать запросы REST, делегируя их службе (которая почти никогда не убивается). Когда запрос будет завершен, он сразу же будет сохранен в встроенной базе данных Android, поэтому данные будут немедленно доступны, когда ваша активность будет готова.

При таком подходе вам никогда не придется беспокоиться о жизненном цикле активности, и ваши запросы обрабатываются гораздо более развязанным способом.

В видеоролике конкретно не говорится о дооснащении, но вы можете легко адаптировать модификацию для этой парадигмы.

Ответ 6

Использование Retrofit2 для обработки изменения ориентации. Меня попросили об этом на собеседовании и он был отклонен за то, что не знал об этом в то время, но здесь он сейчас.

public class TestActivity extends AppCompatActivity {
Call<Object> mCall;
@Override
    public void onDestroy() {
        super.onDestroy();
        if (mCall != null) {
            if (mCall.isExecuted()) {
                //An attempt will be made to cancel in-flight calls, and
                // if the call has not yet been executed it never will be.
                mCall.cancel();
            }
        }
    }
    }

Ответ 7

Используйте Robospice

Все компоненты вашего приложения, которые требуют данных, регистрируются в службе spice. Служба позаботится о том, чтобы отправить ваш запрос на сервер (с помощью модернизации, если хотите). Когда ответ возвращается, все зарегистрированные компоненты получают уведомление. Если один из них больше не доступен (например, деятельность, которая была вызвана из-за вращения), она просто не была уведомлена.

Преимущества: один запрос, который не затерялся, независимо от того, вращаете ли вы свое устройство, открываете новые диалоги/фрагменты и т.д.

Ответ 9

Это типичное ограничение такого рода решений, по умолчанию Android создает новое действие, когда пользователь поворачивает устройство.

Однако в этом случае есть простой способ, настройте прослушиватель ориентации. Сделав это, Android не будет создавать новую активность.

    <activity
        ...
        android:configChanges="orientation" />

Другой способ исправить это - с помощью модификации с другими фреймворками, такими как Robospice.