web-dev-qa-db-fra.com

Rappels de réponse Okhttp sur le thread principal

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.

57
Michael

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é.

69
Jake Wharton

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);
                    }
                });

            }
        });
 }
40
Michael

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);                
            }
        });
   }
});
15
Marcos Casagrande

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

1
Manish Goyal