自定义CallAdapter for Retrofit 2和Android上的线程问题

时间:2017-05-14 08:39:36

标签: java android multithreading retrofit2

在Retrofit2中,当我使用自定义CallAdapter进行Retrofit在samples/ErrorHandlingAdapter.java上提供的错误处理时,回调方法在后台线程而不是主线程上执行,这与执行的默认CallAdapter(Call)不同在主线程上。我通过在

上运行Thread.currentThread()。getName()来确保这一点

这对我来说是个大问题。每次我想用ui-thread做某事时,我都不想使用runOnUiThread方法。

上面提到的ErrorHandlingAdapter的源代码:

/*
 * 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);
    }
  }
}

在Android中,我在进行任何调用之前将ErrorHandlingAdapter添加到Retrofit中:

// 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 个答案:

答案 0 :(得分:2)

你将不得不在另一个线程上处理它,这只是异步库的本质。如果你只有一个错误处理程序,那么进行所需的线程调用并不是太大的负担。如果您有多个,那么您可以进行轻量级委托实现和重用。

您的另一个选择是深入使用rxjava实现,该实现提供精确的线程控制。

答案 1 :(得分:2)

要在主线程上获得回调,您应该使用MyCallAdapter中提供的callbackExecutor

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));
                        }
                    }
                });

示例写了一个TODO:

  

// TODO如果'callbackExecutor'不为null,则应执行'callback'方法                       //通过提交Runnable在该执行程序上。这留给读者练习。

它是一个Retrofit回调执行程序,在android中的UI线程上运行。

See here&amp; here