安卓理论之进程与线程
本文摘录自《Android 开发指南中文版》。
进程和线程
当一个应用程序开始运行它的第一个组件时,Android 会为它启动一个 Linux 进程,并在其中执行一个单一的线程。默认情况下,应用程序所有的组件均在这个进程的这个线程中运行。
然而,你也可以安排组件在其他进程中运行,而且可以为任意进程衍生出其它线程。
进程
组件运行所在的进程由 manifest 文件所控制。组件元素——<activity>, <service>, <receiver>和<provider>——都有一个 process 属性来指定组件应当运行于哪个进程之内。这些属性可以设置为使每
个组件运行于它自己的进程之内,或一些组件共享一个进程而其余的组件不这么做。它们也可以设置为令
不同应用程序的组件在一个进程中运行——使应用程序的组成部分共享同一个 Linux 用户 ID 并赋以同样
的权限。<application>元素也有一个process 属性,以设定所有组件的默认值。
所有的组件实例都位于特定进程的主线程内,而对这些组件的系统调用也将由那个线程进行分发。一般不
会为每个实例创建线程。因此,某些方法总是运行在进程的主线程内,这些方法包括诸如
View.onKeyDown()这样报告用户动作以及后面 组件生命周期一节所要讨论的生命周期通告的。这意味
着组件在被系统调用的时候,不应该施行长时间的抑或阻塞的操作(诸如网络相关操作或是循环计算),
因为这将阻塞同样位于这个进程的其它组件的运行。你应该如同下面线程一节所叙述的那样,为这些长时
间操作衍生出一个单独的线程进行处理。
在可用内存不足而又有一个正在为用户进行服务的进程需要更多内存的时候,Android 有时候可能会关闭
一个进程。而在这个进程中运行着的应用程序也因此被销毁。当再次出现需要它们进行处理的工作的时候,
会为这些组件重新创建进程。
在决定结束哪个进程的时候,Android 会衡量它们对于用户的相对重要性。比如说,相对于一个仍有用户 可见的 activity 的进程,它更有可能去关闭一个其 activity 已经不为用户所见的进程。也可以说,决定是
否关闭一个进程主要依据在那个进程中运行的组件的状态。这些状态将在后续的一节组件生命周期中予以
说明。
线程
尽管你可以把你的应用程序限制于一个单独的进程中,有时,你仍然需要衍生出一个线程以处理后台任务。因为用户界面必须非常及时的对用户操作做出响应,所以,控管 activity 的线程不应用于处理一些诸如网
络下载之类的耗时操作。所有不能在瞬间完成的任务都应安排到不同的线程中去。
线程在代码中是以标准 Java Thread 对象创建的。Android 提供了很多便于管理线程的类: Looper 用 于在一个线程中运行一个消息循环, Handler 用于处理消息,HandlerThread 用于使用一个消息循环启
用一个线程。
远程过程调用
Android 有一个轻量级的远程过程调用(RPC)机制:即在本地调用一个方法,但在远程(其它的进程中)
进行处理,然后将结果返回调用者。这将方法调用及其附属的数据以系统可以理解的方式进行分离,并将其从本地进程和本地地址空间传送至远程过程和远程地址空间,并在那里重新装配并对调用做出反应。返
回的结果将以相反的方向进行传递。Android 提供了完成这些工作所需的所有的代码,以使你可以集中精 力来实现 RPC 接口本身。
RPC 接口可以只包括方法。即便没有返回值,所有方法仍以同步的方式执行(本地方法阻塞直至远程方法
结束)。
简单的说,这套机制是这样工作的:一开始,你用简单的 IDL(界面描绘语言)声明一个你想要实现的 RPC 接口。然后用 aidl 工具为这个声明生成一个 Java 接口定义,这个定义必须对本地和远程进程都可见。它
包含两个内部类,如下图所示:
内部类中有管理实现了你用 IDL 声明的接口的远程方法调用所需要的所有代码。两个内部类均实现了 IBinder 接口。一个用于系统在本地内部使用,你些的代码可以忽略它;另外一个,我们称为 Stub,扩展 了 Binder 类。除了实现了 IPC 调用的内部代码之外,它还包括了你声明的 RPC 接口中的方法的声明。 你应该如上图所示的那样写一个 Stub 的子类来实现这些方法。
一般情况下,远程过程是被一个服务所管理的(因为服务可以通知系统关于进程以及它连接到别的进程的
信息)。它包含着 aidl 工具产生的接口文件和实现了 RPC 方法的 Stub 的子类。而客户端只需要包括aidl
工具产生的接口文件。
下面将说明服务与其客户端之间的连接是如何建立的:
• 服务的客户端(位于本地)应该实现 onServiceConnected() 和 onServiceDisconnected() 方法。这样,当至远程服务的连接成功建立或者断开的时候,它们 会收到通知。这样它们就可以调用 bindService() 来设置连接。 • 而服务则应该实现 onBind() 方法以接受或拒绝连接。这取决于它收到的 intent(intent 将传
递给 bindService())。如果接受了连接,它会返回一个 Stub 的子类的实例。
• 如果服务接受了连接,Android 将会调用客户端的 onServiceConnected() 方法,并传递给它
一个 IBinder 对象,它是由服务所管理的 Stub 的子类的代理。通过这个代理,客户端可以对远 程服务进行调用。
线程安全方法
在一些情况下,你所实现的方法有可能会被多于一个的线程所调用,所以它们必须被写成线程安全的。对于我们上一节所讨论的 RPC 机制中的可以被远程调用的方法来说,这是必须首先考虑的。如果针对一个 IBinder 对象中实现的方法的调用源自这个 IBinder 对象所在的进程时,这个方法将会在调用者的线程中
执行。然而,如果这个调用源自其它的进程,则这个方法将会在一个线程池中选出的线程中运行,这个线
程池由 Android 加以管理,并与 IBinder 存在于同一进程内;这个方法不会在进程的主线程内执行。反过
来说,一个服务的 onBind() 方法应为服务进程的主线程所调用,而实现了由 onBind() 返回的对象(比
如说,一个实现了 RPC 方法的 Stub 的子类)的方法将为池中的线程所调用。因为服务可以拥有多于一个 的客户端,而同一时间,也会有多个池中的线程调用同一个 IBinder 方法。因此 IBinder 方法必须实现为
线程安全的。
类似的,一个内容提供者能接受源自其它进程的请求数据。尽管 ContentResolver 和 ContentProvider 类隐藏了交互沟通过程的管理细节,ContentProvider 会由 query(), insert(), delete(), update() 和 getType()方法来相应这些请求,而这些方法也都是由那个内容提供者的进程中所包涵的线程池提供
的,而不是进程的主线程本身。所以这些有可能在同一时间被很多线程调用的方法也必须被实现为线程安
全的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理