进程和线程概览

当应用组件启动且该应用没有任何其他组件时 运行时,Android 系统会为应用启动一个新的 Linux 进程,其中 执行。默认情况下,同一应用的所有组件都在同一进程和线程中运行, 称为主线程。

如果应用组件启动,并且已有进程 因为应用的另一个组件已经启动,那么这个组件 在该进程内启动,并使用相同的执行线程。不过,您可以安排 应用中的不同组件在单独的进程中运行 任何进程的线程。

本文档介绍进程和线程在 Android 应用中的工作方式。

进程

默认情况下,应用的所有组件均在同一进程中运行,并且大多数应用 不必改变这一点不过,如果您发现自己需要控制 ,您可以在清单文件中执行此操作。

各类组件元素的清单条目(<activity><service><receiver><provider>)支持 android:process 属性,该属性可以指定 以及组件运行时所在的进程您可以设置此属性 或者让一些组件共享一个进程,而另一些组件则不共享。

您还可以设置 android:process,以便不同应用的组件在同一位置运行 进程,前提是这些应用共享相同的 Linux 用户 ID,并使用 相同的证书

<application> 元素还支持 android:process 属性,您可以使用该属性设置 适用于所有组件的默认值。

Android 可能会在某个时候决定关闭某个进程,此时 更即时地为用户服务的其他进程所需的请求。应用 在关闭进程中运行的组件也会随之被销毁。进程启动

在决定关闭哪些进程时,Android 系统会权衡它们与 用户。例如,它更容易关闭托管不再存在的活动的进程。 (与托管可见 activity 的进程相比)。决定是否使用 终止进程,具体取决于在该进程中运行的组件的状态。

本单元详细讨论了进程生命周期及其与应用状态的关系, 进程和应用生命周期

Threads

启动应用时,系统会为应用创建一个执行线程, 称为主线程。此线程非常重要,因为它负责将事件分派给 相应的用户界面微件,包括绘图事件。它还 几乎始终是应用与组件交互的线程 (来自 Android UI 工具包的 android.widget)和 android.view 个软件包。 因此,主线程有时 称为界面线程。但在特殊情况下,应用的主要 线程可能不是其界面线程。有关详情,请参阅 Threads 注释

系统不会为组件的每个实例创建单独的线程。全部 在同一进程中运行的组件会在界面线程中实例化,而系统调用 每个组件均从该线程分派。因此,响应系统的方法 回调,例如 onKeyDown() 来报告用户操作或生命周期回调方法)- 始终在进程的界面线程中运行。

例如,当用户轻触屏幕上的按钮时,应用的界面线程会分派 轻触事件,后者会设置其按下状态,并将失效请求发布到 事件队列中。界面线程将请求移出队列,并通知 widget 重新绘制 本身。

除非正确实现应用,否则这种单线程模型 如果应用执行密集型工作来响应用户互动,则可能会出现性能不佳的情况。 在界面线程中执行长时间运行的操作,例如访问网络或 数据库查询,阻止整个界面。当线程被阻塞时,无法分派任何事件 包括绘图事件

从用户的角度来看, 应用程序似乎挂起更糟糕的是,如果 UI 线程被阻塞超过几秒, 向用户显示了“应用程序 响应”(ANR) 对话框。然后,用户可能会决定退出您的应用,甚至卸载应用 。

请注意,Android 界面工具包并是线程安全的。因此,不要操纵 从工作线程构建界面通过界面对界面执行所有操作 线程。Android 的单线程模型遵循两条规则:

  1. 请勿阻塞界面线程。
  2. 请勿从界面线程外部访问 Android 界面工具包。

工作线程

由于采用这种单线程模式, 避免阻塞界面线程。如果您要执行的操作 无法即时完成,请确保在单独的后台worker 线程。只需记住,除了其他线程,您无法通过其他线程更新界面 界面线程(即主线程)

为了帮助您遵循这些规则,Android 提供了几种从 线程。以下列出了几种有用的方法:

以下示例使用 View.post(Runnable)

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

此实现是线程安全的,因为后台操作是在单独的线程中完成的 而 ImageView 始终通过界面线程操纵。

但是,随着操作复杂性的增加,这种代码可能会变得复杂 难以维护要通过工作线程处理更复杂的交互,您可以考虑 在工作器线程中使用 Handler 处理从界面线程传递的消息。如需关于如何 在后台线程上调度工作并传回界面线程,请参阅 后台工作概览

线程安全方法

在某些情况下,您实现的方法会从多个线程调用,并且 因此必须写入线程安全

这主要适用于可远程调用的方法,例如绑定服务中的方法。当某个 在 IBinder 中实现的方法与 IBinder 正在运行,则系统会在调用方的线程中执行该方法。 不过,如果调用源自另一个进程,该方法会在 系统在与 IBinder 相同的进程中维护的线程池。 它不会在进程的界面线程中执行。

例如,服务的 onBind() 方法是从 在 onBind() 返回的对象中实现的方法(例如 实现远程过程调用 (RPC) 方法的子类,从线程调用 数据池中的资源。由于服务可以有多个客户端,因此可以参与多个池线程 同一 IBinder 方法,因此 IBinder 方法必须 实现为线程安全的

同样,content provider 也可以接收源自其他进程的数据请求。 ContentResolverContentProvider 类用于隐藏有关如何管理进程间通信 (IPC) 的详细信息, 而是响应这些请求的 ContentProvider 方法 - 方法 query(), insert(), delete(), update(), 和getType() 从 content provider 的进程中的线程池(而不是界面)调用 用于启动该进程的线程因为可以从任意数量的线程调用这些方法, 同时,它们也必须实现为线程安全

进程间通信

Android 提供了一种使用 RPC 的 IPC 机制,其中 activity 或其他应用会调用一个方法 组件,但在另一个进程中远程执行,并将所有结果返回给 调用方。这就需要将方法调用及其数据分解到操作系统可以处理的级别 并将其从本地进程和地址空间传输到远程进程 然后在地址空间中重新组装并重新执行调用。

然后,返回值为 以便向相反方向传输数据Android 提供了执行这些 IPC 所需的全部代码 因此您可以专注于定义和实现 RPC 编程接口。

为了执行 IPC,必须使用 bindService() 将应用绑定到服务。如需了解详情,请参阅服务概览