Недавно мне пришлось работать с большим старым проектом, который использует Retrofit 1, okhttp3, jobManager и Picasso 2.71828
Приложение получает данные с сервера.
Логика взаимодействия: пользователь входит в систему, получает токен, токен обновления. Они хранятся в SharedPreferences с помощью shHelper. С помощью токена он может отправлять запросы (где-то в URL, а где-то в теле), с помощью токена обновления пользователь может получить новый токен, если сеанс сброшен или токен гнилой.
Ошибки авторизации (401) обрабатываются аутентификатором okhttp3, который нам удалось использовать с Picasso.
Но возникла проблема - Пикассо, если на экране несколько изображений, - отправляет несколько запросов подряд, одновременно или почти одновременно, и, поскольку все они сразу получают ответ 401, если токен гнилой, аутентификатор немедленно отправляет один и тот же номер. запросов на обновление токена.
Есть ли какой-нибудь элегантный способ дождаться обновления токена и затем повторить запросы для остальных картинок? Теперь это происходит следующим образом - получив ошибку 401, токен сбрасывается в ноль (token = "") и все другие потоки, попадающие в аутентификатор, проверяют, выполняют ли (token == "") Thread.sleep () и I я очень недоволен этим
private Authenticator getAuthenticator() {
return (route, response) -> {
if (errorCount > 3){
return null;
}
if (response.request().url().toString().endsWith("/refreshToken")) {
Log.d(TAG, "getAuthenticator: " + "refreshToken");
PasswordRepeatActivity.start(context);
return null;
}
if (response.request().url().toString().endsWith("/auth")) {
String message = "Попробуйте позже";
try {
com.google.gson.Gson gson = Gson.builder().create();
ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
message = apiError.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
throw new IOException(message);
}
String login = spHelper.getCurrentLogin();
Auth auth = spHelper.getAuth(login);
String token = auth.getToken();
HttpUrl oldUrl = response.request().url();
//if token is empty - repeat checking after some time
Log.d(TAG, "getAuthenticator: token ==" + token);
if (token != null && token.isEmpty()) {
boolean isEmpty = true;
while (isEmpty){
try {
Log.d(TAG, "Authenticator: sleeping...");
Thread.sleep(500);
String mToken = spHelper.getAuth(login).getToken();
if (mToken!= null && !mToken.isEmpty()){
isEmpty = false;
}
Log.d(TAG, "Authenticator: check if token is refreshed");
if (!mToken.isEmpty() && oldUrl.toString().contains("token") && !mToken.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + mToken);
return getRefreshedUrlRequest(mToken, oldUrl);
}
} catch (InterruptedException e) {
e.printStackTrace();
return response.request();
}
}
return response.request();
} else if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + token);
return getRefreshedUrlRequest(token, oldUrl);
} else {
auth.clearToken();
spHelper.putAuth(login, auth);
String refreshToken = auth.getRefreshToken();
RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
try {
AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
errorCount = 0;
Auth newAuth = refreshResponse.getResponse();
spHelper.putAuth(login, newAuth);
Request request = response.request();
RequestBody requestBody = request.body();
String newToken = newAuth.getToken();
Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
if (oldUrl.toString().contains("token")) {
return getRefreshedUrlRequest(newToken, oldUrl);
}
if (requestBody != null
&& requestBody.contentType() != null
&& requestBody.contentType().subtype() != null
&& requestBody.contentType().subtype().contains("json")) {
requestBody = processApplicationJsonRequestBody(requestBody, newToken);
}
if (requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
} else {
LoginActivity.show(context);
}
return request;
} catch (RequestException e) {
AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
return response.request();
}
}
};
}
Я ищу способы после первой ошибки 401 отправить один запрос, чтобы обновить токен и дождаться его со всеми другими потоками, а затем отправить запросы с новым токеном.
Помимо ожидания обновления токена в аутентификаторе, есть ли способ как-то упростить этот код? Теперь этот метод имеет длину около 100 строк, и каждый раз, когда необходимо изменить его, даже чтение и сохранение логики в вашей голове становится проблемой.
Итак, через некоторое время и некоторые попытки я сделал часть аутентификатора синхронизированной с некоторым объектом блокировки. Теперь только один поток одновременно может получить доступ к аутентификатору. Итак, если токену нужно обновить bs - он будет, и после обновления все потоки, ожидающие новый токен, будут повторять свои вызовы с новым токеном.
Спасибо @Yuri Schimke за обмен очень полезной информацией.
private Authenticator getAuthenticator() {
return (route, response) -> {
String responseUrl = response.request().url().toString();
if (responseUrl.endsWith("/refreshToken") ) {
Log.d(TAG, "getAuthenticator: " + "refreshToken");
PasswordRepeatActivity.start(context);
return null;
}
if (responseUrl.endsWith("/auth")) {
String message = "Попробуйте позже";
try {
com.google.gson.Gson gson = Gson.builder().create();
ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
message = apiError.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
throw new IOException(message);
}
synchronized (LOCK) {
String login = spHelper.getCurrentLogin();
Auth auth = spHelper.getAuth(login);
String token = auth.getToken();
HttpUrl oldUrl = response.request().url();
if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + token);
return getRefreshedUrlRequest(token, oldUrl);
} else {
String refreshToken = auth.getRefreshToken();
RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
try {
AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
Auth newAuth = refreshResponse.getResponse();
spHelper.putAuth(login, newAuth);
Request request = response.request();
RequestBody requestBody = request.body();
String newToken = newAuth.getToken();
Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
if (oldUrl.toString().contains("token")) {
return getRefreshedUrlRequest(newToken, oldUrl);
}
if (requestBody != null
&& requestBody.contentType() != null
&& requestBody.contentType().subtype() != null
&& requestBody.contentType().subtype().contains("json")) {
requestBody = processApplicationJsonRequestBody(requestBody, newToken);
}
if (requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
} else {
LoginActivity.show(context);
}
return request;
} catch (RequestException e) {
AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
PasswordRepeatActivity.start(context);
return null;
}
}
}
};
}