http://www.adobe.com/cn/devnet/air/articles/considerations-air-apps-mobile.html
Adobe AIR 经过发展演进,已经超越了最初作为桌面应用程序平台的目标。如今,它支持跨移动、桌面和数字家用设备的独立应用程序开发。AIR 是一种极富吸引力的开发平台,部分原因在于其广泛的覆盖面。与此同时,这些环境中的每一种环境都给移动应用程序开发与设计带来的独特的要求。
举例来说,移动应用程序常常是短期运行的。它们需要一种可在较小的屏幕上使用的 UI,通常也需要能够外扩到平板电脑,并支持不同的屏幕方向。它们必须支持触摸输入,同时集成此类设备独有的硬件和软件设施。它们还必须考虑移动设备的内存和图形模型。
这篇文章描述了 AIR 为支持移动应用程序开发而提供的特性和设计方法。文中介绍的特性和方法将帮助您开发可在安卓、BlackBerry Tablet OS 和 iOS 设备以及智能手机和平板电脑上运行的应用程序。
在将移动设备作为开发目标时,最先想到也是最重要的考虑事项就是屏幕。这种屏幕相对较小,无论是从物理方面还是从能够显示的像素数方面来考虑都是如此。它还有着较高的密度(每英寸像素数),不同的设备有着不同的密度和维度组合。移动设备还可能采用水平或垂直方向放置。
为了跨这类多样化的尺寸和密度正常操作,AIR 提供了对以下关键 API 的支持。
Stage.stageWidth
、Stage.stageHeight
:这两个属性在运行时提供了实际的屏幕维度。请注意,在应用程序进入或退出全屏模式时,或者在屏幕旋转时,这些值可能会发生变化。(后文将进一步介绍旋转。)Capabilities.screenDPI
:这提供了屏幕上每英寸的像素数。
通过将这些属性提供的信息相结合,应用程序即可为广泛的屏幕调整器显示——甚至可调整到在编写应用程序时未曾预计到的尺寸和维度。
注意: 如果您正在 AIR 上构建桌面应用程序,应该注意移动应用程序仅有一个 Stage,NativeWindow 类无法使用。无法使用表示该类可以被引用和实例化,但这样做不会产生任何效果。这使得编写能够在两种环境中正常运作的共享代码成为不可能的任务。要检查 NativeWindow 是否可用,请查询 NativeWindow.isSupported
。
移动应用程序不需要支持屏幕旋转,但至少应该考虑并非所有移动设备的默认设置均为垂直方向(高大于宽)的显示。不希望支持屏幕旋转的应用程序可在应用程序描述符内将 <autoOrients>
设置为false
,从而选择完全放弃。希望处理旋转的应用程序可以将 <autoOrients>
设置为 true
来选择采用,随后侦听 Stage 的REORIENTING
和REORIENT
事件。请注意,并非所有移动平台都会分发 REORIENTING
事件,但它们均会分发 REORIENT
事件。
另外有必要注意,应用程序不需要使用内置的自动方向特性来处理屏幕旋转。然而,如果您希望匹配系统行为,内置的事件将是最适用的。举例来说,在某些带有滑出式物理键盘的设备上,系统放行将更改为与键盘一致,即便在设备本身实际上并未发生旋转时也是如此。对于需要文本输入的应用程序,可能有必要在这种情况下重新定向。对于其他应用程序(例如游戏),则可能需要关闭自动方向调整,转为监视加速计事件来确定设备的物理方向。我将在本文稍后的内容中介绍加速计。
在应用程序显示到屏幕上之后,通常就准备好了接受用户的某些输入。对于移动应用程序来说,这就意味着接受触摸输入。
AIR 可自动将简单的单指手势(例如单指轻敲按钮)映射到对应的鼠标事件。这使得编写能够以合情合理的方式在移动平台和桌面平台中运行的共享代码成为可能。
对于更加复杂的交互,您需要利用多点触控输入。面向移动的 AIR 通过支持以下关键 API 提供了多点触控支持:
- Multitouch:这个控制器类允许应用程序确定有哪些触摸事件和手势事件可用,并选择要使用的事件。
- TouchEvent:在处理原始触摸事件时,应用程序将接收到这种类型的事件。
- GestureEvent, PressAndTapGestureEvent, TransformGestureEvent:在处理手势时,应用程序将接收到这些事件。
对于处理基础平台的标准手势事件(例如,收拢或张开两根手指以便放大或缩小)的应用程序来说,应将Multitouch.inputMode
设置为 MultitouchInputMode.GESTURE
。系统可将多个触摸点综合成手势,并为各手势提供手势事件。举例来说,一个放大手势将分派为类型是TransformGestureEvent.GESTURE_ZOOM
的TransformGestureEvent
。
应用程序还可选择接受原始触摸事件,方法是将Multitouch.inputMode
设置为MultitouchInputMode.TOUCH_POINT
。系统将为每次触摸分派一系列事件,表示触摸点从何时开始,如何逐渐移动,以及在何时结束。除此之外,多个触摸点可以同时发生。应用程序负责将这种事件流综合成为一些有意义的内容。
此外还需要特别考虑带有软键盘(显示在屏幕上的键盘,无物理按键)的移动设备。尽管并非所有移动设备都使用软键盘,但此类设备已经日益普及化,因此您应确保应用程序能够很好地在配备软键盘的设备中工作。
在可见情况下,软键盘无疑要占用一定的可用屏幕空间。为了适应这种情况,AIR 默认将调整 Stage,使得文本输入控件和键盘同时保持可见。在这种情况下,Stage 的调整通常是上推,因此,Stage 的最上端将被屏幕上端截断,无法看到。
应用程序可以禁用这种行为,实现自己的逻辑来支持软键盘。这种行为由应用程序描述符中的softKeyboardBehavior
设置控制。默认设置为 pan
。要实现您自己的逻辑,请使用 none
。
如果默认调整行为被禁用,软键盘被激活或取消激活,则 AIR 将通过Stage.softKeyboardRect
报告 Stage 中被键盘覆盖的区域。应用程序应监听在此值更改时得到通知的SoftKeyboardEvent,随后相应地调整其布局。(将同时为这些软键盘行为分派 SoftKeyboardEvent。)
应用程序通常不需要为激活软键盘而担心,因为文本字段获得焦点时将自动激活软键盘。应用程序还可以设置InteractiveObject.needsSoftKeyboard
,请求为任何可获得焦点的交互式对象显示软键盘,并通过InteractiveObject.requestSoftKeyboard()
要求立即显示键盘。这些 API 对于未使用软键盘的设备不会产生任何效果。
移动设备用户通常不适应使用多点触控屏幕与其移动应用程序交互——他们也希望应用程序能够了解位置,并根据设备的物理方向和运动情况作出反应。AIR 通过两个关键 API 对此提供了支持:
- Geolocation:这个 API 会分派事件来提供设备的地址位置(经度和纬度)以及运动情况(移动方向、速度)。
- Accelerometer:这个 API 会分派事件,报告当前沿 x、y和z 轴施加给设备的力量。
对于某些应用程序来说,地理位置是应用程序操作所固有的特点。例如,一个能过发现最近的 ATM 的应用程序。更多应用程序可利用此信息来加强用户体验。举例来说,一个语音备忘录应用程序可以记录您记录各备忘录的位置,以便在回放时提供更多的上下文。
正如前文所述,如果您希望了解设备的实际方向而不仅仅是它的逻辑方向,那么加速计输入可能会非常有用。加速计数据也能将设备本身转为一个控制器。许多应用程序都利用了这一点,通过设备的倾斜或旋转来控制应用程序本身。
所有这些传感器 API 都允许调用程序设置所要求的更新间隔,也就是说,位置和加速度的更新将以所要求的这个频率分派给任何监听程序。请注意,任何一种方法都不能保证这样的更新频率。实际更新频率将取决于多种因素,包括基础硬件在内。
如果没有 HTML 内容支持,任何现代应用程序运行时都是不完整的,面向移动的 AIR 通过 StageWebView API 提供了这样的支持。StageWebView 为 AIR 应用程序提供了一种访问目标平台的基础、内置 HTML 呈现功能的方法。请注意,由于 StageWebView 使用平台 HTML 控制,因此不能保证在各个平台之间实现一致的呈现。它能保证的是在与之运行的平台一致的平台上呈现一致的内容。如果您正在使用它来托管 Web 页面,这很可能会满足您的用户的期待。
由于依靠本机平台控制,因此 StageWebView 并未集成显示列表。而是浮于所有其他内容的表面。可将其视为直接附加到 Stage——正如其名称所示。StageWebView 控件的内容可通过 drawViewPortToBitmapData()
捕捉为位图,可放置在显示列表中。这可用于支持 Web 页面的快照参与屏幕过渡动画(举例来说)。
对于熟悉 AIR 中的 HTMLLoader API 用户来说,有必要注意,StageWebView 不是适当的替代方案。HTMLLoader 包含内置的 HTML 呈现功能,支持托管在应用程序包含的浏览器沙箱以外运行的托管 HTML 和 JavaScript。StageWebView 只能托管在传统浏览器沙箱内运行的 HTML 和 JavaScript 内容,它不能托管应用程序本身。
如果您的用户希望转到浏览器,您可以调用 navigateToURL()
来启用它。如果对该应用程序注册的 URL 前缀调用,那么这也会将用户重定向到其他应用程序,例如 YouTube 或 Google Maps。
在拍照方面,当今的移动设备的问题并非是否有摄像头,而是有多少个摄像头。针对移动的 AIR 包括的全新 API,提供了与摄像头以及设备内已经存储的任何照片的集成。
CameraUI 和 CameraRoll 类
内置摄像头功能可通过全新的 CameraUI 类访问。正如其名称所展示的那样,它与您熟悉的 Camera 类不同,区别在于这是一种摄像头用户界面的 API,而并不是摄像头的直接 API。根据设备的不同,这就意味着用户可能具备在静态拍照与视频录制之间选择的能力,同时也能选择不同的分辨率、开关闪光灯、选择前摄像头和后摄像头等。
移动设备不仅可以拍照,还会存储照片。用户已拍摄图片的库可通过 CameraRoll 类访问。browseForImage()
方法可用于打开设备的标准 UI,以便从库中选择照片。相册同样可以写入:图片可通过 addBitmapData()
方法存储到库中。
MediaPromise 类
CameraUI 和 CameraRoll 均通过一种称为 MediaEvent 的新事件类型返回选定的图片。MediaEvent 极为简单,只是为其 Event 父类添加了另外一个有趣的成员:data。data 成员的类型是 MediaPromise,必须通过此类访问图片数据。
正如其名称所示,MediaPromise 是提供与一个媒体项相关的数据的承诺,例如图片。但它并不一定存储这些字节。这样的差别是非常重要的,值得花几分钟来研究这种 API,以便理解如何有效地利用它。
最好在内存中保存媒体项,还是在存储中保存媒体项取决于多种因素。举例来说,对于视频,通常必须将其保存在存储中,因为可用内存往往过小;如果媒体项位于设备的相册库中,则表示其已经处于存储之中,除非有必要,否则不应将其读入内存。另一方面,一张刚刚拍摄的静止的照片通常存储在内存中,因为它很可能足够小,而且可能要立即显示。
MediaPromise 类在单独一个谨慎使用即可有效利用的对象中容纳了这样的不确定性。如果应用程序希望在存储中保存媒体项以便释放内存空间,那么可以检查 MediaPromise.file 中是否存在非空值,从而轻松检查该项是否已经位于存储之中。在处理视频时,这可能成为拥有足够的存储或者耗尽存储的区别。
如果应用程序希望在内存中处理媒体项,那么它将始终可通过以 MediaPromise.open()
访问的流读取。根据项的位置,MediaPromise 将从内存中的副本或者存储中自动返回这些字节。在使用 open()
时,应该确保检查MediaPromise.isAsync
,以便确定已经返回的流的类型。
最后,要处理所返回的媒体项将添加到现实列表中的常见情况,可使用一种名为Loader.loadFilePromise()
的新方法扩展 Loader 类。这允许将项直接添加到现实列表中,优化了应用程序节点中任何可能不必要的副本。正如方法的名称所表示的那样,这种方法可与任何 FilePromise 配合使用。MediaPromise 类实现了 IFilePromise 接口。
在移动设备上,应用程序具有一个几乎无法由自己控制的生命周期。它们无法自行启动,但可以由用户直接启动(例如,在从主屏幕上启动时),或者由用户简介启动(例如,通过注册的 URL 模式)。它们可以随时发送到后台。在后台运行时,它们也可随时停止,这通常是在设备中的资源不足以供前台应用程序使用时发生的。
移动应用程序无法自行启动或者关闭。在某些移动平台上,NativeApplication.exit()
是不能操作的(“无作业”)。应用程序不应依靠在关闭过程中保存状态,而是应该在发送到后台时和/或在运行时定期地保存状态。
在通过分派 DEACTIVATE
事件发送到后台时,以及相应地通过分派 ACTIVATE
事件转至前台时,应用程序应该得到通知。在应用程序过渡到后台和前台时,AIR 也会采取一些特定操作。具体情况依平台的不同而有所不同。
安卓后台行为
在安卓平台上,应用程序被鼓励执行尽可能少的后台操作,但并未施加服务器约束。如果安卓平台上的一个 AIR 应用程序发送到后台,则其动画帧速率将减至每秒四帧,尽管所有事件都将继续分派,但事件循环的呈现阶段将被跳过。
因此,安卓平台上的 AIR 应用程序可以继续执行后台任务,例如完成一次上传或者下载操作,或者定期同步信息。然而,在后台运行时,应用程序应该采取措施来进一步降低其帧速率,关闭或减少其他计时器等等。
iOS 后台行为
在 iOS 上,应用程序不允许像通常那样在后台运行,而是必须声明它们希望执行某种类型的后台处理,例如保持一次 IP 语音通话继续,或者完成一次未完成的上传。
AIR 不支持这种 iOS 后台处理模型,因此在发送到后台时,AIR 应用程序将直接暂停。其帧速率将降低为零,不会分派任何事件,也不会呈现任何内容。然而,它们默认将主流在内存中。这允许应用程序在转回前台时保留其状态。
要在移动应用程序中实现出色的性能,首先最好为应用程序的各个方面选择一种可靠的基本方法。尝试为线性时间算法实现 10% 的改进所获得的成果显然无法与在合理位置使用常量时间算法所能够获得的成果相提并论。
启动时间
启动时间极具挑战性,因为其成本往往会波及整个应用程序。为了将启动成本保持在最低限度,应重点关注运行尽可能少的代码,而不是提高代码的运行速度。
举例来说,假设您正在编写一款游戏,在第一个屏幕上,您希望显示当前的最高得分,这些信息是在本地保存的。执行这些代码来检索这些得分可能会带来令人意外的高昂成本。因为这是代码路径首次运行,您可能需要付出解释或编译代码的成本,因此它的速度将慢于稳定状态的 ActionScript 性能。第二,您要等待从文件系统中检索信息。最后,您要付出在屏幕上排版和呈现信息的成本。
您应考虑推迟所有这些工作,直至显示第一个屏幕之后。随后,在用户专心欣赏您的艺术作品时,即可准备最高得分列表。最后,您可以通过淡入或者动画效果将其显示在屏幕上。
请注意,这里的优化模式涉及选择何时执行工作,而不是尽快执行工作。这里最重要的是用户所感受到的性能:用户只有在等待工作完成时才会注意到这些事情。
呈现
GPU 的兴起已经彻底颠覆了典型呈现管道的性能特征。在 CPU 上呈现时,每个像素都有着较高的成本。因此最好通过描述形状来进行呈现,同时执行预处理,使得屏幕上的每个像素仅绘制一次。这是 AIR 在呈现传统基于矢量的内容时所采用的基本方法。
另一方面,GPU 对于形状的呈现不佳,但能够轻松四处移动海量像素——所移动的像素数量往往超过实际适合显示在屏幕上的像素数量几倍之多。利用 GPU 的最佳方法就是通过一组位图构成 UI,随后仅限于转变这些位图。
AIR 集两者之所长。您可以利用 AIR 呈现模型的完整功能来进行绘制,随后将结果作为位图缓存,位图可有效地呈现到屏幕上。利用BitmapData.draw()
即可通过这种方式捕捉您呈现的结果。
请注意,也可将这些位图与您的应用程序打包在一起,而不是动态呈现它们,屏幕大小和密度的增加使得预先生成所有必要的变体成为不可能完成的任务。因此这种方法不仅高速,还能很好地适应当今的设备扩张。
内存
尽管当今的移动设备包含大量的 RAM,但有必要牢记,它们所使用的内存管理模型与传统桌面操作系统不同。在桌面上,如果内存需求过高,则内存的内容可溢出到磁盘中,稍后再返回内存。这使操作系统能够保持几乎无数的程序同时运行。
在移动设备上,这种溢出到磁盘的方法不可用。反之,如果内存需求超出了物理可用内存,则后台应用程序将被强制退出,从而释放其占用的内存。如果完全无法满足某个对于内存的请求,则请求内存的应用程序本身将退出。
这里有两个要点。首先,有必要大体了解您的应用程序的总体内存需求,以便确保不会耗尽内存。其次,为了提高应用程序在后台运行时保持驻留的机会,您的应用程序在后台时必须使用尽可能少的内存。
这些目标均可通过显式管理应用程序的内存来实现。乍听起来,这或许有些奇怪,毕竟垃圾收集器应该代替您完成这样的工作。您最好将垃圾收集器视为某种为您清空垃圾的机制。然而,将无用对象丢到垃圾箱中的决定仍然由您做主。
采用显式内存管理方法时,第一个步骤就是确保您清除了对于不再需要的对象的引用。举例来说,假设您的应用程序在启动时读取一个 XML 配置文件,随后从这个文档中复制一些重要的值。现在,在这个过程中创建的 XML 对象树很有可能不再必要。然而,应用程序也有可能保留了一个对根 XML 对象的引用,从而将整个 XML 文档纳入内存。在读取了配置值之后,应用程序应将对 XML 文档的引用设置为空,从而将这个对象置于垃圾箱中,使之可进行垃圾收集。
在处理大量给定对象时,显式内存管理也是非常重要的。举例来说,对于一个加载一组图片的应用程序来说,如果采用了本机写入,那么如果该组图片数量过大,那么它总是会耗尽内存。另外一方面,如果实现限制了一次可保存在内存中的图片数量,那么无论这组图片的数量有多少,内存始终不会耗尽。这可通过在加载新图片之前释放旧图片来实现,甚至可以采用更有效的方法,即在内存中保留固定数量的对象,并通过它们来循环处理图片。
移动设备提供了本地文件系统,应用程序可利用这种本地文件系统来存储首选项、文档等。通常来说,应用程序应该假设此存储即可由应用程序本身访问,不得与其他应用程序共享。在所有平台上多可以通过File.applicationStorageDirectory
属性访问这个存储。
安卓实现了一个辅助文件系统,它通常位于可用 SD 卡内,通过“/sdcard”路径访问。不同于主应用程序存储,这些位置可由设备上的所有应用程序读取和写入。然而,应用程序应注意,这样的辅助存储并非始终可用,因为 SD 卡可能会被取出,即便未被取出,也可能未挂接。
移动设备上的摄像头日益普及,它们也提供了一种特定于相片的共享存储位置。正如我在之前的“图片”一节中所示,应用程序通常应该通过 CameraRoll API 访问这样的位置。尽管在某些平台上,已存储的相片可通过文件系统 API 直接访问,但这并不是一项适合所有平台的实践。
在移动领域中,部署主要是通过应用市场完成的。这些市场中包括设备上的发现、安装和更新功能。
为了准备 AIR 应用程序以便部署到特定市场,应将其打包为恰当的特定于平台的格式。举例来说,要将您的应用程序上传到 Apple App Store,您应将应用程序打包为 .ipa 文件;要将其上传到安卓市场,则应将其打包为 .apk 文件。这些选项在 Flash Builder 内即可找到,也可通过 ADT 命令行工具编写脚本实现。
所有移动应用市场都要求发布到其中的应用程序进行签名。对于 iOS,签名必须使用 Apple 发行的证书 完成。对于安卓设备,开发人员应该 创建一个有效期至少为 25 年的自签名证书,并且必须使用相同的证书为其应用程序的所有更新签名。由于有着不同的证书要求,因此要对多个市场发布,就需要 跟踪多种证书。
在您准备将一个移动应用程序部署到安卓市场时,务必牢记 AIR 本身是独立部署的。(在 iOS 上,各应用程序均打包了一份 AIR 副本,因此本讨论不适用。)如果您的应用程序要安装在一个尚未安装 AIR 的设备上,那么在应用程序初次启动时,用户将被重定向,以便安装 AIR。您应该尽可能地确保这种重定向会将用户返回其购买您的应用程序时所使用的市场。为此,您可以在调用 ADT 命令行工具时通过 -airDownloadURL
标记传递针对该市场的恰当 URL。如需确定应使用的正确的 URL,请与应用市场联系。
使用 Adobe AIR 开发移动应用程序使您可以创建单独一个跨多种安卓、iOS、BlackBerry Tablet OS 智能手机和平板电脑部署的应用程序。
AIR 之所以能够实现这样的能力,是因为它在必要时(例如用于访问相册)提供了跨平台的抽象,能够动态发现设备属性(例如屏幕尺寸),此外也能在必要时给您让路(例如,使用文件系统 API 访问文件系统的任何部分)。
构建跨设备的应用程序也要求开发人员了解内存、应用程序生命周期以及移动开发的其他特有方面。将这样的知识与 AIR 运行时相结合,即可实现功能强大、跨设备的移动应用程序的快速创建。
有关使用 Adobe AIR 开发移动应用程序的更多信息,请访问移动和设备开发人员中心中的 移动应用程序开发资源,以及 Adobe AIR 开发者中心。