如何在安卓上同步/限制某些异步http调用

4
我在我的应用程序中使用Android Async Http Library来进行异步的http请求。
现在我遇到了这样的情况。我的web api使用访问令牌和刷新令牌。在每个请求时,我都会检查访问令牌是否仍然有效。如果无效,则发出一个http post请求,使用刷新令牌获取新的访问令牌。
现在我注意到以下情况。
我的应用程序用户将手机处于非活动状态足够长的时间,以使访问令牌过期。当他们唤醒手机时,在我的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处理所有不同的线程等。

1
如果您同时发送请求1和2,则可能需要在此处进行一些更改,如果您收到401,则无法注销用户。如果您想保持这种方式,那么请在请求1终止时启动请求2。 如果您想同时发送两个请求以更快地完成,请不要在请求2返回401时注销用户,而只有在请求1返回401时才注销。 - Mostrapotski
2个回答

2

请求2还检查了访问令牌并确定其无效。

这是错误的。因为请求1可能已经发出了refreshAccessToken请求,所以无法通过查询服务器来确定访问令牌的状态。

因此,您需要一个组合操作getAccessToken(),它检查访问令牌,在需要时发出refreshAccessToken,并且在并行调用时只等待先前调用的getAccessToken()操作。

更新。 refreshAccessToken是充当门卫的类的一部分,并且仅在刷新访问令牌后才允许请求运行。如果令牌未刷新,则门卫会发送单个请求以刷新令牌。同时,输入请求保存在队列中。当令牌被刷新时,门卫允许保存的请求运行。


是的,完全正确。除此之外,我应该如何实现呢?我有一个函数用于获取刷新令牌和访问令牌,但是我如何在不阻塞主线程的情况下防止并行请求呢? - Zapnologica
你说Request 1和Request 2是并行运行的。我假设它们使用自己的线程(如果不是,那么你已经阻塞了主线程)。 - Alexei Kaigorodov
利用异步HTTP库,主线程构建并启动两个请求。但它们在由库管理的不同线程上执行。我的主线程只是构建请求。所以我必须阻塞请求2的执行,直到请求1返回。也就是说,我必须在主线程中阻塞它,以免告诉库去执行它。 - Zapnologica
如果您使用异步库,那么“请求1正在等待响应”是什么意思? - Alexei Kaigorodov

1

我通过身份验证找到了解决方案,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;
            }
        });

如果您要多次提供完全相同的答案,请解释您的代码应该做什么。 - emsimpson92

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接