所有 Android 应用都使用主线程来处理界面操作。调用长时间运行的 此主线程中的操作可能会导致冻结和无响应。对于 例如,如果您的应用从主线程发出网络请求,那么应用界面 将被冻结,直到收到网络响应。如果您使用 Java 创建额外的后台线程来处理长时间运行的操作 主线程继续处理界面更新。
本指南介绍了使用 Java 编程语言的开发者如何使用 线程池来设置和使用 Android 应用中的多个线程。本指南 以及如何定义要在线程上运行的代码 在这些线程与主线程之间传输
并发库
请务必了解线程及其底层机制的基础知识。不过,也有许多热门库提供更高级别的 这些概念以及现成可用的实用程序来传递数据, 。这些库包括 Guava 和 RxJava(面向 Java 编程语言用户和协程) 我们建议 Kotlin 用户采用这种方法。
在实际操作中,您应该选择最适合您的应用和 但线程的规则保持不变。
示例概览
根据应用架构指南,本主题中的示例将 并将结果返回给主线程,应用随后 可能在屏幕上显示该结果。
具体而言,ViewModel 会调用主线程上的数据层,
触发网络请求。数据层负责移动
在主线程以外执行网络请求并发回结果
发送到主线程
为了将网络请求的执行移出主线程,我们需要 在我们的应用中创建其他线程。
创建多个线程
线程池是一个在各线程中运行任务的托管线程集合
并行处理。新任务在现有线程上执行,
线程变为空闲状态要将任务发送到线程池,请使用
ExecutorService 接口。请注意,ExecutorService 无需执行任何操作。
与 Android 应用组件 Service 配合使用。
创建线程的成本很高,因此您应该仅创建一次线程池,
您的应用初始化时。请务必保存 ExecutorService 的实例
在 Application 类或依赖项注入容器中。
以下示例创建了一个包含四个线程的线程池
运行后台任务。
public class MyApplication extends Application {
    ExecutorService executorService = Executors.newFixedThreadPool(4);
}
您还可以通过其他方式配置线程池,具体取决于预期 工作负载如需了解详情,请参阅配置线程池。
在后台线程中执行
在主线程上发出网络请求会导致该线程等待,或者
block,直到收到响应。由于线程处于阻塞状态,因此操作系统无法
调用 onDraw(),而您的应用会卡住,这可能会导致出现 Application Not
响应 (ANR) 对话框。我们改为在后台执行此操作
线程。
发出请求
首先,我们来看看 LoginRepository 类,看看它是如何 
网络请求:
// Result.java
public abstract class Result<T> {
    private Result() {}
    public static final class Success<T> extends Result<T> {
        public T data;
        public Success(T data) {
            this.data = data;
        }
    }
    public static final class Error<T> extends Result<T> {
        public Exception exception;
        public Error(Exception exception) {
            this.exception = exception;
        }
    }
}
// LoginRepository.java
public class LoginRepository {
    private final String loginUrl = "https://example.com/login";
    private final LoginResponseParser responseParser;
    public LoginRepository(LoginResponseParser responseParser) {
        this.responseParser = responseParser;
    }
    public Result<LoginResponse> makeLoginRequest(String jsonBody) {
        try {
            URL url = new URL(loginUrl);
            HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
            httpConnection.setRequestMethod("POST");
            httpConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
            httpConnection.setRequestProperty("Accept", "application/json");
            httpConnection.setDoOutput(true);
            httpConnection.getOutputStream().write(jsonBody.getBytes("utf-8"));
            LoginResponse loginResponse = responseParser.parse(httpConnection.getInputStream());
            return new Result.Success<LoginResponse>(loginResponse);
        } catch (Exception e) {
            return new Result.Error<LoginResponse>(e);
        }
    }
}
makeLoginRequest() 是同步的,并且会阻塞发起调用的线程。为了对
而是有自己的 Result 类。
触发请求
ViewModel 会在用户点按(例如,点按)时触发网络请求,
按钮:
public class LoginViewModel {
    private final LoginRepository loginRepository;
    public LoginViewModel(LoginRepository loginRepository) {
        this.loginRepository = loginRepository;
    }
    public void makeLoginRequest(String username, String token) {
        String jsonBody = "{ username: \"" + username + "\", token: \"" + token + "\" }";
        loginRepository.makeLoginRequest(jsonBody);
    }
}
使用前面的代码时,LoginViewModel 会在进行以下操作时阻塞主线程:
网络请求。我们可以使用已实例化的线程池移动
在后台线程上执行
处理依赖项注入
首先,遵循依赖项注入的原则,LoginRepository
它采用 Executor 实例而不是 ExecutorService,因为它
执行代码而不管理线程:
public class LoginRepository {
    ...
    private final Executor executor;
    public LoginRepository(LoginResponseParser responseParser, Executor executor) {
        this.responseParser = responseParser;
        this.executor = executor;
    }
    ...
}
执行器的 execute() 方法采用 Runnable。Runnable是
单个抽象方法 (SAM) 接口,包含一个在run()
在被调用时调用线程。
在后台执行
我们再创建一个名为 makeLoginRequest() 的函数,该函数会将执行任务移至后台线程,并暂时忽略响应:
public class LoginRepository {
    ...
    public void makeLoginRequest(final String jsonBody) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Result<LoginResponse> ignoredResponse = makeSynchronousLoginRequest(jsonBody);
            }
        });
    }
    public Result<LoginResponse> makeSynchronousLoginRequest(String jsonBody) {
        ... // HttpURLConnection logic
    }
    ...
}
在 execute() 方法中,我们使用代码块创建一个新的 Runnable。
我们想在后台线程中执行(在本例中为同步网络)
请求方法。在内部,ExecutorService 管理 Runnable 和
在可用线程中执行它。
注意事项
应用中的任何线程都可以与其他线程并行运行,包括主线程 所以您应确保代码是线程安全的请注意,在我们的 避免向在线程之间共享的变量写入数据,传递 不可变数据这是一种很好的做法 自己的数据实例,同时避免了同步的复杂性。
如果您需要在线程之间共享状态,则必须小心管理访问权限 从使用同步机制(如锁)的线程中读取数据。这不是 本指南的范围一般来说,您应该避免共享可变状态 之间来回切换。
与主线程通信
在上一步中,我们忽略了网络请求响应。要显示
LoginViewModel 需要知道相关信息。为此,我们可以
使用回调函数。
函数 makeLoginRequest() 应将回调作为参数,
它可以异步返回值。带有结果的回调将调用
在网络请求完成或失败时发送此事件。在 Kotlin 中,我们
使用高阶函数。不过,在 Java 中,我们必须创建一个新的回调
具有相同的功能:
interface RepositoryCallback<T> {
    void onComplete(Result<T> result);
}
public class LoginRepository {
    ...
    public void makeLoginRequest(
        final String jsonBody,
        final RepositoryCallback<LoginResponse> callback
    ) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
                    callback.onComplete(result);
                } catch (Exception e) {
                    Result<LoginResponse> errorResult = new Result.Error<>(e);
                    callback.onComplete(errorResult);
                }
            }
        });
    }
  ...
}
ViewModel 现在需要实现回调。它可以根据结果执行不同的逻辑:
public class LoginViewModel {
    ...
    public void makeLoginRequest(String username, String token) {
        String jsonBody = "{ username: \"" + username + "\", token: \"" + token + "\" }";
        loginRepository.makeLoginRequest(jsonBody, new RepositoryCallback<LoginResponse>() {
            @Override
            public void onComplete(Result<LoginResponse> result) {
                if (result instanceof Result.Success) {
                    // Happy path
                } else {
                    // Show error in UI
                }
            }
        });
    }
}
在此示例中,回调在发起调用的线程中执行,该线程是 后台线程。也就是说,您不能直接修改或传达 与界面层相关联,直到您切换回主线程。
使用处理程序
您可以使用 Handler 将要对其他
线程。要指定在哪个线程上运行操作,请构建
Handler,对线程使用 Looper。Looper 是运行
相关会话的消息循环创建Handler后,
然后可以使用 post(Runnable) 方法运行
相应的会话。
Looper 包含一个辅助函数 getMainLooper(),用于检索
Looper。您可以使用以下代码在主线程中运行代码:
Looper 用于创建 Handler。您可能经常会采用这种方式
您还可以将 Handler 的实例保存到
ExecutorService:
public class MyApplication extends Application {
    ExecutorService executorService = Executors.newFixedThreadPool(4);
    Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper());
}
最好将处理程序注入存储库 
为您提供更大的灵活性。例如,将来您可能想传入
不同的 Handler 以在单独的线程上调度任务。如果您一直
将 Handler 传递到同一个线程中,
代码库构造函数,如以下示例所示。
public class LoginRepository {
    ...
    private final Handler resultHandler;
    public LoginRepository(LoginResponseParser responseParser, Executor executor,
            Handler resultHandler) {
        this.responseParser = responseParser;
        this.executor = executor;
        this.resultHandler = resultHandler;
    }
    public void makeLoginRequest(
        final String jsonBody,
        final RepositoryCallback<LoginResponse> callback
    ) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
                    notifyResult(result, callback);
                } catch (Exception e) {
                    Result<LoginResponse> errorResult = new Result.Error<>(e);
                    notifyResult(errorResult, callback);
                }
            }
        });
    }
    private void notifyResult(
        final Result<LoginResponse> result,
        final RepositoryCallback<LoginResponse> callback,
    ) {
        resultHandler.post(new Runnable() {
            @Override
            public void run() {
                callback.onComplete(result);
            }
        });
    }
    ...
}
或者,如果您希望提高灵活性,可以将 Handler 传递给每个
函数:
public class LoginRepository {
    ...
    public void makeLoginRequest(
        final String jsonBody,
        final RepositoryCallback<LoginResponse> callback,
        final Handler resultHandler,
    ) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
                    notifyResult(result, callback, resultHandler);
                } catch (Exception e) {
                    Result<LoginResponse> errorResult = new Result.Error<>(e);
                    notifyResult(errorResult, callback, resultHandler);
                }
            }
        });
    }
    private void notifyResult(
        final Result<LoginResponse> result,
        final RepositoryCallback<LoginResponse> callback,
        final Handler resultHandler
    ) {
        resultHandler.post(new Runnable() {
            @Override
            public void run() {
                callback.onComplete(result);
            }
        });
    }
}
在此示例中,传递到代码库的 makeLoginRequest 调用的回调在主线程上执行。也就是说,您可以直接修改
或使用 LiveData.setValue() 与界面进行通信。
配置线程池
您可以使用其中一个 Executor 辅助函数创建线程池 如上一个示例代码所示。或者 如果您想自定义线程池的详细信息,可以创建一个 ThreadPoolExecutor 实例。您可以配置以下内容 详细信息:
- 初始池大小和最大池大小。
- 保持活跃的时间和时间单位。保持存活时间是 在关闭之前可以保持空闲状态
- 包含 Runnable任务的输入队列。此队列必须实现 BlockQueue 接口。为了符合应用的要求,您可以 从可用的队列实现中进行选择。如需了解详情,请参阅 ThreadPoolExecutor 概览。
下面的例子根据 处理器核心、1 秒的保持活动时间和输入队列。
public class MyApplication extends Application {
    /*
     * Gets the number of available cores
     * (not always the same as the maximum number of cores)
     */
    private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    // Instantiates the queue of Runnables as a LinkedBlockingQueue
    private final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
    // Sets the amount of time an idle thread waits before terminating
    private static final int KEEP_ALIVE_TIME = 1;
    // Sets the Time Unit to seconds
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
    // Creates a thread pool manager
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            NUMBER_OF_CORES,       // Initial pool size
            NUMBER_OF_CORES,       // Max pool size
            KEEP_ALIVE_TIME,
            KEEP_ALIVE_TIME_UNIT,
            workQueue
    );
    ...
}
