Android的多任务之路
Android真正独特的地方在于它允许多个任务同时运行。由于开发者们来自不同的平台,对这样的运行机制可能会感到惊讶。深入理解它的行为对你的应用程序设计是很重要的,因为这样可以无缝的(seamlessly)集成到android的其他版本/平台。本文涵盖了android多任务设计的原因,和它是怎样影响(impact on)你的应用程序工作的以及你要怎样才能最好的发挥出android的优势特色。
设计要素
移动设备有技术局限性,并且用户体验需求不同于桌面或web系统。下面是我们设计android多任务时的4个关键的约束条件(constraints):
我们不想要求用户在“完成”的时候关闭程序。那样的使用模式(指完成后立即关闭程序)在移动(mobile)环境工作的不是很好,特别是每天都要大频率反复使用的程序。
移动设备没有那么奢侈(luxury)的交换空间(指的内存RAM),在内存使用上又有很严格的限制。Robert Love(此人写了很多linux的书籍,本人有一本《linux系统编程》)写过一篇关于本主题的非常好的文章。
应用程序在移动设备上切换启动(switching on)是极其受到挑剔的,我们的目标是显著的减少到少于1秒钟来启动一个新的程序。当用户在很少的程序中切换时,这点尤为重要,比如说当观看视频时切换出来查看一个条新短信,然后再退回观看视频。一个显而易见的(noticeable)等待在此种状况下会很快引起用户对你的憎恨。
可用的API对编写内置的Google应用程序来说应该必须足够(be sufficient for),这也是我们的“所有程序生来平等”哲学的一部分。这就是说后台的音乐播放,数据同步,GPS导航,应用下载等必须可以让第三方开发人员用同样的API来实现。
前两点需求强调了一个有意思的冲突(conflict)。我们并不是要让使用者担心关闭他们的应用,宁可让所有的应用一直运行着。但同时,移动设备的内存使用却很有限,以至于系统会降低性能或者当它需要比可用更多的RAM时,很快就启动失败,而台式电脑,可以交换(指空间),相比起来就很简单的启动(运行)缓慢,来用页面RAM交换空间。这些相互矛盾的约束就成了android设计的动机。
什么时候应用程序“停止”呢?
一个Android多任务普遍的误解是关于一个进程(process,也可以译为程序)和一个应用(application,也可以译为程序)之间的不同之处。在Android中这两者并不是紧密联系的一个实体:应用出现在用户面前可能并不需要实际的进程运行在程序中;多个应用可以共享同一个进程,或者一个应用可能使用多个进程,这些都取决于你是否需要;当应用并不积极的做什么事情的时候,它的进程就会由Android保存。
实际上你看一个应用的进程“运行”不是说这个应用在运行或做什么事情。它简单的在那里可能只是因为Android在某些时候需要它,并且让它最好还是在那里因为可能还要再次需要它。同样地,你可能会离开一个应用一会儿,并从停止的地方回来,在这段时间,Android可能需要取消(get rid of)进程的其他事情。
Android此时处理应用的一个关键是进程不会干净的关闭。当用户离开一个应用程序,它的进程一直在后台存在着,如果需要的话,允许它继续运行(例如下载网页),当用户返回的时候能够立刻回到前台。如果一个设备不出现内存溢出的话,Android将保证所有的进程都在,真正的让所有应用永远“运行”。
当然,这里有个内存的限制,为了解决(accommodate)这个问题,Android必须决定何时移除不再需要的进程。这引出了Android进程的生命周期,这个规则用来决定每个进程的重要程度,哪些进程需要被抛弃。这些规则基于两点,一个是用户当前使用中的进程的重要级别,另一个是进程从上次用户需要到现在经过了多长时间。
一旦Android决定要移除一个进程,它会残忍的(brutally)、简单的强制杀掉它。内核会立刻回收再利用(reclaim)进程的所有资源,不需要应用程序良好的编码和礼貌的响应退出请求。允许内核立即回收再利用资源就可以很容易避免一系列的内存溢出问题。
如果一个用户要回到一个程序,而这个程序恰巧被杀掉了,Android需要一种途径重新加载它并回到最后看到的状态,来保持“所有的程序永远运行”的用户体验。通过记录(is keep track)用户知道(is aware of)的程序的一部分(activity)的信息,就是他们看到的最后状态,重新启动它们。这里说的最后的状态是指用户离开程序时的状态,并不是它被杀掉时的状态,所以内核不用依赖程序在某一点的正确响应而自由的杀掉它。
有些情况,Android的进程管理可以看作为是某种形式的空间交换:程序的进程代表一定数量的(a certain amount of)在用内存;当内存过低时,杀掉一些进程(交换出去);当又需要那些进程时,它们可以从最后的状态重新启动(交换进来)。
明确地运行在后台
到目前为止,我们有一种不明确的方法令程序运行在后台,就是只要求进程不被Android的内存管理部分杀掉。这对于像在后台加载网页等任务还行,如果有更高的要求怎么样?比如后台播放音乐,数据同步,定位、闹钟等。
对于这些任务,应用程序需要一种途径来告诉Android“我在这一点上要明确的运行”。这里有两种主要的设施可用来为应用程序解决此问题,它们以两种组件为代表,广播和服务,可以在它们的清单中(manifest)注册。
广播接收器
广播接收器允许一个应用程序运行,在短暂的时间里,在后台处理一些发生的事情。它可以用多种途径来构建高级别的设施:例如警告管理器允许程序在未来某个时间发送一个广播,位置管理器可以在发现一个感兴趣的位置变化时发送一个广播。因为接收器的信息是程序清单(manifest)的一部分,Android可以在这个程序没有运行时找到并加载它;当然,如果它的进程在后台可用,广播就可以高效的直接投递给它。
在处理广播时,程序会给定一个允许它工作的时间(当前是10秒钟)。如果在这个时间内没有完成,程序就会被认为是违反规定的(misbehaving),它的进程会立即被扔到(tossed)后台状态,当内存需要时被杀掉。
广播接收器擅长做一些需要响应外部刺激的工作,例如发送一个新的GPS位置报告后给用户发布一个通知。在程序的进程因接收广播而露面后,它们就变成了非常轻量级的。因为它们是再特定的时间活动,有很强的保证在它们运行时进程不会被杀掉。然后这并不适用于(appropriate for)一些不确定时间的情况,如网络。
服务
服务允许应用程序实现长时间的后台操作。当然了,事实上服务也提供了很多其他方法,但这里我们讨论的是服务的最基本目的,一个应用程序说“嗨,我想要持续运行我的程序在后台,直到我说我完成了。”应用程序控制服务的运行,通过明确的启动和停止它。
当服务提供富客户服务器模型时,使用它是可选择的。一旦启动了程序的服务,Android就会实例化这个组件并在程序的进程中提供上下文。在这之后如何使用取决于程序:它可以在服务中放入所有需要的没有和程序的其他部分交互的代码,在其他单例(singleton)对象共享给程序的其他部分调用,直接恢复服务实例在其他需要的地方,或者在另一个进程中运行并在需要时做一个完全的(full-blown)远程过程调用(rpc)。
服务的进程管理与广播接收器的不同,因为不清楚数量的服务可以运行未知长度的时间。这里可能没有足够的RAM来满足服务运行的要求,所以不能够强烈保证它们能够永久运行。
如果只有特别少的RAM,进程维护的服务会像后台进程一样立即被杀掉。然而,如果合适的话,Android会记下那些愿意继续运行的服务,在有足够RAM后再重新启动它们的进程。例如,如果用户使用网页需要大量的RAM,Android会同步的杀掉后台服务进程直到浏览器对内存的需求降低。
服务可以更深层次的讨论它们被认为是“前台”的行为。这里服务就处在一个“请不要杀掉我”的状态,但是这需要它包含一个给用户的积极运行的通知。这对于后台播放音乐或车载导航的服务非常有用,因为用户知道(aware of)这些;当你播放音乐和使用浏览器时,你可以经常在状态栏上看到音乐播放的标记(glyph)。Android不会尝试去杀掉这些服务,不过作为一种交换(trade-off),要保证用户知道它们并且可以在需要时明确的停止。
通用组件的值
Android的通用的广播接收器和服务组件允许开发者创建一个广泛的多种多样的有效后台操作,包括一些最初从没有考虑过的事情。在Android 1.0,它们被用来实现近乎所有内置的后台行为和所有的Google应用:
音乐播放运行在服务中,当用户离开音乐程序时允许继续对其进行操作。
闹钟通过闹钟管理器安排一个广播接收器,在下一次设置时间响起(go off)。
日历程序同样地(likewise)在下一个日历事件中安排一个闹钟在适当时间来显示或更新它的通知。
当进程中有下载时,后台文件下载通过服务来实现。
电子邮件程序安排一个闹钟每隔一段时间或由新邮件到来时来唤醒一个服务。
Google应用程序维护一个服务来接收网络上的通知,并依次(in turn)发送广播给那些需要做工作的单独的程序比如同步联系人。
随着平台的演化,这些基本的组件用来实现很多重要的新的开发特性:
输入法被开发者用服务组件来实现了,Android管理和使用其为当前的输入法。
程序的widget(窗体小部件)是广播接收器,当其需要交互时,Android就给它发送一个广播。这需要widget很轻量级的,并不需要它的程序进程一直保持运行。
可接入特性(Accessibility features)是用服务实现的,使用时由Android维持运行并发送适当的用户交互的信息。
同步适配器是在Android2.0中引入的,它是运行在后台的服务,当特殊的数据同步需要时被执行。
活动壁纸是一个服务,当被用户选择时就会被Android启动。