開發Adobe AIR移動應用程序的考慮事項
舉例來說,移動應用程序常常是短期運行的。它們需要一種可在較小的屏幕上使用的 UI,通常也需要能夠外擴到平板電腦,并支持不同的屏幕方向。它們必須支持觸摸輸入,同時集成此類設備獨有的硬件和軟件設施。它們還必須考慮移動設備的內存和圖形模型。
這篇文章描述了 AIR 為支持移動應用程序開發而提供的特性和設計方法。文中介紹的特性和方法將幫助您開發可在安卓、BlackBerry Tablet OS 和 iOS 設備以及智能手機和平板電腦上運行的應用程序。
目錄
要求
預備知識
熟悉 Adobe AIR 和移動設備開發的基本概念。
必須的產品
屏幕
在將移動設備作為開發目標時,最先想到也是最重要的考慮事項就是屏幕。這種屏幕相對較小,無論是從物理方面還是從能夠顯示的像素數方面來考慮都是如此。它還有著較高的密度(每英寸像素數),不同的設備有著不同的密度和維度組合。移動設備還可能采用水平或垂直方向放置。
為了跨這類多樣化的尺寸和密度正常操作,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 都允許調用程序設置所要求的更新間隔,也就是說,位置和加速度的更新將以所要求的這個頻率分派給任何監聽程序。請注意,任何一種方法都不能保證這樣的更新頻率。實際更新頻率將取決于多種因素,包括基礎硬件在內。
WEB 視圖
如果沒有 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 誕生并發展出新穎的安裝技術以來,Oliver 一直致力于這個領域。在轉向 AIR 之前,他致力于 Adobe LiveCycle,而在加入 Adobe 之前,他從事的領域很廣,包括金融服務、數字信號處理和視頻游戲。他有時為 Dr. Dobb's Journal 撰稿,并且是 Kidos Computer 技術咨詢委員會的成員。他獲得了斯坦福大學的計算機本科及碩士學位。