【问题标题】:Okhttp response callbacks on the main thread主线程上的 Okhttp 响应回调
【发布时间】:2014-08-06 11:03:21
【问题描述】:

我创建了一个帮助类来处理我的应用程序中的所有 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 回调在主线程上运行,为什么会出现此错误?

** 顺便说一句,我创建了 HttpCallback 接口来包装 Okhttp 的 Callback 类,因为我想改变 onResponseonFailure 的行为,这样我就可以统一处理失败响应的逻辑由于 i/o 异常和由于服务器问题导致的响应失败。

谢谢。

【问题讨论】:

  • Android 网络活动无法在主线程上运行。我没有使用 Okhttp 的经验,但我很确定您在单独的线程上。
  • 我认为 Okhttp 在单独的线程上处理网络 io 并在主线程上回调。根据@jake-wharton 的创作者之一stackoverflow.com/a/21010181/599912 的说法,至少这就是改造的原因
  • 我明白了。我在他们的documentation 中找到了一个Calback 类。你可以尝试实现那个。
  • @TmKVU 我实际上在client.newCall(request).enqueue() 的匿名类中实现了它。我从来没有从主线程产生另一个线程,所以我怀疑 Okhttp 天生就是这样做的。我认为以某种方式控制没有被转移回主线程

标签: android multithreading okhttp


【解决方案1】:

据我了解,Okhttp 回调在主线程上运行,为什么会出现此错误?

这不是真的。回调在后台线程上运行。如果您想立即处理 UI 中的某些内容,则需要发布到主线程。

由于您已经有一个围绕回调的包装器,您可以在帮助器内部执行此操作,以便为方便起见在主线程上调用所有 HttpCallback 方法。

【讨论】:

  • 谢谢,确实我必须在主线程上运行回调。你能看看我在下面的答案中添加的代码吗?不确定这是否是正确的方法,你会怎么做? Okhttp 代码库是否包含 Android 特定示例?
  • 现在的问题是在 MainActivity 中调用response.body().string() 会抛出android.os.NetworkOnMainThreadException(因为它是在主线程上执行的)。此调用是否进行任何网络操作?
  • 我看不出回调应该在后台线程中而不是在 UI 中的重要原因,因为在大多数情况下,我们会执行网络调用,希望它们在后台运行,但是在前端/UI 上发布他们的结果
  • OkHttp 对主线程一无所知。它不是 Android 库,而是 Java 库。此外,通过回调初始数据,它允许流式响应,并可能在整个响应被使用之前增量更新 UI。
  • "这不是 Android 库,而是 Java 库" - 现在更有意义了。谢谢!
【解决方案2】:

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

            }
        });
 }

【讨论】:

  • 你的context是从哪里来的。当一个活动调用HttpUtil时必须要介绍它的context?我认为这不是很好。
  • 你可以试试new Handler(Looper.getMainLooper())而不是new Handler(context.getMainLooper())
  • 您对片段或活动的配置更改没有问题,因为 cb 在配置更改之前会引用旧对象?
【解决方案3】:

我知道这是一个老问题,但最近我遇到了同样的问题。如果您需要更新任何视图,您需要使用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);                
            }
        });
   }
});

【讨论】:

  • 非常感谢,你拯救了我的一天!
  • 这就是我一直在寻找的东西,谢谢你,你让我的生活变得如此轻松!
  • 完美……正是我想要的。
【解决方案4】:

根据 Retrofit 文档 Callback 方法默认在 UI 线程上执行,直到您向 Retrofit 提供回调执行程序或使用自定义方法返回类型时使用 CallAdapterFactory

【讨论】:

  • 这个问题是关于 OkHttp,而不是 Retrofit。 Retrofit 会自动处理切换回主线程,OkHttp 不会。
【解决方案5】:

Michael 的上述响应是让您的回调发生在调用 Activity 中的一个很好的解决方案,我将它用作我将在下面概述的解决方案的指南(Kotlin 中的解决方案);

HttpCallback 接口

interface HttpCallback {
    fun onResponse(response: Response)
    fun onFailure(call: Call, e: IOException)
}

接口的实现。我将调用活动作为弱引用传递以执行 UI 更新。

class ImageUploadCallback(activity: WeakReference<Activity>) : HttpCallback {
    val _activity = activity

    override fun onResponse(response: Response) {
        if(_activity.get() != null){
            val activty =  _activity.get()!!

            activty.runOnUiThread(Runnable {
                //Update the UI to your hearts content
            });
        }

    }

    override fun onFailure(call: Call, e: IOException) {
         //Your implemtnation here
    }

}

从 Activity 调用 HttpProxy 类,将回调作为参数传递

HttpProxy.UploadImage(
                imageToUpload.name,
                imageToUpload,
                MEDIA_TYPE!!,
                //our callback
                ImageUploadCallback(WeakReference(this))

最后,HttpProxy 类中接受回调的代码;

fun UploadImage(filename: String, sourceImageFile: File, mediaType: MediaType, callback: HttpCallback) {
   
    //building up our request...and then calling
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            callback.onFailure(call, e)
        }

        override fun onResponse(call: Call, response: Response) {
            response.use {
                try{
                    callback.onResponse(response)
                }
                catch(e: Exception){

                }
            }
        }
    });
 }

【讨论】:

    猜你喜欢
    • 2018-10-12
    • 2014-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多