Ответ 1
Отказ от ответственности: На самом деле я использую
Dagger
+RxJava
+RxAndroid
+Retrofit
, но я просто хотел дать ответ, чтобы продемонстрировать логику для будущих посетителей. Единственное отличие заключается в использованииSchedulers.trampoline()
при обновлении вашего токена для блокировки этого потока. Если у вас есть дополнительные вопросы об этих библиотеках, прокомментируйте это ниже, поэтому, возможно, я смогу предоставить вам другой ответ или помочь вам.
Кроме
Важно Пожалуйста, прочтите это: Если вы делаете запросы одновременно, но также используя
dispatcher.setMaxRequests(1);
, ваш токен будет обновляться несколько раз внутри классаTokenInterceptor
. Например, приложение делает запрос, но в то же время ваша служба также запрашивает запрос. Чтобы избить эту проблему, просто добавьте ключевое словоsynchronized
к вашему методуintercept
внутриTokenInterceptor
:public **synchronized** Response intercept(Chain chain)
@Edit 07.04.2017:
Я обновил этот ответ, потому что он был немного старым, и мои обстоятельства изменились - теперь у меня есть фоновый сервис, который также вызывает запросы -
Прежде всего, процесс токен обновления является критическим процессом. В моем приложении и большинстве приложений это делает: Если обновить токен не удается выйти из текущего пользователя и предупредить пользователя о входе в систему. (Возможно, вы можете повторить попытку обновления токена в 2-3-4 раза в зависимости от вас)
Замечание @Important. Пожалуйста, делайте синхронные запросы при обновлении вашего токена внутри Authenticator
или Interceptor
из-за того, что вы должны блокировать этот поток до тех пор, пока ваш запрос не завершится иначе, ваши запросы будут выполняться дважды со старыми и новыми лексема.
В любом случае я объясню это шаг за шагом:
Шаг 1: Обратитесь singleton pattern, мы создадим один класс, ответственный за возврат нашего экземпляра модификации где бы мы ни находились. Поскольку его статический, если нет экземпляра, он просто создает экземпляр только один раз, и когда вы вызываете его, он всегда возвращает этот статический экземпляр. Это также базовое определение шаблона проектирования Singleton.
public class RetrofitClient {
private static Retrofit retrofit = null;
private RetrofitClient() {
// this default constructor is private and you can't call it like :
// RetrofitClient client = new RetrofitClient();
// only way calling it : Retrofit client = RetrofitClient.getInstance();
}
public static Retrofit getInstance() {
if (retrofit == null) {
// my token authenticator , I will add this class below to show the logic
TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();
// I am also using interceptor which controls token if expired
// lets look at this scenerio : if my token needs to refresh after 10 hours but I came
// to application after 50 hours and tried to make request.
// ofc my token is invalid and if I make request it will return 401
// so this interceptor checks time and refresh token immediately before making request and after makes current request
// with refreshed token. So I do not get 401 response. But if this fails and I get 401 then my TokenAuthenticator do his job.
// if my TokenAuthenticator fails too, basically I just logout user and tell him to relogin.
TokenInterceptor tokenInterceptor = new TokenInterceptor();
// this is the critical point that helped me a lot.
// we using only one retrofit instance in our application
// and it uses this dispatcher which can only do 1 request at the same time
// the docs says : Set the maximum number of requests to execute concurrently.
// Above this requests queue in memory, waiting for the running calls to complete.
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(1);
// we using this OkHttp, you can add authenticator, interceptors, dispatchers,
// logging stuff etc. easily for all your requests just editing this OkHttp
OkHttpClient okClient = new OkHttpClient.Builder()
.connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(Constants.READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(Constants.WRITE_TIMEOUT, TimeUnit.SECONDS)
.authenticator(tokenAuthenticator)
.addInterceptor(tokenInterceptor)
.dispatcher(dispatcher)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(context.getResources().getString(R.string.base_api_url))
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.client(okClient)
.build();
}
return retrofit;
}
}
Шаг 2: В моем методе TokenAuthenticator authenticate
:
@Override
public Request authenticate(Route route, Response response) throws IOException {
String userRefreshToken="your refresh token";
String cid="your client id";
String csecret="your client secret";
String baseUrl="your base url";
refreshResult=refreshToken(baseUrl,userRefreshToken,cid,csecret);
if (refreshResult) {
//refresh is successful
String newaccess="your new access token";
// make current request with new access token
return response.request().newBuilder()
.header("Authorization", newaccess)
.build();
} else {
// refresh failed , maybe you can logout user
// returning null is critical here , because if you do not return null
// it will try to refresh token continuously like 1000 times.
// also you can try 2-3-4 times by depending you before logging out your user
return null;
}
}
и refreshToken
, это просто пример, который вы можете создать свою собственную стратегию при обновлении вашего токена. Я использую HttpUrlConnection
, потому что у меня есть дополнительные обстоятельства при обновлении моего токена. Тем временем я рекомендую вам использовать Retrofit
. В любом случае:
public boolean refreshToken(String url,String refresh,String cid,String csecret) throws IOException{
URL refreshUrl=new URL(url+"token");
HttpURLConnection urlConnection = (HttpURLConnection) refreshUrl.openConnection();
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setUseCaches(false);
String urlParameters = "grant_type=refresh_token&client_id="+cid+"&client_secret="+csecret+"&refresh_token="+refresh;
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
int responseCode = urlConnection.getResponseCode();
if(responseCode==200){
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// this gson part is optional , you can read response directly from Json too
Gson gson = new Gson();
RefreshTokenResult refreshTokenResult=gson.fromJson(response.toString(),RefreshTokenResult.class);
// handle new token ...
// save it to the sharedpreferences, storage bla bla ...
return true;
} else {
//cannot refresh
return false;
}
}
Шаг 3: На самом деле мы сделали это, но я покажу простое использование:
Retrofit client= RetrofitClient.getInstance();
//interface for requests
APIService service = client.create(APIService.class);
// then do your requests .....
Шаг 4: Для тех, кто хочет видеть логику TokenInterceptor
:
public class TokenInterceptor implements Interceptor{
Context ctx;
SharedPreferences mPrefs;
SharedPreferences.Editor mPrefsEdit;
public TokenInterceptor(Context ctx) {
this.ctx = ctx;
this.mPrefs= PreferenceManager.getDefaultSharedPreferences(ctx);
mPrefsEdit=mPrefs.edit();
}
@Override
public synchronized Response intercept(Chain chain) throws IOException {
Request newRequest=chain.request();
//when saving expire time :
integer expiresIn=response.getExpiresIn();
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND,expiresIn);
mPrefsEdit.putLong("expiretime",c.getTimeInMillis());
//get expire time from shared preferences
long expireTime=mPrefs.getLong("expiretime",0);
Calendar c = Calendar.getInstance();
Date nowDate=c.getTime();
c.setTimeInMillis(expireTime);
Date expireDate=c.getTime();
int result=nowDate.compareTo(expireDate);
/**
* when comparing dates -1 means date passed so we need to refresh token
* see {@link Date#compareTo}
*/
if(result==-1) {
//refresh token here , and got new access token
String newaccessToken="newaccess";
newRequest=chain.request().newBuilder()
.header("Authorization", newaccessToken)
.build();
}
return chain.proceed(newRequest);
}
}
В моем приложении я делаю запросы в приложении и в фоновом режиме. Оба они используют один и тот же экземпляр, и я могу легко справиться. Пожалуйста, обратитесь к этому ответу и попробуйте создать своего собственного клиента. Если у вас все еще есть вопросы, просто прокомментируйте ниже, укажите мне - еще один вопрос - или отправьте письмо. Я помогу, когда у меня будет время. Надеюсь, это поможет.