【问题标题】:Retrieving HTTP response without blocking the main thread在不阻塞主线程的情况下检索 HTTP 响应
【发布时间】:2017-05-24 04:49:54
【问题描述】:

我目前正在将一个用 Objective-C 编写的网络库移植到 Java,我需要向服务器发出异步 HTTP 请求的功能,而不依赖任何第三方库(因此,没有 Apache 依赖项)。我正在使用HTTPUrlConnection 来执行此操作;但是,如您所知,这是一个同步过程。

我正在尝试使用ExecutorServiceFuture<> 结构在此过程中实现并发。我需要使用Callable 而不是Runnable,因为我需要处理服务器发回给我的响应消息。以下是我正在做的事情的简要概述:

ExecutorService executorService = Executors.newSingleThreadExecutor();
  Future<String> future = executorService.submit(new Callable<String>() {
     @Override
     public String call() throws Exception {
        return postRequest(reqMsg);
     }
  });

其中 reqMsg 是请求消息(在 XML 中),而 postRequest 方法将响应消息作为字符串返回(也在 XML 中)。现在,为了检索这个 Callable 任务的返回值,我需要使用future.get()。但是,此方法是一个阻塞调用,这意味着它会阻塞主线程,直到响应可用。这不是我想要的。

我正在将一个 iOS 应用程序移植到 Android,并且我将使用 J2ObjC 在这些平台之间建立一个跨平台共享库。在 iOS 版本(即 Obj-C 版本的库)中,有完成处理程序来处理来自 HTTP 请求的结果,并且它是异步完成的。不幸的是,Java 在 Java 8 中使用 CompletableFuture 引入了这种回调方式来处理它。但是,Android 仅支持从 API 级别 24 开始的 CompletableFuture。我希望能够支持追溯到 Jelly Bean (API 16) 的 API 级别。据我所知,“对于与 Android 兼容的 JAR 文件,它只能引用作为 Android 的一部分可用的类以及在 JAR 本身中专门实现的其他类”。您可能会建议使用 AsyncTasks;但是,我想在 Java 网络端处理 HTTP 请求的并发性,因为这个库很可能会在两个平台之间共享。

我尝试过明确地使用线程;然而,当我研究它时,我发现为了使用 Callables,你需要使用 ExecutorService 和 Future (另外,似乎有一些与显式创建线程相关的性能开销 - 尽管我认为 TL:DR):

  • 我不想依赖第三方库,除非绝对必要。

  • 我不能使用CompletableFuture,因为我希望我的最低 API 级别为 16。

  • 我不想在 Android 端使用 AsyncTasks 处理这种并发,因为最初,这是在网络库中使用 CompletionHandler (Objective-C) 处理的。

有没有办法在不阻塞的情况下使用Future.get()? (温馨提示,检查 Future.isDone() 的 while 循环也会阻塞主线程。

【问题讨论】:

  • 请阅读How to Ask。你问题的文字很长,这个网站上的大多数人都会放弃阅读它。

标签: java android multithreading concurrency nonblocking


【解决方案1】:

调用Future.get() 是一个阻塞操作,您无法更改它。

您在这里想要的是在工作完成后获得回调。为此,您根本不必使用Future。您始终可以将Executor.execute()Runnable 一起使用,与您的Callable 做完全相同的事情,但它不应返回一个值,而是应该调用一个自定义 - 在方法参数中提供 - 带有该值的回调。基本上相当于 Java 8 中引入的Consumer 接口。

ExecutorService executorService = Executors.newSingleThreadExecutor();

public void execute(YourRequestObject reqMsg, Consumer<String> consumer) {
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            String result = postRequest(reqMsg);
            consumer.accept(result);
        }
    });
}

也不要每次都创建执行器。只需使用一个执行器进行所有操作。也许是缓存的而不是单线程的。

请记住,您的回调将在执行程序线程上调用。如果您想更新 UI,您需要在 UI 处理程序上发帖。

【讨论】:

  • 非常感谢,我试试看!
【解决方案2】:

你的问题没有多大意义。一方面,您想在单独的线程中获取数据,但同时在另一个线程中获取数据,而无需等待获取线程完成。

您需要做的是将 fetch 和“对获取的数据做任何事情”包装到 Runnable 中并在单独的线程中运行它。

确保在事件调度线程中更新 UI,以防您需要使用获取的数据对其进行更新。

【讨论】:

  • 谢谢,现在就试试吧!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-04
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多