如何在android中同步/限制某些异步http调用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在android中同步/限制某些异步http调用相关的知识,希望对你有一定的参考价值。

我在我的应用程序中使用android Async Http Library来生成异步http请求。

我已经在我的Android应用程序中遇到以下情况发生的情况。我的web api使用访问令牌和刷新令牌。在我发出的每个请求中,我都会检查访问令牌是否仍然有效。如果不是我,那么发出一个http帖子来获取使用刷新令牌的新访问令牌。

现在我注意到以下情况。

我的应用程序的用户使他们的手机处于非活动状态足够的时间,以使访问令牌过期。当他们叫醒电话时。在我的onResume()函数中,我启动了两个单独的http请求。

  1. 请求1检查访问令牌并确定其无效。然后它发出refreshAccessToken请求。
  2. 当请求1正在等待响应时,请求2还检查访问令牌并确定其无效。它还发出了refreshAccessToken请求。
  3. 请求1成功返回并更新访问令牌并刷新令牌值。
  4. 请求2,然后从api获取401响应,因为它提供的刷新令牌已被使用。然后我的应用程序认为refreshToken存在错误并将用户注销。

这显然是不正确的,我想避免它。我在同一时间,在refreshAccessToken onFailed()方法中进行了双重检查。查看accessToken是否可能再次有效。然而,这是低效的,因为我仍然通过无线方式处理两个请求,并且我的API必须处理失败的刷新尝试。

问:现在我的问题是我无法使用任何锁或同步,因为你无法阻止android中的主UI线程。 Android Async Http Library处理所有不同的线程等。

答案

Request 2 also checks the access token and determines its not valid.

这是错的。由于请求1可能已经发出了refreshAccessToken请求,因此无法通过咨询服务器来确定访问令牌的状态。

所以你需要一个组合操作getAccessToken()来检查访问令牌,在需要时发出refreshAccessToken,并且当并行调用时,只需要等待之前调用的getAccessToken()操作。

UPDATE。 refreshAccessToken是作为网守的类的一部分,只有在刷新访问令牌时才允许请求运行。如果不刷新令牌,则网守发送单个请求以刷新令牌。同时,输入请求保存在队列中。令牌刷新后,网守允许保存的请求运行。

另一答案

我找到了带验证器的解决方案,id是请求的编号,仅用于识别。评论是西班牙语

 private final static Lock locks = new ReentrantLock();

httpClient.authenticator(new Authenticator() {
            @Override
            public Request authenticate(@NonNull Route route,@NonNull Response response) throws IOException {

                Log.e("Error" , "Se encontro un 401 no autorizado y soy el numero : " + id);

                //Obteniendo token de DB
                SharedPreferences prefs = mContext.getSharedPreferences(
                        BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

                String token_db = prefs.getString("refresh_token","");

                //Comparando tokens
                if(mToken.getRefreshToken().equals(token_db)){

                    locks.lock(); 

                    try{
                        //Obteniendo token de DB
                         prefs = mContext.getSharedPreferences(
                                BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

                        String token_db2 = prefs.getString("refresh_token","");
                        //Comparando tokens
                        if(mToken.getRefreshToken().equals(token_db2)){

                            //Refresh token
                            APIClient tokenClient = createService(APIClient.class);
                            Call<AccessToken> call = tokenClient.getRefreshAccessToken(API_OAUTH_CLIENTID,API_OAUTH_CLIENTSECRET, "refresh_token", mToken.getRefreshToken());
                            retrofit2.Response<AccessToken> res = call.execute();
                            AccessToken newToken = res.body();
                            // do we have an access token to refresh?
                            if(newToken!=null && res.isSuccessful()){
                                String refreshToken = newToken.getRefreshToken();

                                    Log.e("Entra", "Token actualizado y soy el numero :  " + id + " : " + refreshToken);

                                    prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
                                    prefs.edit().putBoolean("log_in", true).apply();
                                    prefs.edit().putString("access_token", newToken.getAccessToken()).apply();
                                    prefs.edit().putString("refresh_token", refreshToken).apply();
                                    prefs.edit().putString("token_type", newToken.getTokenType()).apply();

                                    locks.unlock();

                                    return response.request().newBuilder()
                                            .header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
                                            .build();

                             }else{
                                //Dirigir a login
                                Log.e("redirigir", "DIRIGIENDO LOGOUT");

                                locks.unlock();
                                return null;
                            }

                        }else{
                            //Ya se actualizo tokens

                            Log.e("Entra", "El token se actualizo anteriormente, y soy el no : " + id );

                            prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

                            String type = prefs.getString("token_type","");
                            String access = prefs.getString("access_token","");

                            locks.unlock();

                            return response.request().newBuilder()
                                    .header("Authorization", type + " " + access)
                                    .build();
                        }

                    }catch (Exception e){
                        locks.unlock();
                        e.printStackTrace();
                        return null;
                    }


                }
                return null;
            }
        });

以上是关于如何在android中同步/限制某些异步http调用的主要内容,如果未能解决你的问题,请参考以下文章

如何理解阻塞和非阻塞同步和异步

如何理解阻塞和非阻塞同步和异步

从 Android 22 开始,如何同步检索某些传输类型的网络状态?

同步和异步的概念

Java 多线程 同步和异步

如何识别回调是同步执行还是异步执行? [复制]