En Retrofit2, cuando uso un CallAdapter personalizado para el manejo de errores que Retrofit proporciona en samples / ErrorHandlingAdapter.java, los métodos de devolución de llamada ejecutados en un subproceso en segundo plano en lugar del subproceso principal, a diferencia del CallAdapter predeterminado (Llamada), que se ejecuta en el principal hilo. Me aseguré de eso ejecutando Thread.currentThread (). GetName () en ambos

Este es un gran problema para mí. No quiero usar el método runOnUiThread cada vez que quiero hacer algo en ui-thread.

El código fuente de ErrorHandlingAdapter mencionado anteriormente:

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.retrofit;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.Executor;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Callback;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.GET;

/**
 * A sample showing a custom {@link CallAdapter} which adapts the built-in {@link Call} to a custom
 * version whose callback has more granular methods.
 */
public final class ErrorHandlingAdapter {
  /** A callback which offers granular callbacks for various conditions. */
  interface MyCallback<T> {
    /** Called for [200, 300) responses. */
    void success(Response<T> response);
    /** Called for 401 responses. */
    void unauthenticated(Response<?> response);
    /** Called for [400, 500) responses, except 401. */
    void clientError(Response<?> response);
    /** Called for [500, 600) response. */
    void serverError(Response<?> response);
    /** Called for network errors while making the call. */
    void networkError(IOException e);
    /** Called for unexpected errors while making the call. */
    void unexpectedError(Throwable t);
  }

  interface MyCall<T> {
    void cancel();
    void enqueue(MyCallback<T> callback);
    MyCall<T> clone();

    // Left as an exercise for the reader...
    // TODO MyResponse<T> execute() throws MyHttpException;
  }

  public static class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
    @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit) {
      if (getRawType(returnType) != MyCall.class) {
        return null;
      }
      if (!(returnType instanceof ParameterizedType)) {
        throw new IllegalStateException(
            "MyCall must have generic type (e.g., MyCall<ResponseBody>)");
      }
      Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
      Executor callbackExecutor = retrofit.callbackExecutor();
      return new ErrorHandlingCallAdapter<>(responseType, callbackExecutor);
    }

    private static final class ErrorHandlingCallAdapter<R> implements CallAdapter<R, MyCall<R>> {
      private final Type responseType;
      private final Executor callbackExecutor;

      ErrorHandlingCallAdapter(Type responseType, Executor callbackExecutor) {
        this.responseType = responseType;
        this.callbackExecutor = callbackExecutor;
      }

      @Override public Type responseType() {
        return responseType;
      }

      @Override public MyCall<R> adapt(Call<R> call) {
        return new MyCallAdapter<>(call, callbackExecutor);
      }
    }
  }

  /** Adapts a {@link Call} to {@link MyCall}. */
  static class MyCallAdapter<T> implements MyCall<T> {
    private final Call<T> call;
    private final Executor callbackExecutor;

    MyCallAdapter(Call<T> call, Executor callbackExecutor) {
      this.call = call;
      this.callbackExecutor = callbackExecutor;
    }

    @Override public void cancel() {
      call.cancel();
    }

    @Override public void enqueue(final MyCallback<T> callback) {
      call.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, Response<T> response) {
          // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
          // on that executor by submitting a Runnable. This is left as an exercise for the reader.

          int code = response.code();
          if (code >= 200 && code < 300) {
            callback.success(response);
          } else if (code == 401) {
            callback.unauthenticated(response);
          } else if (code >= 400 && code < 500) {
            callback.clientError(response);
          } else if (code >= 500 && code < 600) {
            callback.serverError(response);
          } else {
            callback.unexpectedError(new RuntimeException("Unexpected response " + response));
          }
        }

        @Override public void onFailure(Call<T> call, Throwable t) {
          // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
          // on that executor by submitting a Runnable. This is left as an exercise for the reader.

          if (t instanceof IOException) {
            callback.networkError((IOException) t);
          } else {
            callback.unexpectedError(t);
          }
        }
      });
    }

    @Override public MyCall<T> clone() {
      return new MyCallAdapter<>(call.clone(), callbackExecutor);
    }
  }
}

En Android, agregué el ErrorHandlingAdapter a Retrofit antes de hacer cualquier llamada:

// Initializing retrofit
BooleanTypeAdapter typeAdapter = new BooleanTypeAdapter();
    gson = new GsonBuilder().setLenient().registerTypeAdapter(boolean.class, typeAdapter).create();
    apiService = new Retrofit.Builder().baseUrl(BASE_API_URL)
        .addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
        .create(ApiService.class);
2
Mustapha Hadid 14 may. 2017 a las 11:39

2 respuestas

La mejor respuesta

Para obtener la devolución de llamada en el hilo principal, debe usar el callbackExecutor provisto en MyCallAdapter.

callbackExecutor.execute(new Runnable() {
                    @Override
                    public void run() {

                        int code = response.code();
                        if (code >= 200 && code < 300) {
                            callback.success(response);
                        } else if (code == 401) {
                            callback.unauthenticated(response);
                        } else if (code >= 400 && code < 500) {
                            callback.clientError(response);
                        } else if (code >= 500 && code < 600) {
                            callback.serverError(response);
                        } else {
                            callback.unexpectedError(new RuntimeException("Unexpected response " + response));
                        }
                    }
                });

La muestra tiene un TODO escrito:

// TODO si 'callbackExecutor' no es nulo, los métodos de 'devolución de llamada' deben ejecutarse // en ese ejecutor enviando un Runnable. Esto se deja como un ejercicio para el lector.

Es un ejecutor de devolución de llamada Retrofit que se ejecuta en la interfaz de usuario en Android.

Ver aquí & aquí

2
farhan patel 2 ago. 2017 a las 14:19

Tendrás que lidiar con que esté en el otro hilo, esa es solo la naturaleza de las bibliotecas asincrónicas. Si solo tiene el controlador de un error, no es una carga demasiado grande para hacer las llamadas de subprocesamiento necesarias. Si tiene múltiples, entonces podría hacer una implementación de delegación ligera y reutilizarla.

Su otra alternativa es sumergirse en el uso de una implementación de rxjava que proporciona un control preciso de subprocesos.

2
pforhan 15 may. 2017 a las 19:14