在不阻塞主线程的情况下检索 HTTP 响应

Retrieving HTTP response without blocking the main thread

我目前正在将一个用 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 方法 return 是字符串形式的响应消息(也在 XML 中)。现在,为了检索此 Callable 任务的 return 值,我需要使用 future.get()。但是,此方法是一个阻塞调用,这意味着它会阻塞主线程,直到响应可用。这不是我想要的。

我正在将 iOS 应用程序移植到 Android,我将使用 J2ObjC 在这些平台之间建立一个跨平台共享库。在 iOS 版本(即库的 Obj-C 版本)中,有处理 HTTP 请求结果的完成处理程序,并且它是异步生成的。 Java,不幸的是,在 Java 8 中引入了这种处理它的回调方式,使用 CompletableFuture。但是,Android 仅支持从 API 级别 24 开始的 CompletableFuture。我希望能够支持返回到 Jelly Bean (API 16) 的 API 级别。据我所知,"for a JAR file to be compatible with Android, it can only reference classes available as part of Android and other classes implemented specifically in the JAR itself"。您可能会建议使用 AsyncTasks;但是,我想在 Java 网络端处理 HTTP 请求的并发,因为这个库很可能会在两个平台之间共享。

我试过明确地使用线程;然而,正如我研究的那样,我发现为了使用 Callables,你需要使用 ExecutorService 和 Future(另外,似乎有一些与显式创建线程相关的性能开销 - 尽管我认为 < 1000ms 的开销是可以接受的).再次重申(即 TL:DR):

有没有办法不阻塞地使用Future.get()? (温馨提醒,while 循环检查 Future.isDone() 也会阻塞主线程)。

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

您需要做的是将 fetch 和 "do whatever with the fetched data" 包装到 Runnable 中,并将其 运行 包装在一个单独的线程中。

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

调用 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 处理程序上创建一个 post。