web-dev-qa-db-ja.com

メインスレッドでのOkhttp応答コールバック

アプリですべてのhttp呼び出しを処理するヘルパークラスを作成しました。 okhttpのシンプルなシングルトンラッパーは次のようになります(重要でない部分は省略しました)。

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

}

次に、私の主な活動では、このヘルパークラスを使用します。

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は、コードの実行時に例外をスローします。

Android.view.ViewRootImpl $ CalledFromWrongThreadException:ビュー階層を作成した元のスレッドのみがそのビューにアクセスできます。

私の理解では、Okhttpコールバックはメインスレッドで実行されますが、なぜこのエラーが発生するのですか?

**補足として、HttpCallbackCallbackの動作を変更したいので、OkhttpのonResponseクラスをラップするonFailureインターフェイスを作成しました。 I/O例外による失敗した応答とサーバーの問題による失敗した応答を処理するロジックを統合できました。

ありがとう。

59
Michael

私の理解では、Okhttpコールバックはメインスレッドで実行されますが、なぜこのエラーが発生するのですか?

本当じゃない。コールバックはバックグラウンドスレッドで実行されます。 UIで何かをすぐに処理する場合は、メインスレッドに投稿する必要があります。

コールバックのラッパーは既にあるので、これをヘルパーで内部的に実行して、すべてのHttpCallbackメソッドがメインスレッドで便利に呼び出されるようにすることができます。

71
Jake Wharton

Jake Whartonが示唆したように、メインスレッドでコールバックを明示的に実行する必要がありました。

そこで、コールバックの呼び出しを次のようにRunnableでラップしました。

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

            }
        });
 }
41
Michael

私はそれが古い質問であることを知っていますが、最近同じ問題に遭遇しました。ビューを更新する必要がある場合は、runOnUiThread()を使用するか、メインスレッドに結果をポストする必要があります。

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

Retrofitのドキュメントによると、 Callback メソッドは、 Retrofit ORを使用してカスタムメソッドを使用する場合、 CallAdapterFactory を使用するタイプ

1
Manish Goyal