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

Posted

技术标签:

【中文标题】使用改造向请求添加标头时如何避免在主线程上等待?【英文标题】:How to avoid waiting on main thread when adding headers to requests using retrofit? 【发布时间】:2015-11-17 11:42:45 【问题描述】:

我用它来配置我的改造:

 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() 以从异步方法获取响应,我知道这是一件坏事,但我需要在这里等待响应从中获取令牌并然后返回令牌。如何在单独的线程中执行此操作以避免在主线程上等待?

注意:

1 - 在任何带有改造的请求之前调用它。

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

3 - 我在Activity 中调用MyApplication.getRestClient().getAccountService().login(loginRequest,callback...‌​),在添加令牌之前,一切都发生在后台线程中。所以我想使用我的令牌并且不要阻塞主线程。

更新:我在新的 OkHttp 中添加了以下 Interceptor

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

如果 token 为 null,我现在如何取消请求或引发异常以在改造回调的 onError 方法中捕获它?

【问题讨论】:

不要在主线程上发送请求? 我在我的活动中使用 MyApplication.getRestClient().getAccountService().login(loginRequest,callback...) 并且在添加令牌之前,一切都发生在后台线程中。 @immibis 说了什么,还有***.com/questions/22490057/… 【参考方案1】:

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

【讨论】:

像瘟疫一样避免 AsyncTask。而是使用例如 IntentServices。这是一个为什么 AsyncTask 不好的例子:simonvt.net/2014/04/17/asynctask-is-bad-and-you-should-feel-bad 是的,@dazito 是对的,这让一切变得更加困难。【参考方案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 调用没有区别。

编辑:

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

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 中创建自己的响应后,您可以创建自定义回调并捕获您的 null 响应并按照您想要的方式转换您的自定义错误,如下所示:

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

【讨论】:

这是应用拦截器还是网络拦截器? github.com/square/okhttp/wiki/Interceptors 这是一个应用程序拦截器。 好的,看起来它正在工作,但是为什么我的拦截器在主线程中运行,而这个拦截器在改造定义的线程中? 我需要更多的东西:如果令牌为空,我需要取消请求或引发自定义异常,然后再将其添加到拦截方法中的请求。我该怎么做? 我不确定,但在我的示例中,我试图解释。在调用 chain.proceed(request) 之前,您可以进行检查,如果它为空,则不继续请求。只需使用应用程序拦截器,您就可以在网络请求之前或之后向网络请求添加一个代码块。您可以做任何您想做的事情。【参考方案3】:

好的,我想如果你在主线程上调用你的 getAuthenticatedRequestInterceptor(),然后调用 getInstance(),我觉得你会在其中创建一个 TokenProvider 类型的对象,因此当你在主线程中创建这个对象时线程您的 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。这将释放您的主线程,但是根据您使用的策略,您必须等到收到令牌才能进行任何调用。

【讨论】:

我不想这样做,因为我将不得不改变调用我迄今为止在我的应用程序中提出的每个请求的方式。 这只会改变你创建的 RestAdapter。尽管如此,更重要的是我试图解释为什么你的对象在主线程上等待。

以上是关于使用改造向请求添加标头时如何避免在主线程上等待?的主要内容,如果未能解决你的问题,请参考以下文章

GCD - 如何在主线程上等待在主队列上执行的异步回调

如何在主线程上安全地使用[NSTask waitUntilExit]?

Redis学习笔记——异步机制:如何避免单线程模型的阻塞?

Redis学习笔记——异步机制:如何避免单线程模型的阻塞?

改造无法将新令牌设置为请求标头

在主线程上处理大型全局对象时如何不阻止来自工作线程的主 UI 线程