当一个应用程序组件启动并且该应用程序没有任何其他组件在运行时,Android 系统会为该应用程序启动一个新的 Linux 进程,并使用单线程执行。
默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程中(称为“main”线程)。 如果应用程序组件启动并且已经存在该应用程序的进程(因为该应用程序的另一个组件存在),则该组件在该进程内启动并使用相同的执行线程。
但是,可以安排应用程序中的不同组件在单独的进程中运行,并且可以为任何进程创建额外的线程。
Process
默认情况下,同一应用程序的所有组件都在同一进程中运行,大多数应用程序不应更改这一点。 但是,如果发现需要控制某个组件属于哪个进程,则可以在清单文件中进行。
每种类型的组件元素 <activity>
、<service>
、<receiver>
和 <provider>
的清单条目都支持 android:process
属性,该属性可以指定该组件应在其中运行的进程。
可以设置此属性,以便每个组件在其自己的进程中运行,或者使某些组件共享一个进程而其他组件不共享。 还可以设置 android:process
以便不同应用程序的组件在同一个进程中运行——前提是这些应用程序共享相同的 Linux 用户 ID 并使用相同的证书进行签名。
<application>
元素还支持 android:process
属性,以设置适用于所有组件的默认值。
当其他更直接为用户服务的进程需要资源时,Android 可能会决定关闭某个进程。 在被杀死的进程中运行的应用程序组件因此被破坏。 当这些组件再次有工作要做时,将再次为这些组件启动一个流程。
在决定要杀死哪些进程时,Android 系统会权衡它们对用户的相对重要性。 例如,与托管可见activity的进程相比,它更容易关闭托管在屏幕上不再可见的activity的进程。
因此,是否终止进程的决定取决于在该进程中运行的组件的状态。
Thread
当应用程序启动时,系统会为应用程序创建一个执行线程,称为“main”。 这个线程非常重要,因为它负责将事件分派给适当的用户界面小部件,包括绘图事件。
它也几乎总是应用程序与来自 Android UI 工具包的组件(来自 android.widget
和 android.view
包的组件)交互的线程。 因此,主线程有时也称为 UI 线程。
但是,在特殊情况下,应用程序的主线程可能不是它的 UI 线程; 有关详细信息,请参阅线程注释。
系统不会为组件的每个实例创建单独的线程。 在同一进程中运行的所有组件都在 UI 线程中实例化,并且从该线程分派对每个组件的系统调用。
因此,响应系统回调的方法(例如用于报告用户操作的 onKeyDown()
或生命周期回调方法)始终在进程的 UI 线程中运行。
例如,当用户触摸屏幕上的按钮时,应用程序的 UI 线程将触摸事件分派给小部件,小部件反过来设置其按下状态并将无效请求发送到事件队列。 UI 线程将请求出列并通知小部件它应该重绘自己。
当应用程序执行密集工作以响应用户交互时,除非正确实现应用程序,否则这种单线程模型可能会产生较差的性能。
具体来说,如果一切都发生在 UI 线程中,那么执行网络访问或数据库查询等长时间操作将阻塞整个 UI。
当线程被阻塞时,不能调度任何事件,包括绘图事件。 从用户的角度来看,应用程序似乎挂起。
更糟糕的是,如果 UI 线程被阻塞超过几秒(目前大约 5 秒),用户会看到臭名昭著的“应用程序无响应”(ANR)对话框。
此外,Android UI 工具包不是线程安全的。 因此,不能从工作线程操作 UI — 必须从 UI 线程对用户界面进行所有操作。 因此,Android 的单线程模型只有两条规则:
- 不要阻塞 UI 线程
- 不要从 UI 线程外部访问 Android UI 工具包
Worker thread
由于上述单线程模型,不阻塞 UI 线程对应用程序 UI 的响应能力至关重要。 如果要执行的操作不是即时的,则应确保在单独的线程(“后台”或“工作”线程)中执行它们。
但是,请注意,不能从 UI 线程或“main”线程以外的任何线程更新 UI。
为了解决这个问题,Android 提供了几种从其他线程访问 UI 线程的方法:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
要处理与工作线程更复杂的交互,可能会考虑在工作线程中使用 Handler
来处理从 UI 线程传递的消息。
Thread-safe methods
在某些情况下,实现的方法可能会从多个线程调用,因此必须编写为线程安全的。
这主要适用于可以远程调用的方法——例如bound service中的方法。 当对 IBinder
中实现的方法的调用源自运行 IBinder
的同一进程时,该方法将在调用者的线程中执行。
但是,当调用源自另一个进程时,该方法会在从系统维护的与 IBinder
相同的进程中的线程池中选择的线程中执行(它不在进程的 UI 线程中执行)。
例如,服务的 onBind()
方法将从服务进程的 UI 线程调用,而在 onBind()
返回的对象(例如,实现 RPC 方法的子类)中实现的方法将从线程池调用。
因为一个服务可以有多个客户端,多个池线程可以同时使用同一个 IBinder 方法。 因此,IBinder 方法必须实现为线程安全的。
类似地,content provider可以接收源自其他进程的数据请求。 尽管 ContentResolver 和 ContentProvider 类隐藏了如何管理进程间通信的细节,
但响应这些请求的 ContentProvider 方法——方法 query()、insert()、delete()、update() 和 getType()——从content provider进程中的线程池调用,而不是进程的 UI 线程。
因为这些方法可能同时从任意数量的线程调用,所以它们也必须实现为线程安全的。
Interprocess communication
Android 提供了一种使用远程过程调用 (RPC) 进行进程间通信 (IPC) 的机制,其中方法由 Activity 或其他应用程序组件调用,但远程执行(在另一个进程中), 并将任何结果返回给调用者。
这需要将方法调用及其数据分解到操作系统可以理解的水平,将其从本地进程和地址空间传输到远程进程和地址空间,然后在那里重新组装和重新制定调用。 然后返回值以相反的方向传输。
Android 提供了执行这些 IPC 事务的所有代码,因此可以专注于定义和实现 RPC 编程接口。