讀古今文學網 > Android程序設計:第2版 > 靜態應用資源和上下文 >

靜態應用資源和上下文

瞭解了構建Android應用的一些基礎架構後,現在我們一起來探索其物理組成部分。

第1章介紹了Android SDK的基礎知識。第5章將詳細探討如何使用最常見的Android開發工具Eclipse IDE來管理項目。現在,我們先繼續來瞭解項目內的代碼是如何組織的。

重述一下,項目(project)是用於生成一個可部署的產出(artifact)的工作區。在Java領域中,該「產出」可能只是一個庫文件(.jar),庫文件自己無法運行,但是實現了某些特定的功能。另外,它可能是可部署的Web應用,或者是可雙擊運行的桌面應用。在Android領域中,該「產出」很可能是一個可單獨運行的服務:一個內容提供者、服務或活動。只被一個活動所使用的內容提供者很可能作為活動項目的一部分而存在。但是,當有第二個活動使用它時,就需要考慮對它進行重構,創建其自己的項目。

Android應用的項目目錄看起來如下:


AndroidManifest.xml
bin/
        ... compiled classes ...
gen/
        ... code automatically generated by the android build system ...
res/
    layout/
        ... contains application layout files ...
    drawable/
        ...contains images, patches, drawable xml ...
    raw/
        ... contains data files that can be loaded as streams ...
    values/
        ... contains xml files that contain strings, number values used in code ...
src/
    java/package/directories/
  

傳統上,Java編譯器期望項目的目錄樹包含其要解析的源文件(.java)和輸出生成的二進制文件(.class)。雖然沒有規定,但是如果這些文件樹有不同的根文件夾,通常是命名為src和bin,以便於項目管理。

在Android項目中,還有其他兩個重要的目錄樹,即res和gen。res目錄包含靜態資源的定義:顏色、常量字符串、佈局等。Android工具會對這些定義進行預處理,通過相關的應用代碼,把它們變成高度優化的表現方式和Java源代碼。自動生成的代碼和為對像AIDL(參見P122「AIDL和遠程過程調用」的內容)所創建的代碼,都放在gen目錄中。編譯器對這兩個目錄下的代碼進行編譯,生成的內容文件放到bin目錄中。下面將會揭曉為何res目錄對於使用Context對像訪問應用數據尤為重要。

注意:當把項目添加到版本控制系統中時,如Git、Subversion或Perforce,一定要排除bin目錄和gen目錄!

組織Java源代碼

把應用程序源代碼放在src目錄中。正如第2章所指出的,應該把所有的代碼放到一個包(package)中,該包的名稱是根據代碼所有者的域名生成的。舉個例子,假設你是一家大型網站awesome-android.net的開發人員,要為網站voracious-carrier.com開發天氣預測應用。你很可能會選擇把所有的代碼放在包com.voraciouscarrier.weatherprediction或com.voracious_carrier.weather_prediction中。雖然符號「-」是完全合法的DNS域名,但是它不是合法的Java包名。該應用的UI包名可能是com.voraciouscarrier.weatherprediction.ui,其模型所在的包名可能是com.voraciouscarrier.weatherprediction.futureweather。

如果查看項目的src目錄,會發現它只包含com目錄,com又包含voraciouscarrier目錄,依此類推。源目錄樹與package的樹結構一一對應。Java編譯器期望以這種組織方式來安排代碼結構,如果違反這種方式則可能會導致無法編譯代碼。

最後,當FutureWeather內容提供者使用較多時,需要創建自己的項目,要把它分解出來生成一個新的項目,其包的名稱不受最初創建它的應用的名稱所限制。手動完成這一分解將是一場噩夢。需要創建一個新的目錄結構,把文件正確地放到該目錄結構中,修改每個源文件第一行的包名,最後修改任何相關的引用變化。

Eclipse重構工具是執行這一過程的最好幫手。只需要單擊幾下,就可以創建包含獨立子樹結構的新項目,將內容提供者代碼剪切和粘貼到該子樹中,然後相應地對包進行重命名。Eclipse會修復大多數問題,包括引用變化。

值得提醒的是,過於簡化包名,如使用weatherprediction作為包名,將會是個壞習慣。即使你非常確定現在編寫的代碼使用範圍永遠不會超出當前上下文,但你也可能需要使用外部生成的代碼。不要使自己陷入命名衝突的困境中!

Resources

除了代碼以外,應用可能還需要存儲大量的數據來控制其運行時的行為。這些數據可能是要顯示的圖片或簡單的文本字符串,以指出要使用什麼樣的背景顏色或字體。這些數據稱為resources,這種方式和軟件最佳實踐一致,把數據和代碼分離開。所有這些信息一起構成了應用的context(上下文),在Android中對這些信息進行訪問需要借助於Context類。Activity類和Service類都是對Context類的擴展,這意味著所有的活動和服務都可以通過該指針訪問Context類型的數據。在後續的章節中,還將描述如何使用Context對像在運行時訪問應用的資源。

Android應用把圖像、圖標和用戶界面佈局文件都放在名為res的目錄中。res目錄通常至少包括4個子目錄,如下所示:

layout

包含Android用戶界面XML文件,這個文件將在第6章詳細介紹。

drawable

包含繪製相關的工具,如之前所描述的應用圖標。

raw

保存了應用執行時可能以stream(流)方式讀取的文件。原始文件是為運行的應用提供調試信息的一種很好的方式,不需要訪問網絡來檢索數據。

values

包含應用在執行中要讀取的值,或應用在某些使用中的靜態數據,如對UI字符串的國際化。

應用通過方法Context.getResour ces和類R來訪問這些目錄下的資源。

要訪問res目錄下的數據,傳統的Java開發人員可能會考慮編寫代碼構建相關的資源文件路徑,然後使用文件API打開這些資源。加載完資源字節後,應用開發人員可能會期望解析特定應用格式,從而能夠訪問每個應用都需要的各種項:圖像、字符串和數據文件。預計到每個應用都會加載類似的信息,Android提供了一個工具,它集成在Eclipse中,通過它應用可以很容易地訪問這些資源,並且也對項目資源進行了標準化。

Eclipse和Android SDK一起創建了一個名為gen的目錄,該目錄中包含了名字都是R的類,這個類保存在Java應用包中,而包的名字由Android Manifest指定。類R文件包含了多個字段,這些字段能夠唯一標識應用包中的所有資源。開發人員調用方法Context.getResources可以獲取包含有應用資源的android.content.res.Resources實例(可以直接調用類Context的方法,因為類Activity和類Service都是對類Context的擴展)。如下所示為開發人員調用Resource對像來獲取期望類型的資源的一種方式:


// code inside an Activity method
String helloWorld = this.getResources.getString(R.string.hello_world);
int anInt = this.getResources.getInteger(R.integer.an_int);
  

你會發現在Android中,類R幾乎無處不在,它使得對資源的訪問變得非常容易,如訪問UI佈局文件的組件。

警告:Java作用域規則指出同一個package中的類彼此可見,即使這些類不是顯式導入(通過import語句)。即使這些類不在文件系統的同一個目錄下,這個規則也適用。因為Android工具會自動把R類放到以清單文件的package屬性命名的Java包中,如果該包剛好包含了應用的大部分代碼,使用就會很方便。清單文件中的package屬性是應用的唯一命名空間,系統並不要求該命名空間名稱和根所在的package名稱相同,但是通常建議保持一致。

應用的清單文件

Android要求應用在XML文件AndroidManifest.xml中顯式地描述其內容。在AndroidManifest.xml文件中,應用對內容提供者、服務、需要的權限及其他元素進行聲明。應用上下文會準備好這些數據,使之在Android運行時中可用。清單文件把Android應用組織成了定義良好的結構,這個結構所有應用都必須遵守,Android操作系統由此可以在可管理的環境中加載並執行這些應用。這個結構由一個通用的目錄佈局和目錄中一組通用的文件類型組成。

可以看到,Android應用的四個組件(Activity、Service、ContentProvider和BroadcastReceiver)奠定了Android應用開發的基礎(如圖3-6所示)。要使用其中任何一個組件,Android應用都必須在其AndroidManifest.xml文件中包含相應的聲明。

圖3-6:四類Android組件

Application類

Android的第5個組件是Application類。但是很多Android應用沒有繼承類Application。因為在大多數情況下,繼承類Application是不必要的,Android項目嚮導(project wizard)不會自動創建類Application。

初始化AndroidManifest.xml文件中的參數

下面這段代碼就是在第1章中介紹的test應用的Android清單文件。test應用的功能只是顯示Android應用的基礎佈局。它的清單文件中包含了之前討論過的一些基礎元素:

如同所有結構良好的XML文件一樣,該文件的第1行是標準的XML版本聲明和使用的字符編碼,然後定義了一些參數並聲明了整個應用所需要的權限。下面這個列表總結並介紹了用到的標籤:

manifest

package=\"com.oreilly.demo.pa.ch01.testapp\"

應用組件所在的默認包。

android:versionCode

這是一個整數,指應用的版本號。每個應用都應該包含一個版本號,而且發佈給用戶的版本號應該總是遞增的。通過這種方式,其他程序(如Android Market、安裝包和啟動包)能夠很容易地根據版本號判斷出哪個版本更新一些。.apk文件的文件名中應該包含這個版本號,這樣可以很清楚地知道這個文件中包含的是哪個版本。

android:versionName

它是一個字符串,跟我們通常看到的應用的版本號更像一些,如1.0.3。這個字符串就是展示給用戶的版本號(根據你的應用,或者根據其他應用)。命名規則可以自己定,但是一般來說,採用的是類似m.n.o這樣的機制(可以使用任意個數字表示)來識別應用的連續變化級別。

uses-permission android:name=...

在TestApp清單文件中,有4個聲明描述了應用希望使用的Android特徵,需要得到運行該應用的設備的用戶的顯式許可。當安裝應用時,會要求輸入許可。然後,Android會記住用戶認為可以(或不可以)運行該應用,允許訪問安全特徵。在Android中定義了很多許可,所有許可都在Android文檔中有描述(搜索android.Manifest.permission)。此外,還可以定義自己的許可,並使用它們來限制其他應用對你的應用中的功能的訪問,除非用戶對其他應用賦予該權限。例如,通常需要開通如下權限:

ACCESS_FINE_LOCATION

需要從GPS傳感器獲取地理位置信息。

CALL_PHONE

允許來自用戶的電話呼叫。

ACCESS_MOCK_LOCATION

當在模擬器中運行時,允許獲取偽位置信息。

INTERNET

允許建立因特網連接來檢索數據。

application

label

應用的可讀標籤。

icon=\"@drawable/icon\"

PNG文件的文件名包含你想要用於應用的圖標(icon)。在這個例子中,告訴Android SDK查找TestApp應用中res(resource)目錄的子目錄下的圖標文件。在Android Desktop(桌面)應用中,Android將為應用使用該圖標。

activity

現在,我們一起來看一下TestActivity的定義,首先一起來定義一些屬性,其中最重要的屬性如下:

android:name

Activity類名稱,activity的全名包括包名(該應用的包名是com.oreilly.demo.pa.ch01.testapp),但是由於該文件總是在包的名字空間下使用,因此不需要包括包名。可以把名字簡化成.TestActivity。實際上,即使是最開頭的點(.)也是可有可無的。

android:label

當activity顯示在屏幕上時,我們希望在Android屏幕上方顯示的內容。在strings.xml文件中定義了和應用匹配的標籤。

intent-filter

intent-filter告訴Android什麼時候可以運行該Activity。當應用要求Android實現某個Intent時,runtime(運行時)會查看可用的活動和服務,找到某項可以提供服務的。有兩個屬性可以設置:

action

一旦運行時確定要運行哪個應用,action會告訴Android如何啟動該應用。Android會查找可以解析MAIN action的活動。Launcher啟動的任意一個應用都需要有且僅有一個活動或服務完成MAIN action解析。

category

Android中的Intent resolver使用該屬性進一步限定查找其需要的Intent。因此,前提是我們希望Activity在User菜單中顯示,從而用戶可以通過選中該Activity來啟動應用。指定LAUNCHER可以完成這一點。如果沒有category屬性,應用也完全可以正常工作——只是你無法從Android桌面啟動應用。再次說明,通常情況下,每個應用應該是有且只有一個LAUNCHER,而且它應該和應用的啟動Activity在同一個intent filter中。

provider(提供者)

對內容提供者的聲明,name是provider類的名字,authorities是內容提供者應該處理的URI權限(URI authority)。URI權限提供了內容提供者URI的域字段,使得Android內容分辨率系統能夠定位要處理的某種類型的URI的提供者。在本章後面,將詳細說明客戶端是如何使用內容提供者的。並且聲明了一個名為TestProvider的提供者。

service(服務)

對應用支持的服務的聲明,其中name指定服務對應的類,label為服務提供可讀的標籤。我們定義了一個名為.TestService的service。

receiver(接收器)

對應用支持的廣播接收器的聲明,name還是表示接收類,label為接收器提供可讀的標籤。我們聲明了一個名為TestBroadcastReceiver的接收器。

Android應用打包:.apk文件

顯然,應用的最終靜態組成部分是對應用本身進行打包。Android提供了一個名為apkbuilder的應用,它能夠生成可安裝的Android應用文件,這些應用文件的擴展名是.apk。.apk是一種ZIP文件格式,它和很多其他面向Java的應用類似,包含應用說明、已經編譯的應用類和應用資源。Android還提供了aapt工具對文件進行打包,這些文件也是生成.apk文件,但是開發人員通常傾向於在其開發環境中使用該工具構建他們的應用。絕大多數用戶只是簡單地依賴其IDE來創建其.apk文件。

一旦開發人員創建了.apk文件,他可以選擇下面任意一種方式在設備上進行安裝:

·使用adb接口路徑,或者更常見的是使用IDE。

·使用SD卡。

·把文件存儲在Web服務器上。

·把文件上傳到Android Market,然後選擇Install。