J'ai créé une classe d'assistance pour gérer tous mes appels http dans mon application. C'est un simple wrapper singleton pour okhttp qui ressemble à ceci (j'ai omis certaines parties sans importance):
public class HttpUtil {
private OkHttpClient client;
private Request.Builder builder;
...
public void get(String url, HttpCallback cb) {
call("GET", url, cb);
}
public void post(String url, HttpCallback cb) {
call("POST", url, cb);
}
private void call(String method, String url, final HttpCallback cb) {
Request request = builder.url(url).method(method, method.equals("GET") ? null : new RequestBody() {
// don't care much about request body
@Override
public MediaType contentType() {
return null;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
}
}).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, Throwable throwable) {
cb.onFailure(null, throwable);
}
@Override
public void onResponse(Response response) throws IOException {
if (!response.isSuccessful()) {
cb.onFailure(response, null);
return;
}
cb.onSuccess(response);
}
});
}
public interface HttpCallback {
/**
* called when the server response was not 2xx or when an exception was thrown in the process
* @param response - in case of server error (4xx, 5xx) this contains the server response
* in case of IO exception this is null
* @param throwable - contains the exception. in case of server error (4xx, 5xx) this is null
*/
public void onFailure(Response response, Throwable throwable);
/**
* contains the server response
* @param response
*/
public void onSuccess(Response response);
}
}
Ensuite, dans mon activité principale, j'utilise cette classe d'assistance:
HttpUtil.get(url, new HttpUtil.HttpCallback() {
@Override
public void onFailure(Response response, Throwable throwable) {
// handle failure
}
@Override
public void onSuccess(Response response) {
// <-------- Do some view manipulation here
}
});
onSuccess
lève une exception lorsque le code est exécuté:
Android.view.ViewRootImpl $ CalledFromWrongThreadException: uniquement le Le fil d'origine qui a créé une hiérarchie de vues peut toucher ses vues.
De ma compréhension, les callbacks Okhttp s'exécutent sur le thread principal, alors pourquoi est-ce que je reçois cette erreur?
** En guise de remarque, j'ai créé l'interface HttpCallback
pour envelopper la classe Callback
d'Okhttp parce que je voulais modifier le comportement de onResponse
et onFailure
afin de pouvoir unifier la logique de traitement des réponses échouées en raison d'une exception d'e/s et des réponses échouées en raison problèmes de serveur.
Merci.
De ma compréhension, les callbacks Okhttp s'exécutent sur le thread principal, alors pourquoi est-ce que je reçois cette erreur?
Ce n'est pas vrai. Les rappels s'exécutent sur un thread d'arrière-plan. Si vous souhaitez traiter immédiatement quelque chose dans l'interface utilisateur, vous devez publier dans le fil principal.
Etant donné que vous avez déjà un wrapper autour du rappel, vous pouvez le faire en interne dans votre aide afin que toutes les méthodes HttpCallback
soient appelées sur le thread principal pour plus de commodité.
Comme suggéré par Jake Wharton, je devais exécuter les rappels sur le thread principal de manière explicite.
J'ai donc enveloppé les appels aux callbacks avec Runnable
comme ceci:
private void call(String method, String url, final HttpCallback cb) {
...
client.newCall(request).enqueue(new Callback() {
Handler mainHandler = new Handler(context.getMainLooper());
@Override
public void onFailure(Request request,final Throwable throwable) {
mainHandler.post(new Runnable() {
@Override
public void run() {
cb.onFailure(null, throwable);
}
});
}
@Override
public void onResponse(final Response response) throws IOException {
mainHandler.post(new Runnable() {
@Override
public void run() {
if (!response.isSuccessful()) {
cb.onFailure(response, null);
return;
}
cb.onSuccess(response);
}
});
}
});
}
Je sais que c'est une vieille question, mais récemment, j'ai rencontré le même problème. Si vous devez mettre à jour une vue, vous devez utiliser runOnUiThread()
ou publier le résultat sur le fil principal.
HttpUtil.get(url, new Callback() { //okhttp3.Callback
@Override
public void onFailure(Call call, IOException e) { /* Handle error **/ }
@Override
public void onResponse(Call call, Response response) throws IOException {
String myResponse = response.body().string();
//Do something with response
//...
MyActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
//Handle UI here
findViewById(R.id.loading).setVisibility(View.GONE);
}
});
}
});
Selon la documentation Retrofit Callback , les méthodes sont exécutées par défaut sur le thread d'interface utilisateur jusqu'à ce que vous fournissiez un exécuteur de rappel à Retrofit OR lors de l'utilisation de types de retour de méthode personnalisés à l'aide de CallAdapterFactory