一般情況Android 應用程序是由以下四種組件構造而成的:
· 活動(dòng)
· 廣播接收器
· 服務(wù)
· 內容提供器
需要注意的是,并不是每個(gè)Andorid 應用程序都必須構建這4 個(gè)組件,有些可能由這些組件的組合而成。
一旦你確定了你的應用程序中需要的組件,那么你就應該在A(yíng)ndroidManifest.xml 中列出他們。 這是一個(gè)XML 配置文件,它用于定義應用程序中需要的組件、組件的功能及必要條件等。這個(gè)文件是必須的。詳情參見(jiàn)Android manifest file documentation
四種組件說(shuō)明如下:
活動(dòng)
活動(dòng)是最基本的Andorid 應用程序組件,應用程序中,一個(gè)活動(dòng)通常就是一個(gè)單獨的屏幕。每一個(gè)活動(dòng)都被實(shí)現為一個(gè)獨立的類(lèi),并且從活動(dòng)基類(lèi)中繼承而來(lái), 活動(dòng)類(lèi)將會(huì )顯示由視圖控件組成的用戶(hù)接口,并對事件做出響應。 大多數的應用是由多屏幕顯示組成。例如,一個(gè)文本信息的應用也許有一個(gè)顯示發(fā)送消息的聯(lián)系人列表屏幕,第二個(gè)屏幕用來(lái)寫(xiě)文本消息和選擇收件人, 再來(lái)一個(gè)屏幕查看消息歷史或者消息設置操作等。
這里每一個(gè)這樣的屏幕就是一個(gè)活動(dòng),很容易實(shí)現從一個(gè)屏幕到一個(gè)新的屏幕并且完成新的活動(dòng)。 在某些情況下當前的屏幕也許需要向上一個(gè)屏幕動(dòng)提供返回值--比如讓用戶(hù)從手機中挑選一張照片返回通訊錄做為電話(huà)撥入者的頭像。
當打開(kāi)一個(gè)新的屏幕時(shí),之前一個(gè)屏幕會(huì )被置為暫停狀態(tài)并且壓入歷史堆棧中。用戶(hù)可以通過(guò)回退操回到以前打開(kāi)過(guò)的屏幕。我們可以選擇性的移除一些沒(méi)有必要保留的屏幕,因為Android 會(huì )把每個(gè)從桌面打開(kāi)的程序保留在堆棧中。
Intent 和 Intent Filters
調用Android 專(zhuān)有類(lèi) Intent 進(jìn)行構屏幕之間的切換。 Intent 是描述應用想要做什么。Intent 數據結構兩最重要的部分是動(dòng)作和動(dòng)作對應的數據。典型的動(dòng)作類(lèi)型有:MAIN(活動(dòng)的門(mén)戶(hù))、VIEW、PICK、EDIT 等。而動(dòng)作對應的數據則以URI 的形式進(jìn)行表示。例如:要查看某一個(gè)人的聯(lián)系方式,你需要創(chuàng )建一個(gè)動(dòng)作類(lèi)型為VIEW 的intent,以及一個(gè)表示這個(gè)人的URI。
與之有關(guān)系的一個(gè)類(lèi)叫IntentFilter。當intent 被要求做某事的時(shí)候,intent filter 用于描述一個(gè)活動(dòng)(或者BroadcastReceiver,看下面)能夠操作哪些intent。一個(gè)活動(dòng)如果要顯示一個(gè)人的聯(lián)系方式時(shí),需要聲明一個(gè)IntentFilter,這個(gè)IntentFilter 要知道怎么去處理VIEW 動(dòng)作和表示一個(gè)人的URI。 IntentFilter 需要在A(yíng)ndroidManifest.xml 中定義。
通過(guò)解析各種intent,從一個(gè)屏幕切換到另一個(gè)屏幕是很簡(jiǎn)單的。當向前導航時(shí),活動(dòng)將會(huì )調用startActivity(myIntent)方法。然后,系統會(huì )在所有安裝的應用程序定義的IntentFilter 中查找,找到最匹配myIntent 的Intent 對應的活動(dòng)。新的活動(dòng)接收到myIntent 的通知后,開(kāi)始運行。當start 活動(dòng)方法被調用將觸發(fā)解析myIntent 的動(dòng)作,這個(gè)機制提供了兩個(gè)關(guān)鍵好處:
· 活動(dòng)能夠重復利用從其它組件中以Intent 的形式產(chǎn)生的一個(gè)請求
· 活動(dòng)可以在任何時(shí)候被一個(gè)具有相同IntentFilter 的新的活動(dòng)取代
廣播接收器
你可以使用BroadcastReceiver 來(lái)讓你的應用對一個(gè)外部的事件做出響應。比如:當電話(huà)呼入時(shí),數據網(wǎng)絡(luò )可用時(shí),或者到了晚上時(shí)。BroadcastReceivers 不能顯示UI,它只能通過(guò) NotificationManager 來(lái)通知用戶(hù)這些有趣的事情發(fā)生了。
BroadcastReceivers 既可以在A(yíng)ndroidManifest.xml 中注冊,也可以在代碼中使用Context.registerReceiver()進(jìn)行注冊。但這些有趣的事情發(fā)生時(shí),你的應用不必對請求調用BroadcastReceivers,系統會(huì )在需要的時(shí)候啟動(dòng)你的應用,并在必要情況下觸發(fā)BroadcastReceivers。各種應用還可以通過(guò)使用Context.sendBroadcast()將它們自己的intent broadcasts 廣播給其它應用程序。
服務(wù)
一個(gè)服務(wù)是具有一段較長(cháng)生命周期且沒(méi)有用戶(hù)界面的程序。比較好的一個(gè)例子就是一個(gè)正在從播放列表中播放歌曲的媒體播放器。在一個(gè)媒體播放器的應用中,應該會(huì )有多個(gè)活動(dòng),讓使用者可以選擇歌曲并播放歌曲。然而,音樂(lè )重放這個(gè)功能并沒(méi)有對應的活動(dòng),因為使用者當然會(huì )認為在導航到其它屏幕時(shí)音樂(lè )應該還在播放的。在這個(gè)例子中,媒體播放器這個(gè)活動(dòng)會(huì )使用Context.startService() 來(lái)啟動(dòng)一個(gè)服務(wù),從而可以在后臺保持音樂(lè )的播放。同時(shí),系統也將保持這個(gè)服務(wù)一直執行,直到這個(gè)service 運行結束。(你可以通過(guò)閱讀Life Cycle of an Android Application 獲取更多關(guān)于服務(wù)的介紹). 另外,我們還可以通過(guò)使用Context.bindService() 方法,連接到一個(gè)服務(wù)上(如果這個(gè)服務(wù)還沒(méi)有運行將啟動(dòng)它)。當連接到一個(gè)服務(wù)之后,我們還可以通過(guò)服務(wù)提供的接口與它進(jìn)行通訊。拿媒體播放器這個(gè)例子來(lái)說(shuō),我們還可以進(jìn)行暫停、重播等操作。
教程:一個(gè)記事本應用程序范例
本教程通過(guò)手把手教你的方式,講解如何利用Android 框架和諸多工具建立自己的手機應用。從一個(gè)預先配置好的工程文件開(kāi)始,該教程通過(guò)一個(gè)簡(jiǎn)單記事本應用程序完整的開(kāi)發(fā)過(guò)程,并輔以貫穿始終的詳盡例子,指導你如何搭建工程、組織應用邏輯以及UI,乃至接下來(lái)的編譯及運行可執行程序等等。
該教程將這個(gè)記事本應用的開(kāi)發(fā)過(guò)程視作一組練習(見(jiàn)如下),每一個(gè)練習都由若干步驟組成。你可以亦步亦趨地完成每個(gè)練習步驟,逐步建立并完善自己的應用程序。這些練習提供了你實(shí)現此應用所需的——細到每一步驟的——具體范例代碼。
當你完成此教程后,一個(gè)具有實(shí)際功能的Android 應用就從你手上誕生了,并且你對Android 應用開(kāi)發(fā)中的一些極為重要的概念也會(huì )有更加深刻的理解。若你想為你這個(gè)簡(jiǎn)單的記事本應用添加更多復雜功能的話(huà),你可以用另一方式實(shí)現的記事本程序比照你的練習代碼,具體可參看 Sample Code 文檔部分。
本教程目標讀者
該教程主要是面向有一定經(jīng)驗,尤其是那些具備一定Java 編程語(yǔ)言知識的開(kāi)發(fā)者。如果你之前從未寫(xiě)過(guò)一個(gè)Java 應用程序的話(huà),仍可以使用此教程,只是學(xué)習進(jìn)度稍稍慢一點(diǎn)罷了。
本教程假定你已熟悉了一些基本的Android 應用概念和術(shù)語(yǔ)。如果你對這些還不夠熟稔的話(huà),你得將 Overview of an Android Application 好好溫故一下,才能繼續下面的學(xué)習。
同時(shí)需注意的時(shí),該教程的集成開(kāi)發(fā)環(huán)境是預裝Android 插件的Eclips教程e。如果你不用Eclipse,仍可做下面的這些練習和建立應用,但你屆時(shí)將不得不面對一些涉及Eclipse的步驟在非Eclipse IDE 中如何實(shí)現的問(wèn)題。
Android 應用程序模塊: 應用, 任務(wù), 進(jìn)程, 和線(xiàn)程
在大多數操作系統里,存在獨立的一個(gè)1 對1 的可執行文件(如Windows 里的exe 文件),它可以產(chǎn)生進(jìn)程,并能和界面圖標、應用進(jìn)行用戶(hù)交互。但在A(yíng)ndroid 里,這是不固定的,理解將這些分散的部分如何進(jìn)行組合是非常重要的。
由于A(yíng)ndroid 這種可靈活變通的,在實(shí)現一個(gè)應用不同部分時(shí)你需要理解一些基礎技術(shù):
· 一個(gè)android 包 (簡(jiǎn)稱(chēng) .apk ) ,里面包含應用程序的代碼以及資源。這是一個(gè)應用發(fā)布,用戶(hù)能下載并安裝他們設備上的文件。
· 一個(gè) 任務(wù) ,通常用戶(hù)能當它為一個(gè)“應用程序”來(lái)啟動(dòng):通常在桌面上會(huì )有一個(gè)圖標可以來(lái)啟動(dòng)任務(wù),這是一個(gè)上層的應用,可以將你的任務(wù)切換到前臺來(lái)。
· 一個(gè) 進(jìn)程 是一個(gè)底層的代碼運行級別的核心進(jìn)程。通常.apk 包里所有代碼運行在一個(gè)進(jìn)程里,一個(gè)進(jìn)程對于一個(gè).apk 包;然而, 進(jìn)程 標簽常用來(lái)改變代碼運行的位置,可以是 全部的.apk 包 或者是獨立的 活動(dòng), 接收器, 服務(wù), 或者 提供器組件。
任務(wù)
記住關(guān)鍵的一點(diǎn):當用戶(hù)看到的“應用”,無(wú)論實(shí)際是如何處理的,它都是一個(gè)任務(wù)。如果你僅僅通過(guò)一些活動(dòng)來(lái)創(chuàng )建一個(gè).apk 包,其中有一個(gè)肯定是上層入口(通過(guò)動(dòng)作的intent-filter 以及分android.intent.category.LAUNCHER),然后你的.apk 包就創(chuàng )建了一個(gè)單獨任務(wù),無(wú)論你啟動(dòng)哪個(gè)活動(dòng)都會(huì )是這個(gè)任務(wù)的一部分。
一個(gè)任務(wù),從使用者的觀(guān)點(diǎn),他是一個(gè)應用程序;對開(kāi)發(fā)者來(lái)講,它是貫穿活動(dòng)著(zhù)任務(wù)的一個(gè)或者多個(gè)視圖,或者一個(gè)活動(dòng)棧。當設置Intent.FLAG_ACTIVITY_NEW_TASK標志啟動(dòng)一個(gè)活動(dòng)意圖時(shí),任務(wù)就被創(chuàng )建了;這個(gè)意圖被用作任務(wù)的根用途,定義區分哪個(gè)任務(wù)。如果活動(dòng)啟動(dòng)時(shí)沒(méi)有這個(gè)標記將被運行在同一個(gè)任務(wù)里(除非你的活動(dòng)以特殊模式被啟動(dòng),這個(gè)后面會(huì )討論)。如果你使用 FLAG_ACTIVITY_NEW_TASK 標記并且這個(gè)意圖的任務(wù)已經(jīng)啟動(dòng),任務(wù)將被切換到前臺而不是重新加載。
FLAG_ACTIVITY_NEW_TASK 必須小心使用:在用戶(hù)看來(lái),一個(gè)新的應用程序由此啟動(dòng)。如果這不是你期望的,你想要創(chuàng )建一個(gè)新的任務(wù)。另外,如果用戶(hù)需要從桌面退出到他原來(lái)的地方然后使用同樣的意圖打開(kāi)一個(gè)新的任務(wù),你需要使用新的任務(wù)標記。否則,如果用戶(hù)在你剛啟動(dòng)的任務(wù)里按桌面(HOME)鍵,而不是退出(BACK)鍵,你的任務(wù)以及任務(wù)的活動(dòng)將被放在桌面程序的后面,沒(méi)有辦法再切換過(guò)去。
任務(wù)親和力(Affinities)
一些情況下Android 需要知道哪個(gè)任務(wù)的活動(dòng)附屬于一個(gè)特殊的任務(wù),即使該任務(wù)還沒(méi)有被啟動(dòng)。這通過(guò)任務(wù)親和力來(lái)完成,它為任務(wù)中一個(gè)或多個(gè)可能要運行的活動(dòng)提供一個(gè)獨一無(wú)二的靜態(tài)名字。默認為活動(dòng)命名的任務(wù)親和力的名字,就是實(shí)現該活動(dòng).apk 包的名字。這提供一種通用的特性,對用戶(hù)來(lái)說(shuō),所有在.apk 包里的活動(dòng)都是單一應用的一部分。
當不帶 Intent.FLAG_ACTIVITY_NEW_TASK 標記啟動(dòng)一個(gè)新的活動(dòng),任務(wù)親和力對新啟動(dòng)的活動(dòng)將沒(méi)有影響作用:它將一直運行在它啟動(dòng)的那個(gè)任務(wù)里。然而,如果使用NEW_TASK 標記,親和力會(huì )檢測已經(jīng)存在的任務(wù)是否具有相同的親和力。如果是,該任務(wù)會(huì )被切換到前臺,新的活動(dòng)會(huì )在任務(wù)的最上面被啟動(dòng)。
你可以在你的表現文件里的應用程序標簽里為.apk 包里所有的活動(dòng)設置你自己的任務(wù)親和力,當然也可以為單獨的活動(dòng)設置標簽。這里有些例子演示如何使用:
· 如果你的.apk 包里包含多個(gè)用戶(hù)可啟動(dòng)的上層應用程序,那么你可能想要為每個(gè)活動(dòng)分配不同的親和力。這里有一個(gè)不錯的協(xié)定,你可以將不同的名字字串加上冒號附加在.apk 包名字的后面。 例如,"com.android.contacts"的親和力命名可以是"com.android.contacts:Dialer"and"com.android.contacts:ContactsList"。
· 如果你想替換一個(gè)通知,快捷鍵,或者其它能從外部啟動(dòng)的應用程序的內部活動(dòng),你需要在你想替換的活動(dòng)里明確的設置任務(wù)親和力(taskAffinity)。例如,如果你想替換聯(lián)系人詳細信息瀏覽界面(用戶(hù)可以直接操作或者通過(guò)快捷方式調用),你需要設置任務(wù)親和力(taskAffinity)為“com.android.contacts”。
啟動(dòng)模式以及啟動(dòng)標記
你控制活動(dòng)和任務(wù)通信的最主要的方法是通過(guò)設置啟動(dòng)模式的屬性以及意圖相應的標記。這兩個(gè)參數能以不同的組合來(lái)共同控制活動(dòng)的啟動(dòng)結果,這在相應的文檔里有描述。
這里我們只描述一些通用的用法以及幾種不同的組合方式。
你最通常使用的模式是singleTop(除了默認為standard 模式)。這不會(huì )對任務(wù)產(chǎn)生什么影響;僅僅是防止在棧頂多次啟動(dòng)同一個(gè)活動(dòng)。
singleTask 模式對任務(wù)有一些影響:它能使得活動(dòng)總是在新的任務(wù)里被打開(kāi)(或者將已經(jīng)打開(kāi)的任務(wù)切換到前臺來(lái))。使用這個(gè)模式需要加倍小心該進(jìn)程是如何和系統其他部分交互的,它可能影響所有的活動(dòng)。這個(gè)模式最好被用于應用程序入口活動(dòng)的標記中。
(支持MAIN 活動(dòng)和LAUNCHER 分類(lèi))。
singleInstance 啟動(dòng)模式更加特殊,該模式只能當整個(gè)應用只有一個(gè)活動(dòng)時(shí)使用。有一種情況你會(huì )經(jīng)常遇到,其它實(shí)體(如搜索管理器SearchManager 或者 通知管理器NotificationManager)會(huì )啟動(dòng)你的活動(dòng)。這種情況下,你需要使用Intent.FLAG_ACTIVITY_NEW_TASK 標記,因為活動(dòng)在任務(wù)(這個(gè)應用/任務(wù)還沒(méi)有被啟動(dòng))之外被啟動(dòng)。就像之前描述的一樣,這種情況下標準特性就是當前和任務(wù)和新的活動(dòng)的親和性匹配的任務(wù)將會(huì )切換到前臺,然后在最頂端啟動(dòng)一個(gè)新的活動(dòng)。當然,你也可以實(shí)現其它類(lèi)型的特性。
一個(gè)常用的做法就是將Intent.FLAG_ACTIVITY_CLEAR_TOP 和NEW_TASK 一起使用。這樣做,如果你的任務(wù)已經(jīng)處于運行中,任務(wù)將會(huì )被切換到前臺來(lái), 在棧里的所有的活動(dòng)除了根活動(dòng),都將被清空,根活動(dòng)的onNewIntent(Intent) 方法傳入意圖參數后被調用。當使用這種方法的時(shí)候 singleTop 或者 singleTask 啟動(dòng)模式經(jīng)常被使用,這樣當前實(shí)例會(huì )被置入一個(gè)新的意圖,而不是銷(xiāo)毀原先的任務(wù)然后啟動(dòng)一個(gè)新的實(shí)例。
另外你可以使用的一個(gè)方法是設置活動(dòng)的任務(wù)親和力為空字串(表示沒(méi)有親和力),然后設置finishOnBackground 屬性。 如果你想讓用戶(hù)給你提供一個(gè)單獨的活動(dòng)描述的通知,倒不如返回到應用的任務(wù)里,這個(gè)比較管用。要指定這個(gè)屬性,不管用戶(hù)使用BACK還是HOME,活動(dòng)都會(huì )結束;如果這個(gè)屬性沒(méi)有指定,按HOME 鍵將會(huì )導致活動(dòng)以及任務(wù)還留在系統里,并且沒(méi)有辦法返回到該任務(wù)里。
請確保閱讀過(guò)文檔啟動(dòng)模式屬性(launchMode attribute) 以及 意圖標記(Intent
flags) ,關(guān)注這些選項的詳細信息。
進(jìn)程
在A(yíng)ndroid 中,進(jìn)程是應用程序的完整實(shí)現,而不是用戶(hù)通常了解的那樣。他們主要用途很簡(jiǎn)單:
· 提高穩定性和安全性,將不信任或者不穩定的代碼移動(dòng)到其他進(jìn)程。
· 可將多個(gè).apk 包運行在同一個(gè)進(jìn)程里減少系統開(kāi)銷(xiāo)。
· 幫助系統管理資源,將重要的代碼放在一個(gè)單獨的進(jìn)程里,這樣就可以單獨銷(xiāo)毀應用程序的其他部分。
像前面描述的一樣,進(jìn)程的屬性被用來(lái)控制那些有特殊應用組件運行的進(jìn)程。注意這個(gè)屬性不能違反系統安全: 如果兩個(gè).apk 包不能共享同一個(gè)用戶(hù)ID,卻試圖運行在通一個(gè)進(jìn)程里,這種情況是不被允許的,事實(shí)上系統將會(huì )創(chuàng )建兩個(gè)不同的進(jìn)程。請查看安全相關(guān)文檔以獲取更多關(guān)于安全限制方面的信息。
線(xiàn)程
每個(gè)進(jìn)程包含一個(gè)或多個(gè)線(xiàn)程。多數情況下,Android 避免在進(jìn)程里創(chuàng )建多余的線(xiàn)程,除非它創(chuàng )建它自己的線(xiàn)程,我們應保持應用程序的單線(xiàn)程性。一個(gè)重要的結論就是所有呼叫實(shí)例, 廣播接收器, 以及 服務(wù)的實(shí)例都是由這個(gè)進(jìn)程里運行的主線(xiàn)程創(chuàng )建的。
注意新的線(xiàn)程不是為活動(dòng),廣播接收器,服務(wù)或者內容提供器實(shí)例創(chuàng )建:這些應用程序的組件在進(jìn)程里被實(shí)例化(除非另有說(shuō)明,都在同一個(gè)進(jìn)程處理),實(shí)際上是進(jìn)程的主線(xiàn)程。這說(shuō)明當系統調用時(shí)這些組件(包括服務(wù))不需要進(jìn)程遠距離或者封鎖操作(就像網(wǎng)絡(luò )呼叫或者計算循環(huán)),因為這將阻止進(jìn)程中的所有其他組件。你可以使用標準的線(xiàn)程 類(lèi)或者Android 的HandlerThread 類(lèi)去對其它線(xiàn)程執行遠程操作。
這里有一些關(guān)于創(chuàng )建線(xiàn)程規則的例外:
· 呼叫IBinder 或者IBinder 實(shí)現的接口,如果該呼叫來(lái)自其他進(jìn)程,你可以通過(guò)線(xiàn)程發(fā)送的IBinder 或者本地進(jìn)程中的線(xiàn)程池呼叫它們,從進(jìn)程的主線(xiàn)程呼叫是不可以的。特殊情況下,,呼叫一個(gè)服務(wù) 的IBinder 可以這樣處理。(雖然在服務(wù)里呼叫方法在主線(xiàn)程里已經(jīng)完成。)這意味著(zhù)IBinder 接口的實(shí)現必須要有一種線(xiàn)程安全的方法,這樣任意線(xiàn)程才能同時(shí)訪(fǎng)問(wèn)它。
· 呼叫由正在被調用的線(xiàn)程或者主線(xiàn)程以及IBinder 派發(fā)的內容提供器 的主方法。被指定的方法在內容提供器的類(lèi)里有記錄。這意味著(zhù)實(shí)現這些方法必須要有一種線(xiàn)程安全的模式,這樣任意其它線(xiàn)程同時(shí)可以訪(fǎng)問(wèn)它。
· 呼叫視圖以及由視圖里正在運行的線(xiàn)程組成的子類(lèi)。通常情況下,這會(huì )被作為進(jìn)程的主線(xiàn)程,如果你創(chuàng )建一個(gè)線(xiàn)程并顯示一個(gè)窗口,那么繼承的窗口視圖將從那個(gè)線(xiàn)程里啟動(dòng)。Android 應用程序的生命周期。
在大多數情況下,每個(gè)Android 應用程序都運行在自己的Linux 進(jìn)程中。當應用程序的某些代碼需要運行時(shí),這個(gè)進(jìn)程就被創(chuàng )建并一直運行下去,直到系統認為該進(jìn)程不再有用為止。然后系統將回收進(jìn)程占用的內存以便分配給其它的應用程序。應用程序的開(kāi)發(fā)人員必須理解不同的應用程序組件(尤其是Activity, Service, 和BroadcastReceiver)是如何影響應用程序進(jìn)程生命周期的,這是很重要的一件事情。不正確地使用這些組件可能會(huì )導致系統殺死正在執行重要任務(wù)的應用程序進(jìn)程。一個(gè)常見(jiàn)的進(jìn)程生命周期bug 的例子是BroadcastReceiver, 當BroadcastReceiver在BroadcastReceiver.onReceive()方法中接收到一個(gè)Intent 時(shí),它會(huì )啟動(dòng)一個(gè)線(xiàn)程,然后返回。一旦它返回,系統將認為BroadcastReceiver 不再處于活動(dòng)狀態(tài),因而B(niǎo)roadcastReceiver 所在的進(jìn)程也就不再有用了(除非該進(jìn)程中還有其它的組件處于活動(dòng)狀態(tài))。因此,系統可能會(huì )在任意時(shí)刻殺死進(jìn)程以回收內存。這樣做的話(huà),進(jìn)程中創(chuàng )建(spawned)出的那個(gè)線(xiàn)程也將被終止。對這個(gè)問(wèn)題的解決方法是從BroadcastReceiver 啟動(dòng)一個(gè)服務(wù),讓系統知道進(jìn)程中還有處于活動(dòng)狀態(tài)的工作。為了決定在內存不足時(shí)讓系統殺死哪個(gè)進(jìn)程,Android 根據每個(gè)進(jìn)程中運行的組件以及組件的狀態(tài)把進(jìn)程放入一個(gè)”重要性分級(importance hierarchy)”中。進(jìn)程的類(lèi)型包括(按重要程度排序):
1. 前臺(foreground)進(jìn)程,與用戶(hù)當前正在做的事情密切相關(guān)。不同的應用程序組件能夠通過(guò)不同的方法使它的宿主進(jìn)程移到前臺。當下面任何一個(gè)條件滿(mǎn)足時(shí),可以考慮將進(jìn)程移到前臺:
1. 進(jìn)程正在屏幕的最前端運行一個(gè)與用戶(hù)交互的Activity (它的onResume()方法被調用)
2. 進(jìn)程有一正在運行的BroadcastReceiver (它的BroadcastReceiver.onReceive()方法正在執行)
3. 進(jìn)程有一個(gè)Service,并且在Service 的某個(gè)回調函數(Service.onCreate(),Service.onStart(), 或 Service.onDestroy())內有正在執行的代碼。
1. 可見(jiàn)(visible)進(jìn)程,它有一個(gè)可以被用戶(hù)從屏幕上看到的Activity,但不在前臺(它的onPause()方法被調用)。舉例來(lái)說(shuō),如果前臺的Activity 是一個(gè)對話(huà)框,以前的Activity 隱藏在對話(huà)框之后,就可能出現這種進(jìn)程。這樣的進(jìn)程特別重要,一般不允許被殺死,除非為了保證前臺進(jìn)程的運行不得不這樣做。
2. 服務(wù)(service)進(jìn)程,有一個(gè)已經(jīng)用startService() 方法啟動(dòng)的Service。雖然這些進(jìn)程用戶(hù)無(wú)法直接看到,但它們做的事情卻是用戶(hù)所關(guān)心的(例如后臺MP3回放或后臺網(wǎng)絡(luò )數據的上傳下載)。因此,系統將一直運行這些進(jìn)程除非內存不足以維持所有的前臺進(jìn)程和可見(jiàn)進(jìn)程。
3. 后臺(background)進(jìn)程, 擁有一個(gè)當前用戶(hù)看不到的Activity(它的onStop()
方法被調用)。這些進(jìn)程對用戶(hù)體驗沒(méi)有直接的影響。如果它們正確執行了Activity生命期(詳細信息可參考Activity),系統可以在任意時(shí)刻殺死進(jìn)程來(lái)回收內存,并提供給前面三種類(lèi)型的進(jìn)程使用。系統中通常有很多個(gè)這樣的進(jìn)程在運行,因此要將這些進(jìn)程保存在LRU 列表中,以確保當內存不足時(shí)用戶(hù)最近看到的進(jìn)程最后一個(gè)被殺掉。
4. 空(empty)進(jìn)程,不包含任何處于活動(dòng)狀態(tài)的應用程序組件。保留這種進(jìn)程的唯一原因是,當下次應用程序的某個(gè)組件需要運行時(shí),不需要重新創(chuàng )建進(jìn)程,這樣可以提高啟動(dòng)速度。
系統將以進(jìn)程中當前處于活動(dòng)狀態(tài)組件的重要程度為基礎對進(jìn)程進(jìn)行分類(lèi)。請參考Activity, Service 和 BroadcastReceiver 文檔來(lái)獲得有關(guān)這些組件在進(jìn)程整個(gè)生命期中是如何起作用的詳細信息。每個(gè)進(jìn)程類(lèi)別的文檔詳細描述了它們是怎樣影響應用程序整個(gè)生命周期的。進(jìn)程的優(yōu)先級可能也會(huì )根據該進(jìn)程與其它進(jìn)程的依賴(lài)關(guān)系而增長(cháng)。例如,如果進(jìn)程A 通過(guò)在進(jìn)程B 中設置Context.BIND_AUTO_CREATE 標記或使用ContentProvider 被綁定到一個(gè)服務(wù)(Service),那么進(jìn)程B 在分類(lèi)時(shí)至少要被看成與進(jìn)程A 同等重要。
二、開(kāi)發(fā)應用程序
Android 應用構成
Android 應用是由各種各樣的組件來(lái)構成。 這些組件大部分都是松散連接的,準確 的說(shuō)你可以把它們看成組件的聯(lián)合而非是一個(gè)單一的應用。
通常,這些組件運行在同一個(gè)系統進(jìn)程里面。你也可以在這個(gè)進(jìn)程里面創(chuàng )建多個(gè)線(xiàn)程(這是很常見(jiàn)的),如果必要你也可以創(chuàng )建獨立的子進(jìn)程。不過(guò)這種情況是非常少見(jiàn)的,因為Android 盡力使代碼進(jìn)程間透明。
以下部分是很重要的Android APIs:
AndroidManifest.xml
AndroidManifest.xml 是系統的控制文件,它告訴系統如何處理你所創(chuàng )建的所有頂層組件(尤其是activities,服務(wù),Intent 接收器和后面描述的內容管理器)。舉例來(lái)說(shuō),控制文件就是把你的活動(dòng)(Activities)要接收的Intents 連接在一起的“膠水”。
活動(dòng)(Activities)
活動(dòng)(Activity)就是一個(gè)有生命周期的對象。 一個(gè)Activity 就是完成某些工作的代碼塊, 如必要的話(huà),這部分工作還可能包括對用戶(hù)UI 界面的顯示。不過(guò)這不是必須的,有些活 動(dòng)從不顯示UI 界面。典型地,你將會(huì )指定你的應用程序中的一個(gè)活動(dòng)為整個(gè)程序的入口點(diǎn)。
視圖(Views)
視圖(Views)可以將其自身繪制到屏幕上。Android 的用戶(hù)界面由一系列的視圖樹(shù) (trees of views)構成。接口都是由一組以樹(shù)的形式出現的視圖組成的。開(kāi)發(fā)者可 以通過(guò)創(chuàng )建一個(gè)新的視圖的方法來(lái)使用自定義的圖形處理技術(shù)(比如開(kāi)發(fā)游戲,或者是 使用了不常用的用戶(hù)圖形(UI)窗口界面(widget))。
Intents
Intents 是一個(gè)簡(jiǎn)單的消息對象,它表示程序想做某事的“意圖”(intention)。比如如果你的應用程序想要顯示一個(gè)網(wǎng)頁(yè),那么它通過(guò)創(chuàng )建一個(gè)Intent 實(shí)例并將其傳遞給 系統來(lái)表示意圖瀏覽這個(gè)URI。系統將定位于知道如何能處理這一Intent 的代碼(在當 前情況下就是瀏覽器),并運行之。Intents 也可以用于廣播系統范圍內的有效事件 (例如通知事件)。
服務(wù)(Services)
服務(wù)是運行在后臺的一段代碼。它可以運行在它自己的進(jìn)程,也可以運行在其他應用程序進(jìn)程的上下文(context)里面,這取決于自身的需要。其它的組件可以綁定到一個(gè)服務(wù)(Service)上面,通過(guò)遠程過(guò)程調用(RPC)來(lái)調用這個(gè)方法。例如媒體播放器的服務(wù), 當用戶(hù)退出媒體選擇用戶(hù)界面,她仍然希望音樂(lè )依然可以繼續播放,這時(shí)就是由服務(wù) (service)來(lái)保證當用戶(hù)界面關(guān)閉時(shí)音樂(lè )繼續播放的。
通知(Notifications)
通知將以小圖標的形式呈現在狀態(tài)欄里,用戶(hù)通過(guò)與圖標的交互式操來(lái)接收消息。最常見(jiàn)的通知包括短信息,通話(huà)記錄,語(yǔ)音郵件,但是應用程序也可以創(chuàng )建它們自己的通知事件。 我們推薦采用通知事件實(shí)現提醒用戶(hù)的注意。
內容管理器(ContentProviders)
內容管理器(ContentProvider)提供對設備上數據進(jìn)行訪(fǎng)問(wèn)的數據倉庫。典型的例子就 是使用內容管理器來(lái)訪(fǎng)問(wèn)聯(lián)系人列表。你的應用程序也可以使用其它程序通過(guò)內容管理器提供的數據,同時(shí)你也可以定義你自己的內容管理器來(lái)向其它應用提供數據訪(fǎng)問(wèn)服務(wù)。
存、取、提供數據
典型的桌面操作系統一般能提供一種通用的文件系統,所有應用程序都能儲存和讀文件,并且其他應用程序也能訪(fǎng)問(wèn)該文件(可能需要一些訪(fǎng)問(wèn)控制設置). Android 使用不同的方式:在平臺上,所有應用程序的數據(包括文件),對該應用程序是私有的。當然,Android 也提供一種標準方法將自己的私有數據提供給其他應用程序訪(fǎng)問(wèn)。這一章節講了很多方法,描述應用如何存取數據,以及將數據提供給其他程序訪(fǎng)問(wèn),當然,你也可以向其他應用程序請求并獲得它的數據。
Android 提供下面的方式來(lái)存取數據:
參數選擇
使用一個(gè)輕量級機制來(lái)存取基本數據類(lèi)型的數據對,這是典型應用程序參數的
存儲模式。
文件
你可以將你的文件存儲在設備上或者其他移動(dòng)媒介上,默認情況下,其他應用程序是不能訪(fǎng)問(wèn)這些文件的。
數據庫教程
Android 有直接SQLite 數據庫的API。應用程序可以創(chuàng )建以及使用SQLite 數據庫。每個(gè)包創(chuàng )建的數據庫都是私有的。
數據提供
數據提供是應用程序的一個(gè)可選組件,它可以提供讀/寫(xiě)應用程序私有數據的方法。內容提供組件實(shí)現了一種標準請求數據的語(yǔ)法,和一種標準處理返回值的機制。Android 提供很多標準數據的提供方式,例如私有聯(lián)系人。
網(wǎng)絡(luò )
不要忘記,我們還可以使用網(wǎng)絡(luò )存取數據。
Android 的安全與權限
Android 是一個(gè)多進(jìn)程系統,每一個(gè)應用程序(和系統的組成部分)都運行在自己的進(jìn)程中。在應用程序和系統間的安全通過(guò)標準的Linux 設備在進(jìn)程級被執行,例如被分配給應用程序的用戶(hù)和組ID。額外的細粒度安全特性通過(guò)“許可”機制來(lái)提供,該機制能夠對一個(gè)指定進(jìn)程可實(shí)現的特定操作進(jìn)行約束。
安全結構
Android 安全學(xué)中的一個(gè)重要的設計點(diǎn)是在默認情況下應用程序沒(méi)有權限執行對其它應用程序、操作系統或用戶(hù)有害的操作。這些操作包括讀/寫(xiě)用戶(hù)的隱私數據(例如聯(lián)系方式或e-mail),讀/寫(xiě)其它應用程序的文件,執行網(wǎng)絡(luò )訪(fǎng)問(wèn),保持設備活動(dòng),等等。
應用程序的進(jìn)程是一個(gè)安全的沙箱。它不能干擾其它應用程序,除非在它需要添加原有沙箱不能提供的功能時(shí)明確聲明權限。這些權限請求能夠被不同方式的操作所處理,特別的要基于證書(shū)和用戶(hù)的提示被自動(dòng)的允許或禁止。權限的請求在那個(gè)應用程序中通過(guò)一個(gè)應用程序被聲明為靜態(tài)的,所以在此之后在安裝時(shí)或沒(méi)有改變時(shí)它們會(huì )預先知道。
應用程序簽名
所有的Android 應用程序(.apk 文件)必須通過(guò)一個(gè)證書(shū)的簽名,此證書(shū)的私鑰必須被開(kāi)發(fā)者所掌握。這個(gè)證書(shū)的標識是應用程序的作者。這個(gè)證書(shū)不需要通過(guò)證書(shū)組織的簽署:Android 應用程序對于使用自簽署的證書(shū)是完全允許的和特別的。這個(gè)證書(shū)僅僅被用于與應用程序建立信任關(guān)系,不是為了大規模的控制應用程序可否被安裝。最重要的方面是通過(guò)確定能夠訪(fǎng)問(wèn)原始簽名權限和能夠共享用戶(hù)ID 的簽名來(lái)影響安全。
用戶(hù)標識和文件訪(fǎng)問(wèn)
安裝在設備中的每一個(gè)Android包文件(.apk)都會(huì )被分配給一個(gè)屬于自己的統一的Linux用戶(hù)ID,并且為它創(chuàng )建一個(gè)沙箱以防止影響其它應用程序(或者其它應用程序影響它)。
用戶(hù)ID 在應用程序安裝到設備中時(shí)被分配,并且在這個(gè)設備中保持它的永久性。
因為安全執行發(fā)生在進(jìn)程級,所以一些不同包中的代碼在相同進(jìn)程中不能正常的運行,自從他們需要以不同Linux 用戶(hù)身份運行時(shí)。你可以使用每一個(gè)包中的
AndroidManifest.xml 文件中的manifest 標簽屬性sharedUserId 擁有它們分配的相同用戶(hù)ID。通過(guò)這樣做,兩個(gè)包被視為相同的應用程序的安全問(wèn)題被解決了,注意為了保持安全,僅有相同簽名(和請求相同sharedUserId 標簽)的兩個(gè)應用程序簽名將會(huì )給相同的用戶(hù)ID。
應用創(chuàng )建的任何文件都會(huì )被賦予應用的用戶(hù)標識,并且,正常情況下不能被其它包訪(fǎng)問(wèn)。當你通過(guò)getSharedPreferences(String, int), openFileOutput(String, int) 或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)創(chuàng )建一個(gè)新文件時(shí), 你可以同時(shí)或分別使用 MODE_WORLD_READABLE 和
MODE_WORLD_WRITEABLE 標志允許其它包讀/寫(xiě)此文件。當設置了這些標志時(shí),這個(gè)文件仍然屬于你的應用程序,但是它的全局讀、寫(xiě)和讀寫(xiě)權限已經(jīng)設置所以其它任何應用程序可以看到它。
權限命名
一個(gè)基本的Android 應用程序沒(méi)有與其相關(guān)聯(lián)的權限,意味著(zhù)它不能做任何影響用戶(hù)體驗或設備中的數據的有害操作。要利用這個(gè)設備的保護特性,在你的應用程序需要時(shí),你必須在A(yíng)ndroidManifest.xml 文件中包含一個(gè)或更多的<uses-permission>
標簽來(lái)聲明此權限。
例如:需要監聽(tīng)來(lái)自SMS 消息的應用程序將要指定如下內容:
<manifest
xmlns:android="<uses-permission
android:name="android.permission.RECEIVE_SMS" />
</manifest>
在安裝應用程序時(shí),通過(guò)包安裝器應用程序要通過(guò)權限請求的許可,使建立在與應用程序簽名的核對下聲明對于用戶(hù)的那些權限和影響。在應用運行期間對用戶(hù)不做檢查:它要么在安裝時(shí)被授予特定的許可,并且使用想用的特性;要么不被授予許可,并且使得一切使用特性的嘗試失敗而不提示用戶(hù)。
例如,sendBroadcast(Intent) 方法就是當數據被發(fā)送給到每個(gè)接收器時(shí)檢查許可的,
在方法調用返回之后,因此當許可失敗時(shí)你不會(huì )收到一個(gè)異常。然而,幾乎在所有例子中,許可失敗都會(huì )被打印到系統日志中。通常,多次的許可錯誤會(huì )產(chǎn)生拋回至應用程序的SecurityException 異常。
Android 系統提供的許可可以在Manifest.permission 中找到。每個(gè)引用也可以定義和Enforce 它自己的許可,因此這不是全面的所有可能的列表。
在程序操作期間,個(gè)別權限在一些地方可能被強制:
· 在系統接到呼叫的時(shí)候,預防一個(gè)應用程序去執行特定的函數。
· 在啟動(dòng)Activity 時(shí),防止一個(gè)應用啟動(dòng)其它應用程序的Activities。
· 發(fā)送和接收Intent 廣播時(shí),控制誰(shuí)能接收你的廣播或者誰(shuí)能發(fā)送廣播給你。
· 在一個(gè)內容提供器上訪(fǎng)問(wèn)和操作時(shí)。
· 綁定或開(kāi)始一個(gè)服務(wù)時(shí)。
權限的聲明和支持
為了執行你自己的權限,你必須首先在你的AndroidManifest.xml 中使用一個(gè)或多個(gè)<permission> 標簽聲明它們。
<protectionLevel> 屬性是必需的,告訴系統用戶(hù)應如何處理應用程序接到請求此權限的通知,或者在這個(gè)文檔中對這個(gè)權限的許可的描述。
<permissionGroup> 屬性是可選的,僅僅用于幫助系統為用戶(hù)顯示權限。通常,你要設置這些,向一個(gè)標準的系統組(列在android.Manifest.permission_group 中),或者在更多的情況下要自定義。它更偏向于使用一個(gè)已經(jīng)存在的組,做為簡(jiǎn)化的權限用戶(hù)界面顯示給用戶(hù)。
注意:應該為每個(gè)權限提供標簽(label) 和描述(description)。當用戶(hù)瀏覽權限列表時(shí),它們可以為用戶(hù)展示字符資源,如(android:label) 或者一個(gè)許可的詳細信息( android:description) 。標簽(label)比較短,用幾個(gè)詞來(lái)描述該權限保護的關(guān)鍵功能。描述(description)應該是一組句子,用于描述獲得權限的用戶(hù)可以做什么。我們寫(xiě)描述的習慣是兩句話(huà),第一句聲明權限,第二句警告用戶(hù)如果應用許可該權限時(shí),會(huì )發(fā)生什么不好的事情。
你可以在系統中通過(guò)shell 命令 adb shell pm list permissions 查看權限當前
在A(yíng)ndroidManifest.xml 文件中支持權限
通過(guò) AndroidManifest.xml 文件可以設置高級權限,以限制訪(fǎng)問(wèn)系統的所有組件或者使用應用程序。所有的這些請求都包含在你所需要的組件中的 android:permission屬性,命名這個(gè)權限可以控制訪(fǎng)問(wèn)此組件。
Activity 權限 (使用 <activity> 標簽) 限制能夠啟動(dòng)與 Activity 權限相關(guān)聯(lián)的組件或應用程序。此權限在 Context.startActivity() 和 Activity.startActivityForResult() 期間要經(jīng)過(guò)檢查;如果調用者沒(méi)有請求權限,那么會(huì )為調用拋出一個(gè)安全異常
( SecurityException )。
Service 權限(應用 <service> 標簽)限制啟動(dòng)、綁定或啟動(dòng)和綁定關(guān)聯(lián)服務(wù)的組件或應用程序。此權限在 Context.startService(), Context.stopService() 和
Context.bindService() 期間要經(jīng)過(guò)檢查;如果調用者沒(méi)有請求權限,那么會(huì )為調用拋出一個(gè)安全異常( SecurityException )。
BroadcastReceiver 權限(應用 <receiver> 標簽)限制能夠為相關(guān)聯(lián)的接收者發(fā)送廣播的組件或應用程序。在 Context.sendBroadcast() 返回后此權限將被檢查,同時(shí)系統設法將廣播遞送至相關(guān)接收者。因此,權限失敗將會(huì )導致拋回給調用者一個(gè)異常;它將不能遞送到目的地。在相同方式下,可以使 Context.registerReceiver() 支持一個(gè)權限,使其控制能夠遞送廣播至已登記節目接收者的組件或應用程序。其它的,當調用
Context.sendBroadcast() 以限制能夠被允許接收廣播的廣播接收者對象一個(gè)權限(見(jiàn)下文)。
ContentProvider 權限(使用 <provider> 標簽)用于限制能夠訪(fǎng)問(wèn) ContentProvider中的數據的組件或應用程序。(Content providers 有一個(gè)重要的附加安全設施可用于它們調用被描述后的URI 權限。) 不同于其它組件,它有兩個(gè)不相連系的權限屬性要設置:android:readPermission 用于限制能夠讀取提供器的組件或應用程序,android:writePermission 用于限制能夠寫(xiě)入提供器的組件或應用程序。注意,如果一個(gè)提供者的讀寫(xiě)權限受保護,意思是你只能從提供器中讀,而沒(méi)有寫(xiě)權限。當你首次收回提供者(如果你沒(méi)有任何權限,將會(huì )拋出一個(gè)SecurityException 異常),那么權限要被檢查,并且做為你在這個(gè)提供者上的執行操作。 使用 ContentResolver.query() 請求獲取讀權限; 使用 ContentResolver.insert(), ContentResolver.update() 和ContentResolver.delete() 請求獲取寫(xiě)權限。在所有這些情況下,一個(gè)SecurityException異常從一個(gè)調用者那里拋出時(shí)不會(huì )存儲請求權限結果。
發(fā)送廣播時(shí)支持權限
當發(fā)送一個(gè)廣播時(shí)你能總指定一個(gè)請求權限,此權限除了權限執行外,其它能發(fā)送Intent到一個(gè)已注冊的BroadcastReceiver 的權限均可以。 通過(guò)調用
Context.sendBroadcast() 及一些權限字符串, 為了接收你的廣播,你請求一個(gè)接收器應用程序必須持有那個(gè)權限。
注意,接收者和廣播者都能夠請求一個(gè)權限。當這樣的事發(fā)生了,對于Intent 來(lái)說(shuō),這兩個(gè)權限檢查都必須通過(guò),為了交付到共同的目的地。
其它權限支持
任意一個(gè)好的粒度權限都能夠在一些調用者的一個(gè)服務(wù)中被執行。 和
Context.checkCallingPermission() method. 方法一起被完成。調用并產(chǎn)生一個(gè)需要的權限字符串,它將返回一個(gè)整型,以確定當前調用進(jìn)程是否被許可。注意,僅僅當你執行的調用進(jìn)入到其它進(jìn)程的時(shí)候這些才會(huì )被使用,通常,通過(guò)IDL 接口從一個(gè)服務(wù)發(fā)布,或從一些其它方式通知其它進(jìn)程。
這有許多其它有益的方式去檢查權限。如果你有一個(gè)其它進(jìn)程的PID,你可以使用上下文的方法 Context.checkPermission(String, int, int) 檢查權限違反PID。 如果你有一個(gè)其它應用程序的包名, 你可以使用直接的包管理器方法
PackageManager.checkPermission(String, String) 去查看特定的包是否被指定的權限所許可。
URI 權限
迄今為止,在與內容提供器共同使用時(shí),標準權限系統描述通常是不充分的。一個(gè)內容提供器要保護它自己及讀和寫(xiě)權限,當為了它們產(chǎn)生作用,它的直接客戶(hù)端總是需要手動(dòng)的對其它應用程序指定URI。一個(gè)典型的例子是郵件應用程序中的附件。訪(fǎng)問(wèn)郵件的權限應該被保護,因為這是敏感用戶(hù)數據??梢?,如果一個(gè)網(wǎng)址圖片附件提供給一個(gè)圖片查看器,那個(gè)圖片查看器將沒(méi)有權限打開(kāi)這個(gè)附件,因為它沒(méi)有原因去擁有一個(gè)權限從而不能訪(fǎng)問(wèn)所有的電子郵件。
對于這個(gè)問(wèn)題的解決是通過(guò)網(wǎng)址權限:當開(kāi)始一個(gè)活動(dòng)或對一個(gè)活動(dòng)返回一個(gè)結果,調用者可通過(guò)設置Intent.FLAG_GRANT_READ_URI_PERMISSION 和
Intent.FLAG_GRANT_WRITE_URI_PERMISSION 中的一個(gè)或者兩個(gè)。允許接收活動(dòng)權限訪(fǎng)問(wèn)在Intent 中特定的數據地址,不論它有權限訪(fǎng)問(wèn)數據在內容提供器相應的Intent 中。
這種機制允許一個(gè)公用的功能性模型使用戶(hù)相互交互(打開(kāi)一個(gè)附件,從一個(gè)列表中選擇一個(gè)聯(lián)系人,等等)驅動(dòng)ad-hoc 在優(yōu)粒度權限的許可下。這可能是一個(gè)主要設備,應用程序為了減少這個(gè)權限而需要,僅僅直接關(guān)系到它們的行為。
這優(yōu)粒度URI 權限的許可工作,然而,請求一些協(xié)作和內容提供者保持那些URI。強烈推薦內容提供者實(shí)現這種設備,并且通過(guò)android:grantUriPermissions 屬性或者<grant-uri-permissions> 標簽聲明支持它。
更多信息可以在Context.grantUriPermission(), Context.revokeUriPermission(), 和Context.checkUriPermission() 方法中找到。
資源管理和多國版本
資源是外部文件(不含代碼的文件),它被代碼使用并在編譯時(shí)編入應用程序。Android支持不同類(lèi)型的資源文件,包括XML,PNG 以及JPEG 文件XML 文件根據描述的不同有不同格式。這份文檔描述可以支持什么樣的文件,語(yǔ)法,以及各種格式.源代碼以及XML 文件將資源打包并編譯進(jìn)二進(jìn)制文件,這種模式能使得資源更快得被加載。字符串也同樣被壓縮成更高效的模式。由于這些原因, Android 平臺上存在不同的資源類(lèi)型.
資源
Android 資源系統能跟蹤所有非代碼相關(guān)的應用程序。你可以使用 資源 類(lèi)來(lái)訪(fǎng)問(wèn)應用程序的資源,資源的實(shí)例通常和應用程序聯(lián)系在一起,你可以通過(guò)Context.getResources()來(lái)訪(fǎng)問(wèn)。
應用程序的資源在編譯時(shí)就被編譯到應用程序二進(jìn)制代碼里。為了使用某個(gè)資源,你需要將它在代碼目錄結構里放正確,然后編譯。作為編譯過(guò)程的一部分,產(chǎn)生的資源代號你可以在源代碼里使用 -- 這允許編譯器驗證你的程序代碼和你定義的資源是否相符。
創(chuàng )建資源
Android 支持字符串,圖片以及很多其他類(lèi)型的資源。每個(gè)對象語(yǔ)法、格式以及它們存儲位置的支持,都是取決于不同類(lèi)型的對象? 通常,你可以通過(guò)三種類(lèi)型的文件來(lái)創(chuàng )建資源:XML 文件(除位圖以及原數據文件),位圖文件(對于圖片)以及原始數據(其它類(lèi)型,例如聲音文件,等等。)。事實(shí)上,有兩種不同類(lèi)型的XML 文件,一種是編譯到包里的,另外一種是通過(guò)aapt 來(lái)產(chǎn)生的資源文件,這里有一張包含所有資源類(lèi)型,
文件格式,文件描述以及所有XML 文件的詳細信息的列表。
在項目里,你可以在子目錄res/下創(chuàng )建和存儲資源文件。Android 有一個(gè)資源編譯工具(aapt),它可以編譯在這個(gè)目錄下所有的子目錄中的資源,這里有個(gè)各種資源的列表。你可以從 資源引用 這里看到各種類(lèi)型的對象,包含其語(yǔ)法以及格式。
路徑 資源類(lèi)型res/anim/ XML 文件被編譯進(jìn) 逐幀動(dòng)畫(huà) 或 補間動(dòng)畫(huà) 的對象res/drawable/.png, .9.png, .jpg files 這些類(lèi)型的文件被編譯進(jìn)下列這些圖表資源列表為了獲得這些資源的類(lèi)型,使用Resource.getDrawable(id)
· 位圖文件
· 9-patches (可改變尺寸的圖像)
res/layout/ 可編譯成屏幕布局的XML 文件 (或者屏幕的一部分). 查看 布局 res/values/ 可編譯成多種類(lèi)型資源的文件
注意: 不像其他 res/ 文件夾,它能容納任何數量的文件,但只是描述其創(chuàng )建而不是資源本身. XML 的元素類(lèi)型可以決定這些資源在R.class 里什么位置被替換 .文件可以被命名為任何名字,文件夾里有一些典型的文件(一般約定文件以定義的元素類(lèi)型后面部分為文件名)::
· arrays.xml 定義數組
· colors.xml 定義 顏色 和 顏色字串數值. 你可以使用
Resources.getDrawable() 以及Resources.getColor(), respectively, 取得這些資源.· dimens.xml 定義 尺寸數據 . 使用Resources.getDimension() 取得這些資源?!?strings.xml 定義字符串數值 (使用Resources.getString 或Resources.getText()取得資源,(后者更好一點(diǎn))getText() 能取到在用戶(hù)界面上顯示的文本框里的文本?!?styles.xml 定義類(lèi)型 對象。res/xml/ 任何XML 文件可以進(jìn)行編譯,并能在運行時(shí)調用Resources.getXML() 顯示XML 原文件。
res/raw/ 這里的任何文件都將直接被復制到設備上。編譯產(chǎn)品時(shí),這些數
據不會(huì )被編譯,它們被直接加入到程序包里。 為了在程序中使用這些資源,你可以調用Resources.openRawResource() , 參數為
ID: R.raw.somefilename.
資源最終會(huì )被編譯成APK 文件,Android 創(chuàng )建一個(gè)包裝類(lèi),命名為R,這樣你能做你的代碼里使用這些資源類(lèi)。根據資源路徑和文件名的不同,R 包含很多子類(lèi)。
全局資源
· 一些資源類(lèi)允許你定義顏色。它能接受多種網(wǎng)絡(luò )類(lèi)型的值 -- 你可以寫(xiě)成 #RGB,
#ARGB, #RRGGBB, #AARRGGBB 這樣16 進(jìn)制常數都可以。
· 所有的顏色都可以設置一個(gè)阿爾法值,開(kāi)始的兩個(gè)16 進(jìn)制數指定為透明。 0 在阿爾法值里意味著(zhù)透明。當然,默認值是不透明的。
使用資源
編譯時(shí),Android 產(chǎn)生一個(gè)叫R 的類(lèi),它指向你程序中所有的資源。這個(gè)類(lèi)包含很多子類(lèi)。每一種都是Android 支持的,同時(shí),編譯后會(huì )產(chǎn)生一個(gè)資源文件。每個(gè)類(lèi)提供一個(gè)或多個(gè)編譯后資源的標識符,你可以在代碼中使用。下面是個(gè)源代碼的文件,里面包含了字符串,布局文件(全屏或者部分屏幕),以及圖像資源。
注意: 這個(gè)R 類(lèi)是自動(dòng)產(chǎn)生的,你不能手動(dòng)編寫(xiě)。當資源變化的時(shí)候它會(huì )自動(dòng)更新。
在代碼中使用資源
只要知道資源的ID 以及你編譯進(jìn)目標文件的資源類(lèi)型就可以在代碼里使用它來(lái)。下面是一些語(yǔ)法:
R.resource_type.resource_name或者android.R.resource_type.resource_name
resource_type 是R 子類(lèi)的一種類(lèi)型。 resource_name 是定義在XML 文件里的資源名或者為其他文件類(lèi)型定義的資源文件(沒(méi)有后綴)名。每種類(lèi)型的資源會(huì )被加入到一個(gè)特定的R 的子類(lèi)中;為了學(xué)習哪種R 的子類(lèi)里有你編譯的資源類(lèi)型,參考資源引用 文檔。被編譯進(jìn)應用程序的資源不需要包的名字就可以直接被訪(fǎng)問(wèn)到(像這樣:
R.resource_type.resource_name). Android 包含一些標準資源,如屏幕的類(lèi)型,按鈕的背景。要使用這些代碼,你需要包含 android, 如
android.R.drawable.button_background.
這里有一些好的和糟糕的例子說(shuō)明如何在代碼里使用編譯后的資源:
// 從畫(huà)圖資源類(lèi)里裝載一個(gè)當前屏幕背景。
this.getWindow().setBackgroundDrawableResource(R.drawable.my_background_image);
// 錯誤! 將一個(gè)資源ID 裝入一個(gè)需要字符串的方法中
this.getWindow().setTitle(R.string.main_title);
//正確!需要從資源封裝類(lèi)里取得標題。
this.getWindow().setTitle(Resources.getText(R.string.main_title));
// 從當前屏幕中裝載布局數據。
setContentView(R.layout.main_screen);
//從ViewFlipper 對象中設置動(dòng)畫(huà)中一幀 。
mFlipper.setInAnimation(AnimationUtils.loadAnimation(this,
R.anim.hyperspace_in));
// 在TextView 對象中設置文本內容。
TextView msgTextView = (TextView)findViewByID(R.id.msg);
msgTextView.setText(R.string.hello_message);
資源引用
一個(gè)在屬性(或者資源)里提供的數值可以被指向一個(gè)具體的資源。這常常被使用在布局文件中用于字符串(可以被本地化) 以及圖片(存在于其他文件中的),通過(guò)一個(gè)引用可以是包括顏色和整數的任何資源類(lèi)型。
例如,如果有 顏色資源, 我們可以將文本的顏色值寫(xiě)在布局文件中,顏色值可以從資源文件里取得:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="注意這里使用‘@’的前綴是說(shuō)明資源引用 -- 后面的文本是資源的名字
@[package:]type/name. 這里我們不需要指定包,因為我們在我們自己的包里引用資源。為了指定一個(gè)系統資源,你需要這樣寫(xiě):
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="另外一個(gè)例子,當你在布局文件里使用字符串,你必須做資源引用,這樣字符串才能被使用:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="這段代碼也能被用來(lái)創(chuàng )建資源間引用。例如,我們能這樣創(chuàng )建圖像資源:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable
id="my_background">@android:drawable/theme2_background</drawab
le>
</resources>
主題屬性引用
另一種資源數值允許你引用當前主題屬性值。這種屬性引用只能被用于特殊的資源類(lèi)以及XML 屬性中;它允許你根據現在主題風(fēng)格將你定制的UI 變得更標準化,而不用使用大量的具體數值。
這里有個(gè)例子,我們能在布局文件中將文本顏色設置為基本系統主題中定義好的標準顏色:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="注意除來(lái)我們將前綴'?'代替了,其他非常像資源引用。當你使用這個(gè)標記,系統會(huì )自動(dòng)查找你提供的屬性的名字 -- 資源工具知道肯定會(huì )有資源屬性相符合,你不需要詳細指定(?android:attr/android:textDisabledColor).
使用資源標識符到主題里去尋找相應的數據而不是直接使用原數據,其語(yǔ)法和模式是一樣的: ?[namespace:]type/name 這里的type 是可選擇的.
使用系統資源
許多系統資源應用程序是可以使用的。這樣的資源定義在"android.R"的類(lèi)里。 例如,你可以使用下面的代碼在屏幕上顯示一個(gè)標準的應用程序圖標:
public class MyActivity extends Activity
{
public void onStart()
{
requestScreenFeatures(FEATURE_BADGE_IMAGE);
super.onStart();
setBadgeResource(android.R.drawable.sym_def_app_icon);
}
}
用相似的方法,這段代碼能將你的屏幕變成系統定義的標準的“綠色背景”:
public class MyActivity extends Activity
{
public void onStart()
{
super.onStart();
setTheme(android.R.style.Theme_Black);
}
}
對于不同的語(yǔ)言和設置支持不同的資源
你可以根據產(chǎn)品界面語(yǔ)言以及硬件配置設置不同的資源。注意,雖然你可以包含不同的字串,布局以及其他資源,但開(kāi)發(fā)包(SDK)不會(huì )給你顯式的方法去指定不同的資源去加載。Android 檢測你的硬件以及位置信息選擇合適的設置去加載。用戶(hù)可以到設備上的設置界面去選擇不同的語(yǔ)言。
要包含不同的資源,在同一目錄下創(chuàng )建并行的文件夾,在每個(gè)文件夾后加上合適的名字,這個(gè)名字能表明一些配置信息(如語(yǔ)言,原始屏幕等等)。例如,這里的項目字符串文件一個(gè)是英文版的,另一個(gè)是法文版的:
MyApp/
res/
values-en/
strings.xml
values-fr/
strings.xml
Android 支持不同類(lèi)型的修飾語(yǔ),并可以加多條在文件夾名的后面, 修飾語(yǔ)之間以破折號分開(kāi)。例如:一個(gè)繪圖資源類(lèi)指定全部配置名稱(chēng)命名會(huì )像這樣:
MyApp/
res/
drawable-en-rUS-port-160dpi-finger-keysexposed-qwerty-dpad-480
x320/
更典型的,你可以?xún)H僅指定部分特定的配置選項。只要保證所有的數值都是按順序排列:
MyApp/
res/
drawable-en-rUS-finger/
drawable-port/
drawable-port-160dpi/
drawable-qwerty/
修飾語(yǔ)值
語(yǔ)言 兩個(gè)小寫(xiě)字母 ISO 639-1。例如: en, fr, es
地區 兩個(gè)大寫(xiě)字母加上一個(gè)小寫(xiě)字母'r' ISO 3166-1-alpha-2。 例如:
rUS, rFR, rES
屏幕方向 port, land, square
屏幕像素
92dpi, 108dpi, 等等。
觸摸屏類(lèi)型 notouch, stylus, finger
鍵盤(pán)是否有效 keysexposed, keyshidden
基本文本輸入模式
nokeys, qwerty, 12key
無(wú)觸摸屏的主要導航模式
notouch, dpad, trackball, wheel
屏幕分辨率
320x240, 640x480, 等等。大分辨率需要開(kāi)始指定。
這個(gè)列表不包含一些特殊的參數,如載體,商標,設備/硬件,制造商。任何應用程序需要知道的信息都在資源修飾語(yǔ)里有說(shuō)明。
這里有一些通用的關(guān)于資源目錄的命名指導:
· 各個(gè)變量用破折號分開(kāi) (每個(gè)基本的目錄名后跟一個(gè)破折號)
· 變量大小寫(xiě)敏感(其大小寫(xiě)法必須始終一致)例如,
o 一個(gè)drawable 的目錄必須命名為 drawable-port, 而不是drawable-PORT。
o 你不能有兩個(gè)目錄命名為 drawable-port 以及 drawable-PORT,
甚至故意將"port" 和 "PORT"指為不同的參數也不可以。
· 一個(gè)式子里同一個(gè)類(lèi)型修飾語(yǔ)中只有一個(gè)值是有效的(你不能指定像這樣
drawable-rEN-rFR/)
· 你可以指定多個(gè)參數去定義不同的配置,但是參數必須是上面表格里的。例如,
drawable-en-rUS-land 意思在US-English 的機器里載入風(fēng)景視圖。
· Android 會(huì )尋找最適合當前配置的目錄,這會(huì )在下面描述
· 表格里所列的參數是用來(lái)打破平衡以防止多重路徑限制。 (看下面的例子)
· 所有目錄,無(wú)論是限制的,還是不限制的,只要在 res/ 目錄下.一些目錄是不能嵌套的(這樣 res/drawable/drawable-en 是不可以的)
· 所有的資源在被代碼引用中最好都使用簡(jiǎn)單的、不加修飾的名字,如果一個(gè)資源這樣命名:
MyApp/res/drawable-port-92dp/myimage.png
它將這樣被引用:
R.drawable.myimage (code)
@drawable/myimage (XML)
Android 如何找到最合適的目錄
Android 將會(huì )挑出哪些基本資源文件在運行時(shí)會(huì )被使用,這依靠當前的配置。 選擇過(guò)程如下:
1. 刪去一些和當前設備配置不符合的資源。例如,如果屏幕的像素是108dpi,這可以刪除 MyApp/res/drawable-port-92dpi/.
2. MyApp/res/drawable/myimage.png
3. MyApp/res/drawable-en/myimage.png
4. MyApp/res/drawable-port/myimage.png
5. MyApp/res/drawable-port-92dpi/myimage.png
6. 挑出一些最經(jīng)常符合配置的資源。例如,如果我們的地區是 en-GB, 方向是
port,那我們有兩個(gè)符合配置的選項: MyApp/res/drawable-en/ 和
MyApp/res/drawable-port/. 這個(gè)目錄 MyApp/res/drawable/ 可以被
刪除了,因為當另外一個(gè)有一次匹配正確,而它沒(méi)有。
7. MyApp/res/drawable/myimage.png
8. MyApp/res/drawable-en/myimage.png
9. MyApp/res/drawable-port/myimage.png
10. 根據配置的優(yōu)先級選取最終適合的文件,它們按順利被排列在上面的表格里。更確切得說(shuō),語(yǔ)言匹配比方位匹配更重要, 所以我們可以通過(guò)選擇語(yǔ)言文件來(lái)平衡,MyApp/res/drawable-en/.
11. MyApp/res/drawable-en/myimage.png
12. MyApp/res/drawable-port/myimage.png
術(shù)語(yǔ)
資源系統將一系列分散內容集合在一起形成最終的完整的資源功能,去幫助我們了解整個(gè)系統。這里有一些核心概念以及組件的概要說(shuō)明,你在開(kāi)發(fā)中將可能使用到:
最終文件: 應用程序的獨立的數據包。這包含所有從java 程序編譯成的目標文件,圖像(例如PNG 圖片), XML 文件等等。這些文件以一種特定的方式組織在一起,在程序打包最后時(shí),它們被打包成一個(gè)獨立的ZIP 文件。
aapt: Android 最終文件打包工具。這個(gè)工具產(chǎn)生最終程序的ZIP 文件。除了將最終的元數據文件打包在一起,它也解析資源定義到最終的二進(jìn)制數據里。
資源表:aapt 工具產(chǎn)生的特殊的文件,描述了所有在程序/包里的資源。這個(gè)文件可以通過(guò)資源類(lèi)來(lái)訪(fǎng)問(wèn);它不能直接和應用程序接觸。
資源: 資源表里一條記錄描述的是單一的命名值。大體上, 資源分成兩種:基本的和包裝的.資源標識符: 在資源表里所有的資源都被唯一的整數標識著(zhù)。所有的代碼中(資源描述,XML 文件,Java 源代碼)你可以直接使用符號名代替真實(shí)的整數數值。
基本資源: 所有基本資源都可以被寫(xiě)成一個(gè)簡(jiǎn)單的字串,使用一定的格式可以描述資源系統里各種不同的基本類(lèi)型:整數,顏色,字串,其他資源的引用,等等。像圖片以及XML 描述文件這些復雜資源,被以基本字串資源儲存,它們的值就是相關(guān)最終數據文件的路徑。
包裝資源: 有一種特殊類(lèi)型的資源,不是簡(jiǎn)單的字符串,而是有一個(gè)隨意的名字/數值配對列表。每個(gè)數值可以對應它本身的資源標識,每個(gè)值可以持相同類(lèi)型的字符串格式的數據作為一個(gè)正常的資源。包裝資源支持繼承:一個(gè)包里的數據能從其他包里繼承,有選擇地替換或者擴展能產(chǎn)生你自己需要的內容。
種類(lèi): 資源種類(lèi)是對于不同需求的資源標識符而言的。例如,繪制資源類(lèi)常常實(shí)例化繪制類(lèi)的對象,所以這些包含顏色以及指向圖片或XML 文件的字符串路徑數據是原始數據。其它常見(jiàn)資源類(lèi)型是字符串(本地化字符串),顏色(基本顏色),布局(一個(gè)指向XML 文件的字串路徑,它描述的是一個(gè)用戶(hù)界面)以及風(fēng)格(一個(gè)描述用戶(hù)接口屬性的包裝資源)。還有一個(gè)標準的“attr”資源類(lèi)型,它定義了命名包裝數據以及XML 屬性的資源標識符。
風(fēng)格: 包含包裝資源類(lèi)型的名字常常用來(lái)描述一系列用戶(hù)接口屬性。例如,一個(gè)
TextView 的類(lèi)可能會(huì )有一個(gè)描述界面風(fēng)格的類(lèi)來(lái)定義文本大小,顏色以及對齊方式。
在一個(gè)界面布局的XML 文件中,可以使用“風(fēng)格” 屬性來(lái)確定整體界面風(fēng)格,它的值就是風(fēng)格資源的名字。
風(fēng)格類(lèi): 這里將詳述一些屬性資源類(lèi)。其實(shí)數據不會(huì )被放在資源表本身,通常在源代碼里它以常量的形式出現,這也可以使你在風(fēng)格類(lèi)或者XML 的標簽屬性里方便找到它的值。例如,Android 平臺里定義了一個(gè)“視圖”的風(fēng)格類(lèi),它包含所有標準視圖的屬性:
畫(huà)圖區域,可視區域,背景等。這個(gè)視圖被使用時(shí),它就會(huì )借助風(fēng)格類(lèi)去從XML 文件取得數據并將其載入到實(shí)例中。
配置: 對許多特殊的資源標識符,根據當前的配置,可以有多種不同的值。配置包括地區(語(yǔ)言和國家),屏幕方向,屏幕分辨率,等等。當前的配置用來(lái)選擇當資源表載入時(shí)哪個(gè)資源值生效。
主題: 一個(gè)標準類(lèi)型的資源能為一個(gè)特殊的上下文提供全局的屬性值。例如,當應用工程師寫(xiě)一個(gè)活動(dòng)時(shí),他能選擇一個(gè)標準的主題去使用,白色的或者黑色的;這個(gè)類(lèi)型能提供很多信息,如屏幕背景圖片/顏色,默認文本顏色,按鈕類(lèi)型,文本編輯框類(lèi)型,文本大小,等。當布置一個(gè)資源布局時(shí),控件(文本顏色,選中后顏色,背景)的大部分設置值取自當前主題;如果需要,布局中的風(fēng)格以及屬性也可以從主題的屬性中獲得。
覆蓋層: 資源表不能定義新類(lèi)型的資源,但是你可以在其他表里替換資源值。就像配置值,這可以在裝載時(shí)候進(jìn)行;它能加入新的配置值(例如,改變字串到新的位置),替換現有值(例如,將標準的白色背景替換成"Hello Kitty"的背景圖片),修改資源包(例如修改主題的字體大小。白色主題字體大小為18pt)。這實(shí)際上允許用戶(hù)選擇設備不同的外表,或者下載新的外表文件。
資源引用
資源引用 這份文檔提供了不同類(lèi)型資源的詳細列表,并提供了如何在Java 代碼中使用資源以及如何引用資源的描述。
國際化和本地化
即將完成: 國際化和本地化是非常關(guān)鍵的,但現在的SDK 還沒(méi)有完全支持好。當SDK成熟時(shí),這個(gè)章節會(huì )包含Android 平臺國際化和本地化的相關(guān)信息。 那時(shí),外部字串以及良好的結構將會(huì )使得創(chuàng )建和使用資源變得更省事。
三、開(kāi)發(fā)工具箱
Android 設計哲學(xué)
即使平臺之間有很大的不同,但是如何利用API 創(chuàng )建應用程序的學(xué)習過(guò)程是大同小異的。一般來(lái)說(shuō),有兩個(gè)步驟:首先,應該知道怎么用API 實(shí)現你的功能。其次,要了解平臺間的細微差別。換句話(huà)說(shuō),首先你應該學(xué)會(huì )如何創(chuàng )建應用程序(了解應用程序的基本結構等),然后就要學(xué)會(huì )根據具體情況實(shí)現這個(gè)應用程序。
相比而言,第二階段(學(xué)習使用正確的方法來(lái)實(shí)現應用程序)通常需要很長(cháng)一段時(shí)間,在這個(gè)過(guò)程中你會(huì )不斷地寫(xiě)代碼,犯錯誤,然后從錯誤中吸取教訓。顯然,這不是一個(gè)有效的學(xué)習方法,本小節和下面的一些連接針對這向你伸出援助之手,教你怎么學(xué)習創(chuàng )建你的應用程序。
在此之前,先講一個(gè)要點(diǎn):成功的應用程序往往提供一個(gè)突出的用戶(hù)體驗。當Android團隊構建了一個(gè)有著(zhù)健壯核心的系統時(shí),大多數的用戶(hù)體驗將來(lái)源于用戶(hù)和應用程序之間的的交互。因此,我們鼓勵你們花時(shí)間去構建應用程序優(yōu)秀的用戶(hù)體驗。
顯著(zhù)的用戶(hù)體驗體現在三個(gè)核心特征上:1、快速;2、響應;3、無(wú)縫。當然,自從計算機出現以后,每一個(gè)平臺都曾經(jīng)有過(guò)類(lèi)似的三種性質(zhì)。盡管如此,每個(gè)平臺實(shí)現這些特性的方式也有所不同;下面將會(huì )簡(jiǎn)單的介紹在A(yíng)ndroid 平臺下面你的應用程序將如何達到這些要求。
快速(Fast)
Android 程序執行應該是很快的。當然,準確來(lái)說(shuō)它的程序應該執行的很有效率(有效率才會(huì )快)。在目前的計算機世界里喲這樣一個(gè)傾向:假設摩爾定律能夠最終解決我們所有的問(wèn)題。當這種傾向遇到嵌入式應用程序的時(shí)候,摩爾定律就變得有些復雜了。
與桌面和服務(wù)應用程序不一樣,摩爾定律在移動(dòng)設備應用程序上面不是真正適用的。摩爾定律實(shí)際上是關(guān)于電子晶體管集成密度的,它原本的含義是:隨著(zhù)時(shí)間的流逝,在給定的電路尺寸上面可以集成更多的電路。對于桌面和服務(wù)應用陳旭來(lái)說(shuō),它的含義是:
你可以打包更多的“速度”在一個(gè)大小差不多的芯片上面,速度的提高,其他相應的一些性能也會(huì )顯著(zhù)的提高。對以像手機這樣的嵌入式應用程序來(lái)說(shuō),相反的,使用摩爾定律是為了使芯片變得更小。這樣,隨著(zhù)芯片密度的提高,相同功能的芯片會(huì )變得越來(lái)越小,功耗會(huì )越來(lái)越低,從而使手機做的越來(lái)越小,電池的持續的時(shí)間越來(lái)越長(cháng)。這樣就導致手持嵌入式設備相對于桌面系統來(lái)說(shuō)正在以一種比較慢二實(shí)際的速度在增漲。因而,對于嵌入式設備來(lái)說(shuō),摩爾定律就意味著(zhù)更多的功能和更少的功耗,速度只是次要的。這就是我們要寫(xiě)出高效代碼的原因:你不能天真的認為電話(huà)在速度上面的增漲速度和桌面、服務(wù)應用程序是一樣的。一般來(lái)說(shuō),高效的代碼意味著(zhù)最小的內存占用,意味著(zhù)緊湊的風(fēng)格,意味著(zhù)避免了因為某種語(yǔ)言和編碼習慣對性能的影響。我們用面向對象這個(gè)概念來(lái)理解,大部分這樣的工作都是在方法層面上,與實(shí)際的代碼,循環(huán)等等相類(lèi)似。
在 “編寫(xiě)高效Android” 一文中,我們會(huì )對此做詳細的介紹。
響應(Responsive)
我們有可能能夠編寫(xiě)贏(yíng)得世界上所有的性能測試的代碼,但是用戶(hù)使用起來(lái)會(huì )感到很惱火,這是因為應用程序沒(méi)有足夠的響應性——讓人感覺(jué)反映遲鈍,在關(guān)鍵的時(shí)刻失靈,或者處理輸入太慢。在A(yíng)ndroid 平臺下,那些響應性不夠的應用程序會(huì )經(jīng)常彈出"Application Not Responding" (ANR)這樣的致命消息。
通常,這會(huì )在應用程序不能響應用戶(hù)的輸入的情況下發(fā)生。例如,你的應用程序在一些I/O 操作上(如網(wǎng)絡(luò )接口調用)阻塞,這是主線(xiàn)程將不會(huì )處理用戶(hù)的輸入事件,一段時(shí)間之后應用系統就會(huì )認為你的程序掛起了,就會(huì )給一個(gè)選項給用戶(hù)詢(xún)問(wèn)是否結束它。同樣的,如果你的應用程序花費很多時(shí)間去構建內存中的一個(gè)結構、或者計算游戲的下一步,這時(shí)系統同樣會(huì )認為程序已經(jīng)掛起。當碰到上面情況的時(shí)候,要確保計算的高效性,但是即使是最高效的代碼也需要花費時(shí)間。
在上面兩個(gè)例子中,問(wèn)題的解決方案是建立一個(gè)子線(xiàn)程來(lái)處理大部分的工作,這樣就能保證你的主線(xiàn)程(響應用戶(hù)界面事件)一直運行,這樣防止系統認為你的程序已經(jīng)僵化。因為這種線(xiàn)程的實(shí)現一般在“類(lèi)”(CLASS)這個(gè)層次上,你可以把響應當成是類(lèi)的問(wèn)題來(lái)處理(這里,可以和方法層次描述的基本性能相比較)。
這里只是一個(gè)簡(jiǎn)單的介紹,在 “構建響應Android 應用程序” 一文中對于應用程序的響應性有詳細的介紹。
無(wú)縫性(Seamless)
即使是你的應用程序執行很快,并且具有很高的響應性,它仍然有可能讓用戶(hù)苦惱。一個(gè)常見(jiàn)的例子是后臺進(jìn)程(比如Android 的 Service 和 BroadcastReceiver)對某些事件可能會(huì )突然彈出一個(gè)UI 響應。這似乎是無(wú)關(guān)緊要的,并且一般開(kāi)發(fā)者會(huì )認為這這是正常的,因為他們花費了戴亮時(shí)間去測試和使用自己的應用程序??墒?,Android 應用程序模型的構建是能夠允許用戶(hù)在不同的應用程序之間進(jìn)行流暢的切換。這就意味著(zhù),當你的后臺進(jìn)程實(shí)際上彈出那個(gè)UI 的時(shí)候,用戶(hù)可能正在系統的其他部分中,做一些其他的事情,如在接電話(huà)。想像一下,如果SMS 服務(wù)每次都會(huì )在文本消息傳入時(shí)彈出一個(gè)對話(huà)框,這很快就會(huì )使用戶(hù)崩潰。這就是為什么Android 標準對于這些事件使用的是通知(Notifications)機制;這使用戶(hù)能夠自己控制。
這僅僅是一個(gè)例子,相似的例子數不勝數。比如,如果Activities 沒(méi)有正確的實(shí)現onPause()方法和其他生命周期方法,這將會(huì )導致數據丟失?;蛘呷绻愕膽贸绦蛴幸獾谋┞稊祿o其他應用程序使用,你應該使用一個(gè)ContentProvider,而不是用一個(gè)路人皆可見(jiàn)的未加工過(guò)的文件或者數據庫。
這些例子有一個(gè)共同的特點(diǎn),他們都涉及到程序與程序或則程序與系統之間的交互。系統被設計為將多個(gè)應用程序視為一種松耦合組件的聯(lián)合,而不是大塊的代碼黑盒。這就允許作為開(kāi)發(fā)人員的你將整個(gè)系統看作是一個(gè)這些組件的大聯(lián)合。這允許你干凈地封裝,無(wú)縫地和其他應用程序結合,因而你能設計自己喜歡的程序。
這使一種組件層次的概念(與性能和響應的類(lèi)層次和方法層次相對應)。至于怎樣編寫(xiě)無(wú)縫性能很高的代碼, “與系統相結合” 一文中將會(huì )對此做出介紹,提供代碼提示和最佳實(shí)例。
構建自定義組件
Android 中,你的應用程序程序與View 類(lèi)組件有著(zhù)一種固定的聯(lián)系,例如按鈕(Button)、文本框(TextView), 可編輯文本框(EditText), 列表框(ListView), 復選框(CheckBox),單選框(RadioButton), 滾動(dòng)條(Gallery), 微調器(Spinner), 等等,還有一些比較先進(jìn)的有著(zhù)特殊用途的View 組件,例如 AutoCompleteTextView, ImageSwitcher 和TextSwitcher。除此之外,種類(lèi)繁多的像 線(xiàn)性布局(LinearLayout), 框架布局(FrameLayout), 這樣的布局組件(Layout)也被認為是View 組件,他們是從View類(lèi)派生過(guò)來(lái)的。
你的應用程序就是這些控制組件和布局組件以某種方式結合顯示在屏幕上,一般來(lái)說(shuō)這些組件對你來(lái)說(shuō)基本夠用,但是你也應該知道你是可以通過(guò)類(lèi)繼承創(chuàng )建屬于自己的組件,一般可以繼承像View、Layouts(布局組件)這樣的組件,甚至可以是一些比較高級的控制類(lèi)組件。下面我們說(shuō)一下為什么要繼承:
· 你可以為實(shí)現某種功能創(chuàng )建一個(gè)完全自定義風(fēng)格的組件,例如用二維的圖形創(chuàng )建控制組件實(shí)現聲音的控制,就像電子控制一樣。
· 你可以把幾種組件結合形成一個(gè)新的組件,你的組件可能同時(shí)包含ComboBox
(一個(gè)能輸入的文本列表)和dual-pane selector control(左右兩個(gè)List 窗口,
你可以分配窗口每一項的從屬關(guān)系)等等。
· 你可以創(chuàng )建自己的布局組件(Layout)。SDK 中的布局組件已經(jīng)提供了一系列的選項讓你打造屬于自己的應用程序,但是高級的開(kāi)發(fā)人員會(huì )發(fā)現根據現有的
Layout 組件開(kāi)發(fā)新的Layout 組件是很有必要的,甚至是完全從底層開(kāi)發(fā)新的組
件。
· 你可以覆蓋一個(gè)現有組件的顯示或功能。例如,改變EditText(可編輯文本)組件在屏幕上的顯示方式(可以參考Notepad 的例子,里面教你如何創(chuàng )建一個(gè)下劃線(xiàn)的顯示頁(yè)面)。
· 你可以捕獲像按鍵按下這樣的事件,以一些通用的方法來(lái)處理這些事件(一個(gè)游戲的例子)。
為了實(shí)現某種目標你可能很有必要擴展一個(gè)已經(jīng)存在的View 組件,下面我們結合一些例子教你如何去做。
基本方法(The Basic Approach )
下面的一些步驟都比較概括,教你如何創(chuàng )建自己的組件:
1. 讓你的類(lèi)(Class)繼承一個(gè)現有的View 類(lèi)或View 的子類(lèi)。
2. 重載父類(lèi)的一些方法:需要重載的父類(lèi)方法一般以‘on’開(kāi)頭,如onDraw(),onMeasure()和 onKeyDown()等等。 這個(gè)在A(yíng)ctivity 或則 ListActivity 派生中同樣適用,你需要重載一些生命周期函數和一些其他功能性的HOOK 函數。
3. 使用你的繼承類(lèi):一旦你的繼承類(lèi)創(chuàng )建完成,你可以在基類(lèi)能夠使用的地方使用你的繼承類(lèi),但完成功能就是你自己編寫(xiě)的了。
繼承類(lèi)能夠定義在activities 里面,這樣你能夠方便的調用,但是這并不是必要的(或許在你的應用程序中你希望創(chuàng )建一個(gè)所有人都可以使用的組件)。
完全自定義組件(Fully Customized Components)
完全自定義組件的方法可以創(chuàng )建一些用于顯示的圖形組件(graphical components),也許是一個(gè)像電壓表的圖形計量器,或者想卡拉OK 里面顯示歌詞的小球隨著(zhù)音樂(lè )滾動(dòng)。
無(wú)論那種方式,你也不能單純的利用組件的結合完成,無(wú)論你怎么結合這些現有的組件。
幸運的是,你可以以你自己的要求輕松地創(chuàng )建完全屬于自己的組件,你會(huì )發(fā)現不夠用的只是你的想象力、屏幕的尺寸和處理器的性能(記住你的應用程序最后只會(huì )在那些性能低于桌面電腦的平臺上面運行)。
下面簡(jiǎn)單介紹如何打造完全自定義的組件:
1. 最為通用的VIEW 類(lèi)的父類(lèi)毫無(wú)疑問(wèn)是View 類(lèi),因此,最開(kāi)始你要創(chuàng )建一個(gè)基于此類(lèi)的一個(gè)子類(lèi)。
2. 你可以寫(xiě)一個(gè)構造函數從XML 文件中提取屬性和參數,當然你也可以自己定義這些屬性和參數(也許是圖形計量器的顏色和尺寸,或者是指針的寬度和幅度等等)
3. 你可能有必要寫(xiě)自己的事件監聽(tīng)器,屬性的訪(fǎng)問(wèn)和修改函數和一些組件本身的功能上的代碼。
4. 如果你希望組件能夠顯示什么東西,你很有可能會(huì )重載 onMeasure() 函數,因而你就不得不重載 onDraw() 函數。當兩個(gè)函數都用默認的,那么 onDraw()函數將不會(huì )做任何事情,并且默認的 onMeasure() 函數自動(dòng)的設置了一個(gè)100x100 —的尺寸,這個(gè)尺寸可能并不是你想要的。
5. 其他有必要重載的on... 系列函數都需要重新寫(xiě)一次。
onDraw()和onMeasure() onDraw()函數將會(huì )傳給你一個(gè) Canvas 對象,通過(guò)它你可以在二維圖形上做任何事情,包括其他的一些標準和通用的組件、文本的格式,任何你可以想到的東西都可以通過(guò)它實(shí)現。
注意: 這里不包括三維圖形如果你想使用三維的圖形,你應該把你的父類(lèi)由View 改為SurfaceView 類(lèi),并且用一個(gè)單獨的線(xiàn)程??梢詤⒖糋LSurfaceViewActivity 的例子。
onMeasure() 函數有點(diǎn)棘手,因為這個(gè)函數是體現組件和容器交互的關(guān)鍵部分,onMeasure()應該重載,讓它能夠有效而準確的表現它所包含部分的測量值。這就有點(diǎn)復雜了,因為我們不但要考慮父類(lèi)的限制(通過(guò)onMeasure()傳過(guò)來(lái)的),同時(shí)我們應該知道一旦測量寬度和高度出來(lái)后,就要立即調用setMeasuredDimension() 方法。
概括的來(lái)講,執行onMeasure()函數分為一下幾個(gè)階段:
1. 重載的onMeasure()方法會(huì )被調用,高度和寬度參數同時(shí)也會(huì )涉及到
(widthMeasureSpec 和heighMeasureSpec 兩個(gè)參數都是整數類(lèi)型),同時(shí)你應該考慮你產(chǎn)品的尺寸限制。這里詳細的內容可以參考View.onMeasure(int,int) (這個(gè)連接內容詳細的解釋了整個(gè)measurement 操作)。
2. 你的組件要通過(guò)onMeasure()計算得到必要的measurement 長(cháng)度和寬度從而來(lái)顯示你的組件,它應該與規格保持一致,盡管它可以實(shí)現一些規格以外的功能(在這個(gè)例子里,父類(lèi)能夠選擇做什么,包括剪切、滑動(dòng)、提交異?;蛘哂貌煌膮涤忠淮握{用onMeasure()函數)。
3. 一旦高度和寬度計算出來(lái)之后,必須調用setMeasuredDimension(int
width, int height),否則就會(huì )導致異常。
一個(gè)自定義組件的例子(A Customized Component Example)
在 API Demos 中的CustomView 提供了以一個(gè)自定義組件的例子,這個(gè)自定義組件在LabelView 類(lèi)中定義。
LabelView 例子涉及到了自定義組件的方方面面:
· 首先讓自定義組件從View 類(lèi)中派生出來(lái)。
· 編寫(xiě)帶參數的構造函數(參數可以來(lái)源于XML 文件)。這里面的一些處理都已經(jīng)在View 父類(lèi)中完成,但是任然有些Labelview 使用的自定義組件特有的新的參數需要處理。
· 一些標準的Public 函數,例如setText(), setTextSize(),
setTextColor()
· 重載onMeasure()方法來(lái)確定組件的尺寸(注意:在LabelView 中是通過(guò)一個(gè)私有函數measureWidth()來(lái)實(shí)現的)
· 重載onDraw()函數把Lable 顯示在提供的canvas 上。
在例子中,你可以通過(guò)custom_view_1.xml 看到自定義組件LabelView 的用法。在XML文件中特別要注意的是android:和app:兩個(gè)參數的混合運用,app:參數表示應用程序中被認為是LabelView 組件的個(gè)體,這些也會(huì )作為資源在R 類(lèi)中定義。
組件混合技術(shù)Compound Components (or Compound
Controls)
如果你不想創(chuàng )建一個(gè)完全自定義的組件,而是由幾個(gè)現有組件的組合產(chǎn)生的新的組件,那么混合組件技術(shù)就更加適合。簡(jiǎn)單的來(lái)說(shuō),這樣把幾個(gè)現有的組件融合到一個(gè)邏輯組合里面可以封裝成一個(gè)新的組件。例如,一個(gè)Combo Box 組件可以看作是是一個(gè)EditText 和一個(gè)帶有彈出列表的Button 組件的混合體。如果你點(diǎn)擊按鈕為列表選擇一項,
在A(yíng)ndroid 中,其實(shí)還有其他的兩個(gè)View 類(lèi)可以做到類(lèi)似的效果: Spinner 和
AutoCompleteTextView,,但是Combo Box 作為一個(gè)例子更容易讓人理解。
下面簡(jiǎn)單的介紹如何創(chuàng )建組合組件:
1. 一般從Layout 類(lèi)開(kāi)始,創(chuàng )建一個(gè)Layout 類(lèi)的派生類(lèi)。也許在Combo box 我們會(huì )選擇水平方向的LinearLayout 作為父類(lèi)。記住,其他的Layout 類(lèi)是可以嵌套到里面的,因此混合組件可以是任何組件的混合。注意,正如Activity 一樣,你既可以使用外部XML 文件來(lái)聲明你的組件,也可以嵌套在代碼中。
2. 在新的混合組件的構造函數中,首先,調用所有的父類(lèi)的構造函數,傳入對應的參數。然后可以設置你的混合組件的其他的一些方面,在哪創(chuàng )建EditText 組件,又在哪創(chuàng )建PopupList 組件。注意:你同時(shí)也可以在XML 文件中引入一些自己的屬性和參數,這些屬性和參數也可以被你的混合組件所使用。
3. 你也可以創(chuàng )建時(shí)間監聽(tīng)器去監聽(tīng)新組件中View 類(lèi)觸發(fā)的事件,例如,對List 選項單擊事件的監聽(tīng),你必須在此時(shí)間發(fā)生后更新你EditText 的值。
4. 你可能創(chuàng )建自己的一些屬性,帶有訪(fǎng)問(wèn)和修改方法。例如,允許設置EditText
初始值并且提供訪(fǎng)問(wèn)它的方法。
5. 在Layout 的派生類(lèi)中,你沒(méi)有必要去重載onDraw()和onMeasure()方法,因為L(cháng)ayout 會(huì )有比較好的默認處理。但是,如果你覺(jué)得有必要你也可以重載它。
6. 你也可能重載一些on 系列函數,例如通過(guò)onKeyDown()的重載,你可以通過(guò)按某個(gè)鍵去選擇列表中的對應的值。
總之,把Layout 類(lèi)作為基類(lèi)有下面幾個(gè)優(yōu)點(diǎn):
· 正如activity 一樣,你也可以通過(guò)XML 文件去聲明你的新組件,或者你也可以在代碼中嵌套。
· onDraw()函數和onMeasure()函數是沒(méi)有必要重載的,兩個(gè)函數已經(jīng)做得很好了。
· 你可以很快的創(chuàng )建你的混合組件,并且可以像單一組件那樣使用。
混合組件的例子(Examples of Compound Controls)
In the API Demos project 在A(yíng)PI Demos 工程中,有兩個(gè)List 類(lèi)的例子——Example 4
和Example 6,里面的SpeechView 組件是從LinearLayout 類(lèi)派生過(guò)來(lái),實(shí)現顯示演講顯示功能,對應的原代碼是List4.java 和List6.java。
調整現有組件(Tweaking an Existing Component)
在某些情況下,你可能有更簡(jiǎn)單的方法去創(chuàng )建你的組件。如果你應經(jīng)有了一個(gè)非常類(lèi)似的組件,你所要做的只是簡(jiǎn)單的從這個(gè)組件派生出你的組件,重在其中一些有必要修改的方法。通過(guò)完全自定義組件的方法你也可以同樣的實(shí)現,但通過(guò)沖View 派生產(chǎn)生新的組件,你可以簡(jiǎn)單獲取一些已經(jīng)存在的處理機制,這些很可能是你所想要的,而沒(méi)有必要從頭開(kāi)始。
例如,在SDK 中有一個(gè)NotePad 的例子(NotePad application )。該例子演示了很多Android 平臺實(shí)用的細節,例如你會(huì )學(xué)到從EditView 派生出能夠自動(dòng)換行的記事本。這還不是一個(gè)完美的例子,因為相比早期的版本來(lái)說(shuō),這些API 已經(jīng)感變了很多,但它確實(shí)說(shuō)明了一些問(wèn)題。
如果你還未查看該程序,現在你就可以在Eclipse 中導入記事本例程(或僅通過(guò)提供的鏈接查看相應的源代碼)。特別是查看NoteEditor.java 中的MyEditText 的定義。
下面有幾點(diǎn)要注意的地方:
1. 聲明(The Definition)
這個(gè)類(lèi)是通過(guò)下面一行代碼來(lái)定義的:
public static class MyEditText extends EditText
o 它是定義在NoteEditor activity 類(lèi)里面的,但是它是共有的
(public),因此如果有必要,它可以通過(guò)NoteEditor.MyEditText 從
NoteEditor 外面來(lái)調用。
o 它是static 類(lèi)(靜態(tài)類(lèi)),意味著(zhù)不會(huì )出現所謂的通過(guò)父類(lèi)訪(fǎng)問(wèn)數據的
“虛態(tài)方法”, 這樣就使該類(lèi)成為一個(gè)可以不嚴重依賴(lài)NoteEditor 的單獨
類(lèi)。對于不需要從外部類(lèi)訪(fǎng)問(wèn)的內聯(lián)類(lèi)的創(chuàng )建,這是一個(gè)很清晰地思路,保
證所產(chǎn)生的類(lèi)很小,并且允許它可以被其他的類(lèi)方便的調用。
o 它是EditText 類(lèi)的擴展,它是我們選擇的用來(lái)自定義的父類(lèi)。當我們
完成以后,新的類(lèi)就可以作為一個(gè)普通的EditText 來(lái)使用。
2. 類(lèi)的初始化
一般來(lái)說(shuō),父類(lèi)是首先調用的。進(jìn)一步來(lái)說(shuō),這不是一個(gè)默認的構造函數,而是
一個(gè)帶參數的構造函數。因為EditText 是使用從XML 布局文件提取出來(lái)的參
數進(jìn)行創(chuàng )建,因此我們的構造函數也要取出參數并且將這些參數傳遞給父類(lèi)。
3. 方法重載
在本例中,僅對onDraw()一個(gè)方法進(jìn)行重載。但你可以很容易地為你的定制組
件重載其他需要的方法。
對于記事本例子來(lái)說(shuō),通過(guò)重載onDraw()方法我們可以在EidtView 的畫(huà)布
(canvas)上繪制藍色的線(xiàn)條(canvas 類(lèi)是通過(guò)重寫(xiě)的onDraw()方法傳遞)。
該函數快要結束時(shí)要調用super.onDraw()函數。父類(lèi)的方法是應該調用,但
是在這個(gè)例子里面,我們是在我們劃好了藍線(xiàn)之后調用的。
4. 使用定制組件
現在,我們已經(jīng)有自己定制的組件了,但是應該怎樣使用它呢?在記事本例子中,
定制的組件直接在預定義的布局文件中使用,讓我們看一看res/layout 目錄
中的note_editor.xml 文件。
<view
xmlns:android="o 該自定義組件在XML 中是作為一個(gè)一般的View 類(lèi)來(lái)創(chuàng )建的,并且是通過(guò)全路徑包來(lái)描述的。注意這里內聯(lián)類(lèi)是通過(guò)NoteEditor$MyEditText 來(lái)表示的,這是Java 編程中引用內聯(lián)類(lèi)的標準方法。
o 在定義中的其他屬性和參數將傳遞給定制組件的構造函數,然后才傳到EditText 構造函數中,因此這些參數也是你使用EditText 組件的參數。注意,這里你也可以增加你自己的參數,我們將在下面討論這個(gè)問(wèn)題。這就是你全部需要做的,誠然這是一個(gè)簡(jiǎn)單的例子。但問(wèn)題的關(guān)鍵是:你的需求有多復雜,那么你的自定義組件就有多么復雜。
一個(gè)更為復雜的組件可能需要重載更多的on 系列函數,并且還要很多特有的函數來(lái)充分實(shí)現自定義組件的功能。唯一的限制就是你的想象力和你需要組件去執行什么工作。
現在開(kāi)始你的組件化之旅吧
如你所見(jiàn),Android 提供了一種精巧而又強大的組件模型,讓你盡可能的完成你的工作。從簡(jiǎn)單的組件調整到組件混合,甚至完全自定義組件,靈活的運用這些技術(shù),你應該可以得到一個(gè)完全符合你外觀(guān)要求的的Android 程序
Android 平臺的可選API
Android 適用于各種各樣的手機,從最低端直到最高端的智能手機。核心的Android API在每部手機上都可使用,但任然有一些API 接口有一些特別的適用范圍:這就是所謂的“可選API”。這些API 之所以是“可選的”,主要是因為一個(gè)手持設備并不一定要完全支持這類(lèi)API,甚至于完全不支持。例如,一個(gè)手持設備可能沒(méi)有GPS 或Wi-FI 的硬件。在這個(gè)條件下,這類(lèi)功能的API 任然存在,但不會(huì )以相同的方式來(lái)工作。例如Location API 任然在沒(méi)有GPS 的設備上存在,但極有可能完全沒(méi)有安裝功能提供者,意味著(zhù)這類(lèi)API 就不能有效地使用。
你的應用應該無(wú)障礙地運行或連接在一個(gè)可能不支持你API 的設備,因為你的設備上有這些上層接口(the classes)。當然執行起來(lái)可能什么也不會(huì )做,或者拋出一個(gè)異常。
每個(gè)API 會(huì )做些什么我們可以參考這些API 的說(shuō)明文檔,你應該編寫(xiě)你的程序來(lái)適當的處理這類(lèi)問(wèn)題。
Wi-Fi API
Wi-Fi API 為應用程序提供了一種與那些帶有Wi-FI 網(wǎng)絡(luò )接口的底層無(wú)線(xiàn)堆棧相互交流的手段。幾乎所有的請求設備信息都是可利用的,包括網(wǎng)絡(luò )的連接速度、IP 地址、當前狀態(tài)等等,還有一些其他可用網(wǎng)絡(luò )的信息。一些可用的交互操作包括掃描、添加、保存、結束和發(fā)起連接。Wi-Fi API 在 android.net.wifi 包中。
定位服務(wù)(Location-Based Services)
定位服務(wù)允許軟件獲取手機當前的位置信息。這包括從全球定位系統衛星上獲取地理位置,但相關(guān)信息不限于此。例如,未來(lái)其他定位系統可能會(huì )運營(yíng),屆時(shí),對其相應的API 接口也會(huì )加入到系統中。定位服務(wù)的API 在android.location 包中。
多媒體API(Media APIs)
多媒體API 主要用于播放媒體文件。這同時(shí)包括對音頻(如播放MP3 或其他音樂(lè )文件以及游戲聲音效果等)和視頻(如播放從網(wǎng)上下載的視頻)的支持,并支持"播放URI地址"(Note:URI 即是統一資源識別地址)模式-在網(wǎng)絡(luò )上直接播放的流媒體。技術(shù)上來(lái)說(shuō),多媒體API 并不是“可選的”,因為它總是要用到。但是不同的硬件環(huán)境上面可能有不同的編解碼的硬件機制,因而它又是“可選的”。多媒體API 在 android.media 包中。
基于OpenGL 的3D 圖形(3D Graphics with OpenGL)
Android 的主要用戶(hù)接口框架是一個(gè)典型的面向控件的類(lèi)繼承系統。但不要讓表面的情況迷惑了你,因為在它下面是一種非??斓?D 和3D 組合的圖形引擎,并且支持硬件加速。用來(lái)訪(fǎng)問(wèn)平臺3D 功能的API 接口是OpenGL ES API。和多媒體API 一樣,
聯(lián)系客服