1.3 进程和线程
当应用程序的第一个组件需要运行时,Android会为它启动一个Linux进程,及单一的执行线程。默认情况下,应用程序所有的组件均在这个进程、线程中运行。
然而,你也可以安排组件在其他进程中运行,而且可以为任意进程衍生出其它线程。
1.3.1 进程
组件运行的进程由manifest文件控制。组件元素——<activity>,<service>,<receiver>和<provider>每个都有一个process属性来指定组件希望运行的进程。可以设置这些属性让每个组件运行于自己的进程之内,或一些组件共享一个进程而其余的组件拥有独立的进程。我们也可以设置让不同应用程序的组件在同一个进程运行——使应用程序的组成部分共享同一个Linux用户ID并赋以同样的权限。<application>元素也有一个process属性,以设定所有组件的默认属性值。
所有的组件实例都位于指定进程的主线程内,而对这些组件的系统调用也将由那个线程进行分发。一般不会为每个实例创建线程。因此,某些方法总是运行在进程的主线程内,这些方法包括诸如View.onKeyDown()这样响应用户动作以及后面组件生命周期一节所要讨论的生命周期通知。这意味着组件在被系统调用的时候,不应该执行长时间的、或者阻塞的操作(例如网络相关操作或是循环计算),因为这将阻塞同样位于这个进程中的其它组件的运行。而应该如同下面线程一节所叙述的那样,为这些长时间操作衍生出一个单独的线程进行处理。
在可用内存不足而又有一个正在为用户进行服务的进程需要更多内存的时候,Android有时候可能会关闭一个进程。此时这个进程中运行着的应用程序也因此被销毁。当再次需要这种组件进行处理工作时,会为他们重新创建进程。
在决定结束哪个进程的时候,Android会衡量它们对于用户的相对重要性。比如说,相对于一个仍对用户可见的Activity的进程,它更有可能去关闭一个其Activity已经不为用户所见的进程。因此,决定是否关闭一个进程主要依据运行在那个进程中的组件状态。这些状态将在后续的组件生命周期一节中予以说明。
1.3.2 线程
尽管可以把应用程序限制于一个单独的进程中,有时,我们仍然需要衍生出一个线程以处理后台任务。因为用户界面必须非常及时的对用户操作做出响应,所以,控制Activity的线程不应处理一些诸如网络下载之类的耗时操作。所有不能在瞬间完成的任务都应安排到不同的线程中去。
线程是由标准Java Thread对象在代码中创建的。Android提供了很多用于管理线程的类:Looper用于在一个线程中运行一个消息循环,Handler用于消息处理,HandlerThread用于建立一个带消息循环的线程。
1.3.3 远程过程调用
Android有一个轻量级的远程过程调用(RPC)机制:即在本地调用一个方法,但在远程(其它的进程中)进程执行,然后将结果返回调用者。这就需要将方法调用及其附属的数据按操作系统可以理解的方式进行分解,并将其从本地进程和地址空间传送至远程进程和地址空间,并在那里重新装配、调用。返回值必须以相反的方向进行传递。Android提供了完成这些工作所需的所有代码,使我们可以集中精力来定义和实现RPC接口本身。
RPC接口可以只包括方法。即使没有返回值,所有方法都以同步的方式执行(本地方法阻塞直至远程方法结束)。
简单的说,这套机制这样工作:首先我们用简单的IDL(接口定义语言)来声明想要实现的RPC接口。然后用 aidl 工具为声明生成一个Java接口定义,这个定义必须对本地和远程进程都可见。它包含两个内部类,如下图所示:
对于我们用IDL声明的接口远程过程调用接口,Inner类中包含有管理它所需要的所有代码。两个内部类均实现了 IBinder接口。一个由系统在本地内部使用,我们写的代码可以忽略它;另外一个,我们称为Stub,扩展了Binder类。除了实现了IPC调用的内部代码之外,它还包括了我们在RPC接口中声明的方法的声明。我们应该如上图所示的那样写一个Stub的子类来实现这些方法。
通常,远程进程由一个服务所管理的(因为服务可以将进程以及它到其他进程的连接信息通知系统)。它包含 aidl工具生成的接口文件和实现了RPC方法的Stub子类。而客户端只需要包括aidl工具生成的接口文件。
下面说明如何建立服务与其客户端之间的连接:
v 服务的客户端(位于本地)应该实现onServiceConnected()和onServiceDisconnected()方法。这样,当至远程服务的连接成功建立或者断开时,都会收到通知。它们应该调用bindService()来建立连接。
v 而服务则应该实现onBind()方法以接受或拒绝连接。这取决于它收到的Intent(传递给bindService()的Intent)。如果接受连接,则返回一个Stub子类的实例。
v 如果服务接受了连接,Android将会调用客户端的onServiceConnected()方法,并传递给它一个IBinder对象,它是由服务所管理的Stub的子类的代理。通过这个代理,客户端可以对远程服务进行调用。
这里的描述了忽略了RPC机制的某些细节。更多信息请参考Designing a Remote Interface Using AIDL和IBinder类描述。
1.3.4 线程安全方法
在某些情况下,我们所实现的方法有可能会被多个线程调用,所以它们必须是线程安全的。
对于我们上一节所讨论的RPC机制中的可以被远程调用的方法来说,这是必须首先考虑的。如果针对一个IBinder对象中实现的方法的调用源自这个 Ibinder对象所在的进程时,这个方法将会在调用者的线程中执行。然而,如果这个调用源自其它的进程,则这个方法将会在一个线程池中选出的线程中运行,这个线程池由Android进行管理,并与Ibinder存在于同一进程内;这个方法不会在进程的主线程内执行。反过来说,服务的onBind()方法应为服务进程的主线程所调用,而实现了由onBind()返回的对象(比如一个实现了RPC方法的Stub子类)的方法将为池中的线程所调用。因为服务可以有多个客户端,而同一时间,也会有多个池中的线程调用同一个Ibinder方法。因此Ibinder方法必须实现为线程安全的。
类似的,内容提供者也可能接受源自其它进程的数据请求。尽管ContentResolver和ContentProvider类隐藏了交互沟通过程的管理细节,ContentProvider方法query(),insert(),delete(),update()和getType()会响应这些请求,而这些方法是从内容提供者的进程所包涵的线程池调用的,而不是进程的主线程。所以这些方法可能在同一时间被多个线程调用,他们也必须实现为线程安全的。