进程和线程概览

当应用组件启动且该应用没有任何其他组件时 运行时,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() 将应用绑定到服务。如需了解详情,请参阅服务概览