深入Android(安卓)【五】 —— 任务和进程
任务、进程和线程
原文地址:http://www.cnblogs.com/duguguiyu/archive/2010/02/22/1671547.html
关于Android中的组件和应用,之前涉及,大都是静态的概念。而当一个应用运行起来,就难免会需要关心进程、线程这样的概念。在Android中,组件的动态运行,有一个最与众不同的概念,就是Task,翻译成任务,应该还是比较顺理成章的。
Task的介入,最主要的作用,是将组件之间的连接,从进程概念的细节中剥离出来,可以以一种不同模型的东西进行配置,在很多时候,能够简化上层开发人员的理解难度,帮助大家更好的进行开发和配置。任务
在SDK中关于 Task( guide/topics/fundamentals.html#acttask),有一个很好的比方,说,Task就相当于应用( application)的概念。在开发人员眼中,开发一个Android程序,是做一个个独门独户的组件,但对于一般用户而言,它们感知到的,只是一个运行起来的整体应用,这个整体背后,就是Task。 Task,简单的说,就是 一组以栈的模式聚集在一起的Activity组件集合。它们有潜在的前后驱关联,新加入的Activity组件,位于栈顶,并仅有在栈顶的Activity,才会有机会与用户进行交互。而当栈顶的Activity完成使命退出的时候,Task会将其退栈,并让下一个将跑到栈顶的Activity来于用户面对面,直至栈中再无更多Activity,Task结束。事件 | Task栈(粗体为栈顶组件) |
点开Email应用,进入收件箱(Activity A) | A |
选中一封邮件,点击查看详情(Activity B) | AB |
点击回复,开始写新邮件(Activity C) | ABC |
写了几行字,点击选择联系人,进入选择联系人界面(Activity D) | ABCD |
选择好了联系人,继续写邮件 | ABC |
写好邮件,发送完成,回到原始邮件 | AB |
点击返回,回到收件箱 | A |
退出Email程序 | null |
进程
在大多数其他平台的开发中,每个开发人员对自己应用的进程模型都有非常清晰的了解。比如,一个控制台程序,你可以想见它从main函数开始启动一个进程,到main函数结束,进程执行完成退出;在UI程序中,往往是有一个消息循环在跑,当接受到Exit消息后,退出消息循环结束进程。在该程序运行过程中,启动了什么进程,和第三方进程进行通信等等操作,每个开发者都是心如明镜一本帐算得清清楚楚。进程边界,在这里,犹如国界一般,每一次穿越都会留下深深的印迹。 在Android程序中,开发人员可以直接感知的,往往是Task而已。倍感清晰的,是组件边界,而进程边界变得难以琢磨,甚至有了进程托管一说。Android中不但剥夺了手工锻造内存权力,连手工处置进程的权责,也毫不犹豫的独占了。 当然,Android隐藏进程细节,并不是刻意为之,而是自然而然水到渠成的。如果,我们把传统的应用称为 面向进程的开发,那么,在Android中,我们做得就是 面向组件的开发。从前面的内容可以知道,Android组件间的跳转和通信,都是在 第三方介入的前提下进行,正由于这种介入,使得两个组件一般不会直接发生联系(于Service的通信,是不需要第三方介入的,因此Android把它全部假设成为穿越进程边界,统一基于RPC来通信,这样,也是为了掩盖进程细节...),其中是否穿越进程边界也就变得不重要。因此,如果这时候,还需要开发者关注进程,就会变得很奇怪,很费解,干脆,Android将所有的进程一并托管去了,上层无须知道进程的生死和通信细节。 在Android的底层,进程构造了底部的一个 运行池,不仅仅是Task中的各个Activity组件,其他三大组件Service、Content Provider、Broadcast Receiver,都是 寄宿在底层某个进程中,进行运转。在这里,进程更像一个 资源池(概念形如线程池,上层要用的时候取一个出来就好,而不关注具体取了哪一个...),只是为了承载各个组件的运行,而各个组件直接的逻辑关系,它们并不关心。但我们可以想象,为了保证整体性,在默认情况下,Android肯定倾向于 将同一Task、同一应用的各个组件扔进同一个进程内,但是当然,出于效率考虑,Android也是允许开发者进行配置。 在Android中,整体的 <application>(将影响其中各个组件...)和底下各个组件,都可以设置 <process>属性,相同<process>属性的组件将扔到 同一个进程中运行。最常见的使用场景,是通过配置<application>的process属性,将不同的相关应用,塞进一个进程,使得它们可以同生共死。还有就是将经常和某个 Service组件进行通信的组件,放入同一个进程,因为与Service通信是个密集操作,走的是RPC,开销不小,通过配置,可以变成进程内的直接引用,消耗颇小。 除了通过<process>属性,不同的组件还有一些特殊的配置项,以 Content Provider为例(通过 <provider>项进行配置...)。<provider>项有一个 mutiprocess的属性,默认值为false,这意味着Content Provider,仅会在提供该组件的应用 所在进程构造一个实例,第三方想使用就需要经由RPC传输数据。这种模式,对于构造开销大,数据传输开销小的场合是非常适用的,并且可能提高缓存的效果。但是,如果是数据传输很大,抑或是希望在此提高传输的效率,就需要将mutiprocess设置成true,这样,Content Provider就会在每一个调用它的进程中构造一个实例, 避免进程通信的开销。 既然,是Android系统帮助开发人员托管了进程,那么就需要有一整套纷繁的算法去执行回收逻辑。Android中各个进程的生死,和运行在其中的各个组件有着密切的联系,进程们依照其上组件的特点,被排入一个 优先级体系,在需要回收时,从低优先级到高优先级回收。Android进程共分为五类优先级,分别是: Foreground Process, Visible Process, Service Process, Background Process, Empty Process。顾名思义不难看出,这说明, 越和用户操作紧密相连的,越是正与用户交互的,优先级越高,越难被回收。具体详情,参见: guide/topics/fundamentals.html#proclife。 有了优先级,还需要有良好的 回收时机。回收太早,缓存命中概率低可能引起不断的创造进程销毁进程,池的优势荡然无存;回收的太晚,整体开销大,系统运行效率降低,好端端的法拉利可能被糟蹋成一枚QQ老爷车。Android的进程回收,最重要的是考量 内存开销,以及电量等其他资源状况,此外每个进程承载的组件数量、单个应用开辟的进程数量等 数量指标,也是作为衡量的一个重要标识。另外,一些运行时的 时间开销,也被严格监控,启动慢的进程会很被强行kill掉。Android会 定时检查上述参数,也会在一些很 可能发生进程回收的时间点,比如某个组件执行完成后,来做回收的尝试。 从用户体验角度来看,Android的进程机制,会有很可喜的一面,有的程序启动速度很慢,但是在资源充沛的前提下,你反复的退出再使用,则启动变得极其快速(进程没死,只是从后台弄到了前台),这就是拜进程托管所赐的。当然,可喜的另一面就是可悲了,Android的托管算法,还时不时的展现其幼稚的一面,明明用户已经明显感觉到操作系统运行速度下降了,打开任务管理器一看,一票应用还生龙活虎的跳跃着,必须要手动帮助它们终结生命找到坟墓,这使得任务管理器基本成为Android的装机必备软件。 从开发角度上来看,Android这套进程机制,解放了开发者的手脚。开发人员不需要处心积虑的构造一个后台进程偷偷默默监听某个时间,并尝试用各种各样的守护手段,把自己的进程锻造的犹如不死鸟一辉一般,进程生死的问题,已经原理了普通开发人员需要管理的范畴内。但同时,于GC和人肉内存管理的争议一样,所有开发人员都不相信算法能比自己做得效率更高更出色。但我一直坚信一点, 所有效率的优势都会随着算法的不断改良硬件的不断提升而消失殆尽,只有开发模式的简洁不会随时间而有任何变化。组件生命周期
任何架构上的变化,都会引起上层开发模式的变化,Android的进程模型,虽然使开发者不再需要密切关注进程的创建和销毁的时机,但仍然需要关注这些时间点对组件的影响。比如,你可能需要在进程销毁之前,将写到内存上的内容,持久化到硬盘上,这就需要关注进程退出前发生的一些事件。 在Android中,把握这些时间点,就必须了解 组件生命周期( Components Lifecycles)。所谓组件的生命在周期,就是在组件在前后台切换、被用户创建退出、被系统回收等等事件发生的时候,会有一些事件通知到对应组件上,开发人员可以选择性的处理这些事件在对应的时间点上来完成一些附加工作。除Content Provider,其他组件都会有生命周期的概念,都需要依照这个模型定时定点处理一些状况,全部内容参见:guide/topics/fundamentals.html#lcycles。在这里,擒贼先擒王,还是拿Activity出来作楷模。
继续偷图,来自SDK。一个自然的Activity生命旅途,从onCreate开始,到onDestroy消亡。但月有阴晴圆缺组件有祸福旦夕,在系统需要的时候且组件位于后台时,所在的进程随时可能为国捐躯被回收,这就使得知道切入后台这个事情也变得很重要。
线程
读取数据,后台处理,这些猥琐的伙计,自然少不了线程的参与。在Android核心的调度层面,是不屑于考量线程的,它关注的只有进程,每一个组件的构造和处理,都是在进程的主线程上做的,这样可以保证逻辑的足够简单。多线程,往往都是开发人员需要做的。
Android的线程,也是通过派生Java的 Thread对象,实现 Run方法来实现的。但当用户需要跑一个具有消息循环的线程的时候,Android有更好的支持,来自于 Handler和 Looper。Handler做的是消息的传送和分发,派生其 handleMessage函数,可以处理各种收到的消息,和win开发无异。Looper的任务,则是构造循环,等候退出或其他消息的来临。在Looper的 SDK页面,有一个消息循环线程实现的标准范例,当然,更为标准的方式也许是构造一个 HandlerThread线程,将它的Looper传递给Handler。 在Android中, Content Provider的使用,往往和线程挂钩,谁让它和数据相关呢。在 前面提到过,Content Provider为了保持更多的灵活性,本身只提供了同步调用的接口,而由于异步对Content Provider进行增删改查是一个常做操作,Android通过 AsyncQueryHandler对象,提供了异步接口。这是一个Handler的子类,开发人员可以调用 startXXX方法发起操作,通过派生 onXXXComplete方法,等待执行完毕后的回调,从而完成整个异步调用的流程,十分的简约明了。实现
整个任务、进程管理的核心实现,尽在 ActivityManagerService中。 上一篇说到,Intent解析,就是这个ActivityManagerService来负责的,其实,它是一个很名不副实的类,因为虽然名为Activity的Manager Service,但它管辖的范围,不只是Activity,还有其他三类组件,和它们所在的进程。 在ActivityManagerService中,有两类数据结构最为醒目,一个是 ArrayList,另一个是 HashMap。ActivityManagerService有大量的ArrayList,每一个组件,会有多个ArrayList来分状态存放。调度工作,往往就是从一个ArrayList里面拿出来,找个方法调一调,然后扔到另一个ArrayList里面去,当这个组件没对应的ArrayList放着的时候,说明它离死不远了。HashMap,是因为有组件是需要用名字或Intent信息做定位的,比如Content Provider,它的查找,都是依据Uri,有了HashMap,一切都顺理成章了。ActivityManagerService用一些名曰xxxRecord的数据结构,来表达各个存活的组件。于是就有了,HistoryRecord(保存Activity信息的,之所以叫History,是相对Task栈而言的...),ServiceRecord,BroadcastRecord,ContentProviderRecord,TaskRecord,ProcessRecord,等等。
值得注意的,是TaskRecord,我们一直再说,Task栈这样的概念,其实,真实的底层,并不会在TaskRecord中,维系一个Activity的栈。在ActivityManagerService中,各个任务的Activity,都以HistoryRecord的形式,集中存放在一个ArrayList中,每个HistoryRecord,会存放它所在TaskRecord的引用。当有一个Activity,执行完成,从概念上的Task栈中退出,Android是通过从当前HistoryRecord位置往前扫描同一个TaskRecord的HistoryRecord来完成的。这个设计,使得上层很多看上去逻辑很复杂的Task体系,在实现变得很统一而简明,值得称道。 ProcessRecord,是整个进程托管实现的核心,它存放有运行在这个进程上,所有组件的信息,根据这些信息,系统有一整套的算法来决议如何处置这个进程,如果对回收算法感兴趣,可以从ActivityManagerService的 trimApplications函数入手来看。 对于开发者来说,去了解这部分实现,主要是可以帮助理解整个进程和任务的概念,如果觉得这块理解的清晰了,就不用去碰ActivityManagerService这个庞然大物了。更多相关文章
- android widget开发点滴
- 收藏老罗的----------Android应用程序组件Content Provider简要
- Android应用开发———Android中的常用UI组件
- 【转】Android之大话设计模式与企业级开发
- Android(安卓)模拟系统事件(三)
- 系统角度解读Android(安卓)P新特性
- Android中Service与Thread的区别
- Android---44---使用AIDL Service 实现跨进程调用Service
- Android(安卓)7.0 ActivityManagerService(5) 广播(Broadcast)相