使用改造向请求添加标头时如何避免等待主线程?

时间:2015-08-24 06:28:32

标签: java android multithreading retrofit

我用它来配置我的改造:

 RestAdapter restAdapter = new RestAdapter.Builder()

            //add headers to requests
            .setRequestInterceptor(getAuthenticatedRequestInterceptor())
            .setEndpoint(BASE_URL)
            .setConverter(new GsonConverter(getGson()))
            .build();

并且getAuthenticatedRequestInterceptor()方法会为请求添加标头:

public AccountRequestInterceptor getAuthenticatedRequestInterceptor() {
    AccountRequestInterceptor interceptor = new AccountRequestInterceptor();
    Map<String, String> headers = new HashMap<>();
     String accessToken = null;
    try {
        accessToken = TokenProvider.getInstance(mContext).getToken();
    } catch (InterruptedException e) {

    }
    headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
    interceptor.setHeader(headers);
    return interceptor;
}

getToken()方法是:

private synchronized string getToken() throws InterruptedException {
    if (!isRefreshing()) {
        //This is very important to call notify() on the same object that we call wait();
        final TokenProvider myInstance = this;
        setRefreshing(true);

        MyApplication.getRestClient().getAccountService().getRefreshedToken(mLoginData.getRefreshToken())
                .subscribe(new Observer<LoginResponse>() {
                    @Override
                    public void onCompleted() {
                        synchronized (myInstance) {
                            setRefreshing(false);
                            myInstance.notifyAll();
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        synchronized (myInstance) {
                            setRefreshing(false);
                            myInstance.notifyAll();
                        }
                    }

                    @Override
                    public void onNext(LoginResponse loginResponse) {
                        synchronized (myInstance) {
                            mLoginData = loginResponse;
                            mAccountProvider.saveLoginData(loginResponse);
                            myInstance.notifyAll();
                        }
                    }
                });
    }
    this.wait();
    return mLoginData.getToken();
}

TokenProvider.getInstance(mContext).getToken()在主线程上有一个wait()来获取异步方法的响应,我知道这是一件坏事,但我需要在这里等待响应采取从它获取令牌然后返回token.how我可以在一个单独的线程中执行此操作以避免在主线程上等待吗?

注意:

1 - 在任何改装请求之前调用它。

2 - 我read this我知道我可以在失败请求后刷新令牌,但出于商业原因,我想避免使用无效令牌。

3 - 我在我的MyApplication.getRestClient().getAccountService().login(loginRequest,callback...‌​)中调用Activity,然后在添加令牌后,在后台线程中发生了所有事情。所以我想使用我的令牌,不要阻止主线程。

更新:我将以下Interceptor添加到我的新OkHttp中:

public class RequestTokenInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
        Request newRequest;
        try {
            Log.d("addHeader", "Before");
            String token = TokenProvider.getInstance(mContext).getToken();
            if (token != null) {
                newRequest = request.newBuilder()
                        .addHeader("Bearer", token)
                        .build();
            } else {
                // I want to cancel the request or raise an exception to catch it in onError method
                // of retrofit callback.
            }
        } catch (InterruptedException e) {
            Log.d("addHeader", "Error");
            e.printStackTrace();
            return chain.proceed(request);
        }
        Log.d("addHeader", "after");
        return chain.proceed(newRequest);
    }
}

现在,如果令牌为空,我如何取消请求或引发异常以在onError方法中进行改进回调?

3 个答案:

答案 0 :(得分:2)

这是一个有点奇怪的问题,但让我试着帮助你。 :)

如您所知,使用响应拦截器进行改造后,您可以在失败的请求后刷新令牌。

让我们在请求之前尝试使用拦截器。

public class RequestTokenInterceptor implements Interceptor {
   @Override
   public Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      // Here where we'll try to refresh token.
      // with an retrofit call
      // After we succeed we'll proceed our request
      Response response = chain.proceed(request);
      return response;
   }
}

当你创建你的api时,创建一个新的HttpClient:

OkHttpClient client = new OkHttpClient();
client.interceptors().add(new RequestTokenInterceptor());

并将您的http客户端添加到您的适配器,如下所示:

.setClient(new OkClient(client))

如果这样做,在每次请求之前,您将首先尝试刷新令牌,然后将继续您的api请求。因此,在ui中,与正常的api呼叫没有区别。

编辑:

我也在编辑我的答案。如果你想在其他情况下返回错误,如果令牌为null,则在其他情况下你可以创建自定义响应:

private Response(Builder builder) {
    this.request = builder.request;
    this.protocol = builder.protocol;
    this.code = builder.code;
    this.message = builder.message;
    this.handshake = builder.handshake;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.networkResponse = builder.networkResponse;
    this.cacheResponse = builder.cacheResponse;
    this.priorResponse = builder.priorResponse;
  }

或者只是你可以返回一个空响应。如果您构建自定义响应并将代码设置为200(例如401或400+),则会在Retrofit的回调失败方法中收到该响应。你可以做任何你想做的事。

如果你返回null,你会得到一个RuntimeException,我想你仍然可以在你的回调失败方法中捕获响应。

在else中创建自己的响应后,您可以创建自定义回调并捕获空响应并转换您想要的自定义错误,如下所示:

public abstract class DefaultRequestCallback<T> implements Callback<T> {

    public abstract void failure(YourCustomException ex);

    public abstract void success(T responseBean);

    @Override
    public void success(T baseResponseBean, Response response) {
        if (response == null) {
            // Here we catch null response and transform it to our custom     Exception
            failure(new YourCustomException());
        }
        } else {
            success(baseResponseBean);
        }
    }

    @Override
    public void failure(RetrofitError error) {
        // Here's your failure method.
        // Also you can transform default retrofit errors to your customerrors
        YourCustomException ex = new YourCustomException();
        failure(ex);
    }
}

这可以帮到你的想法。

编辑2:

您可以构建一个新的响应,如下所示。 Retrofit的Response类中有一个构建器模式。你可以从那里查看。

Response response = new Response.Builder().setCode(401).setMessage("Error Message").build();

答案 1 :(得分:1)

您可以在AsyncTask doInBackground方法中执行所有长操作,而在onPre-和onPostExecute中,您可以在用户等待时显示/隐藏一些进度条

答案 2 :(得分:1)

好吧,我想如果你在主线程上调用你的getAuthenticatedRequestInterceptor(),然后调用getInstance(),我觉得你会创建一个Type TokenProvider的对象,因此当你在main中创建这个对象时线程你的object.wait()在主线程上运行,因此在后台线程上运行它可能修改你的getAuthenticatedRequestInterceptor()方法在新线程中执行以下行。

try {
    accessToken = TokenProvider.getInstance(mContext).getToken();
} catch (InterruptedException e) {

}
headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
interceptor.setHeader(headers);
return interceptor;

但是这会因为主线程将继续执行而通知你的RestAdapter有问题,因此我建议  你首先在一个新线程中调用getAuthenticatedRequestInterceptor()方法然后通知你的主线程来构建你的RestAdapter。这将释放你的主线程但是你使用的策略你必须等到你收到令牌才能进行任何调用。