登录时从服务器获取令牌 – >一小时后,此令牌过期 – >当它到期时,我们会发现API调用失败,所以我们应该(在重试时) – >在该呼叫失败时获取新令牌,然后 – >重试原始呼叫.
我已经实现了这个,并且令牌成功返回,但是因为我认为我对Volley RequestQueue做错了,原始请求在新的有效令牌能够被使用之前使用了所有它的重试.请参阅以下代码:
public class GeneralAPICall extends Request<JSONObject> { public static String LOG_TAG = GeneralAPICall.class.getSimpleName(); SessionManager sessionManager; //instance of sessionManager needed to get user's credentials private Response.Listener<JSONObject> listener; //the response listener used to deliver the response private Map<String,String> headers = new HashMap<>(); //the headers used to authenticate private Map<String,String> params; //the params to pass with API call,can be null public GeneralAPICall(int method,String url,Map<String,String> params,Context context,Response.Listener<JSONObject> responseListener,Response.ErrorListener errorListener) { super(method,url,errorListener); sessionManager = new SessionManager(context); //instantiate HashMap<String,String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication this.listener = responseListener; this.params = params; //encode the user's username and token String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + Constants.APIConstants.Characters.CHAR_COLON + credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(),Base64.NO_WRAP)); Log.v(LOG_TAG,loginEncoded); //TODO: remove this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION,Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header setRetryPolicy(new TokenRetryPolicy(context)); //**THE RETRY POLICY** }
我设置的重试策略被定义为默认值,但我实现了自己的重试方法:
@Override public void retry(VolleyError error) throws VolleyError { Log.v(LOG_TAG,"Initiating a retry"); mCurrentRetryCount++; //increment our retry count mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); if (error instanceof AuthFailureError) { //we got a 401,and need a new token Log.v(LOG_TAG,"AuthFailureError found!"); VolleyUser.refreshTokenTask(context,this); //**GET A NEW TOKEN** } if (!hasAttemptRemaining()) { Log.v(LOG_TAG,"No attempt remaining,ERROR"); throw error; } }
刷新令牌任务定义RefreshAPICall
public static void refreshTokenTask(Context context,IRefreshTokenReturn listener) { Log.v(LOG_TAG,"refresh token task called"); final IRefreshTokenReturn callBack = listener; RefreshAPICall request = new RefreshAPICall(Request.Method.GET,Constants.APIConstants.URL.GET_TOKEN_URL,context,new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { String token = response.getString(Constants.APIConstants.Returns.RETURN_TOKEN); Log.v(LOG_TAG,"Token from return is: " + token); callBack.onTokenRefreshComplete(token); } catch (JSONException e) { callBack.onTokenRefreshComplete(null); //TODO: log this e.printStackTrace(); } } },new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.v(LOG_TAG,"Error with RETRY : " + error.toString()); } }); VolleySingleton.getInstance(context).addToRequestQueue(request); }
我们的RefreshAPICall定义:
public RefreshAPICall(int method,String> credentials = sessionManager.getRefreshUserDetails(); //get the user's credentials for authentication this.listener = responseListener; //encode the user's username and token String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + Constants.APIConstants.Characters.CHAR_COLON + credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD)).getBytes(),Base64.NO_WRAP)); this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION,Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header setTag(Constants.VolleyConstants.RETRY_TAG); //mark the retry calls with a tag so we can delete any others once we get a new token setPriority(Priority.IMMEDIATE); //set priority as immediate because this needs to be done before anything else //debug lines Log.v(LOG_TAG,"RefreshAPICall made with " + credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + " " + credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD)); Log.v(LOG_TAG,"Priority set on refresh call is " + getPriority()); Log.v(LOG_TAG,"Tag for Call is " + getTag()); }
我将此请求的优先级设置为高,以便在失败之前触发,因此一旦我们获得令牌,原始调用就可以使用有效令牌触发.
最后,在响应时,我使用重试标记删除任何其他任务(如果多个API调用失败并进行多次重试调用,我们不希望多次覆盖新令牌)
@Override public void onTokenRefreshComplete(String token) { VolleySingleton.getInstance(context).getRequestQueue().cancelAll(Constants.VolleyConstants.RETRY_TAG); Log.v(LOG_TAG,"Cancelled all retry calls"); SessionManager sessionManager = new SessionManager(context); sessionManager.setStoredToken(token); Log.v(LOG_TAG,"Logged new token"); }
不幸的是,LogCat告诉我在使用令牌之前所有的重试都在进行.令牌成功返回,但显然IMMEDIATE优先级对队列调度调用的顺序没有影响.
有关如何确保我的RefreshAPICall的任何帮助都会被激发,然后才会非常感谢其他任务.我想知道Volley是否将RefreshAPICall视为原始失败任务的子任务,因此它会尝试将该原始任务调用其重试次数,直到这些任务结束,然后触发RefreshAPICall.
LogCat(不知道如何让它看起来很漂亮):
05-05 16:12:07.145: E/Volley(1972): [137] BasicNetwork.performRequest: Unexpected response code **401 for https://url.me/api/get_friends** 05-05 16:12:07.145: V/TokenRetryPolicy(1972): Initiating a retry 05-05 16:12:07.145: V/TokenRetryPolicy(1972): AuthFailureError found! 05-05 16:12:07.146: V/VolleyUser(1972): refresh token task called 05-05 16:12:07.146: V/RefreshAPICall(1972): RefreshAPICall made with username user_password 05-05 16:12:07.147: V/RefreshAPICall(1972): Priority set on refresh call is HIGH 05-05 16:12:07.147: V/RefreshAPICall(1972): Tag for Call is retry 05-05 16:12:07.265: E/Volley(1972): [137] BasicNetwork.performRequest: Unexpected response code **401 for https://url.me/api/get_friends** 05-05 16:12:07.265: V/TokenRetryPolicy(1972): Initiating a retry 05-05 16:12:07.265: V/TokenRetryPolicy(1972): AuthFailureError found! 05-05 16:12:07.265: V/VolleyUser(1972): refresh token task called 05-05 16:12:07.265: V/RefreshAPICall(1972): RefreshAPICall made with user user_password 05-05 16:12:07.265: V/RefreshAPICall(1972): Priority set on refresh call is HIGH 05-05 16:12:07.265: V/RefreshAPICall(1972): Tag for Call is retry 05-05 16:12:07.265: V/TokenRetryPolicy(1972): No attempt remaining,ERROR 05-05 16:12:08.219: I/Choreographer(1972): Skipped 324 frames! The application may be doing too much work on its main thread. 05-05 16:12:08.230: V/RefreshAPICall(1972): Response from server on refresh is: {"status":"success","token":"d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64"} 05-05 16:12:08.230: V/VolleyUser(1972): Token from return is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64 05-05 16:12:08.231: V/TokenRetryPolicy(1972): Cancelled all retry calls 05-05 16:12:08.257: V/SessionManager(1972): New Token In SharedPref is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64 05-05 16:12:08.257: V/TokenRetryPolicy(1972): Logged new token
解决方法
当我使用Volley创建我的一般(最常见)API调用时,如果失败则保存对该调用的引用,并将其传递给我的重试策略.
public GeneralAPICall(int method,errorListener); sessionManager = SessionManager.getmInstance(context); HashMap<String,String> credentials = sessionManager.getUserDetails(); // Get the user's credentials for authentication this.listener = responseListener; this.params = params; // Encode the user's username and token String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + Constants.APIConstants.Characters.CHAR_COLON + credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(),Constants.APIConstants.BasicAuth.BASIC + loginEncoded); // Set the encoded information as the header setRetryPolicy(new TokenRetryPolicy(context,this)); //passing "this" saves the reference }
然后,在我的重试策略类(它只是扩展DefaultRetryPolicy,当我收到401错误告诉我需要一个新令牌时,我拍摄了一个refreshToken调用来获取一个新令牌.
public class TokenRetryPolicy extends DefaultRetryPolicy implements IRefreshTokenReturn{ ... @Override public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; //increment our retry count mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); if (error instanceof AuthFailureError && sessionManager.isLoggedIn()) { mCurrentRetryCount = mMaxNumRetries + 1; // Don't retry anymore,it's pointless VolleyUser.refreshTokenTask(context,this); // Get new token } if (!hasAttemptRemaining()) { Log.v(LOG_TAG,ERROR"); throw error; } } ... }
一旦该调用返回,我在我的重试策略类中处理响应.我修改失败的调用,给它新的令牌(在将标记存储在SharedPrefs中之后)进行身份验证,然后再将其激活!
@Override public void onTokenRefreshComplete(String token,String expiration) { sessionManager.setStoredToken(token,expiration); HashMap<String,String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication //encode the user's username and token String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + Constants.APIConstants.Characters.CHAR_COLON + credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(),loginEncoded); //TODO: remove callThatFailed.setHeaders(Constants.APIConstants.BasicAuth.AUTHORIZATION,Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //modify "old,Failed" call - set the encoded information as the header VolleySingleton.getInstance(context).getRequestQueue().add(callThatFailed); Log.v(LOG_TAG,"fired off new call"); }
这个实现对我很有用.
但是,我应该注意这种情况不应该发生太多,因为我知道在进行任何API调用之前我应该检查我的令牌是否已过期.这可以通过在SharedPrefs中存储到期时间(从服务器返回),并查看current_time – 到期时间< some_time,some_time是你想要在它到期之前得到一个新令牌的时间,对我来说是10秒. 希望这可以帮助那里的人,如果我错了什么,请评论!