轉載于 ?http://www.uml.org.cn/mobiledev/201211063.asp#2,文章是2012年之前發(fā)布的,雖然有點久了,但內容還是很值得看的。
1. Android應用程序
在目前Android大紅大紫的情況下,很多人對編寫Android應用程序已經(jīng)有了足夠深入的了解。即便是沒有充分的認識,在現(xiàn)在Android 手機已經(jīng)相當普及的情況下,大家至少也會知道Android的應用程序會是一個以.apk為后綴名的文件(在Windows系統(tǒng)里,還會是一個帶可愛機器 人圖標的文件)。那這個apk包又有什么樣的含義呢?
如果您正在使用Linux操作系統(tǒng),可以使用命令file命令來查看這一文件的類型。比如我們下載了一個Sample.apk的文件,則使用下面的命令:
$file Sample.apk
Sample.apk: Zip archive data, at least v1.0 to extract
對,沒有看錯,只一個簡單的zip文件。要是做過Java開發(fā)的人,可以對這種格式很親切,因為傳說中的.jar、.war格式,都是Zip壓縮格 式的文件。我們可繼續(xù)使用unzip命令將這一文件解壓(或是任何的解壓工具,zip是人類歷史是最會古老最為普及的壓縮格式之一,幾乎所有壓縮工具都支 持)。通過解壓,我們就得到了下面的文件內容:
AndroidManifest.xml,
classes.dex,
resources.arsc,
META-INF,
res,
到這里,我們就可以看到一個Android應用程序結構其實是異常簡單的。這五部分內容(其中META-INF和res是目錄,其他是文件)除了 META-INF是這一.apk文件的校驗信息,resources.arsc是資源的索引文件,其他三部分則構成了Android應用程序的全部。
AndroidManifest.xml,這是每個Android應用程序包的配置文件,這里會保存應用程序名字、作者、所實現(xiàn)的功能、以及一些權限驗證信息。但很可惜,在編譯完成的.apk文件里,這些文件都被編譯成了二進制版本,我們暫時沒有辦法看到內容,后面我們可以再看看具體的內容。
classes.dex,這則是Android應用程序實現(xiàn)的邏輯部分,也就是通過Java編程寫出來而被編譯過的代碼。這種特殊的格式,是Android里特定可執(zhí)行格式,是可由Dalvik虛擬機所執(zhí)行的代碼,這部分內容我們也會在后續(xù)的介紹Dalvik虛擬機的章節(jié)里介紹。
res,這一目錄里則保存了Android所有圖形界面設計相關的內容,比如界面應該長成什么樣子、支持哪些語言顯示等等。
從一個android應用程序的包文件內容,我們可以看到android應用程序的特點,這也是Android編程上的一些特征:
1 簡單:最終生成的結果是如些簡單的三種組成,則他們的編程上也不會有太大的困難性。這并不是說 Android系統(tǒng)里無法實現(xiàn)很復雜的應用程序,事實上Android系統(tǒng)擁有世界上僅次于iOS的應用程序生態(tài)環(huán)境,也擁有復雜的辦公軟件、大型3D游 戲。而只是說,如果要實現(xiàn)和構成同樣的邏輯,它必然會擁有其他格式混雜的系統(tǒng)更簡化的編程模式。
2 Java操作系統(tǒng):既然我們編譯得到的結果,classes.dex文件,是用于Java虛擬機 (雖然是Dalvik虛擬機,但實際上這一虛擬機只是一種特定的Java解析器和虛擬機執(zhí)行環(huán)境 )解析執(zhí)行的,于是我們也可以猜想到,我們的Android系統(tǒng),必然是一個Java操作系統(tǒng)。我們在后面會解釋,如果把Android系統(tǒng)直接看成 Linux內核和Java語言組合到一起的操作系統(tǒng)很不準確,但事實上Android,也還是Java操作系統(tǒng),Java是唯一的系統(tǒng)入口。
使用MVC設計模式:所謂的MVC,就是Model,View,Controller的首字母組合起來的一種設計模 式,主要思想就是把顯示與邏輯實現(xiàn)分離。Model用于保存上下文狀態(tài)、View用于顯示、而Controller則是用于處理用戶交互。三者之間有著如 下圖所示的交互模型,交互只到Controller,而顯示更新只通過View進行,這兩者再與Model交換界面狀態(tài)信息:

在現(xiàn)代的圖形交互相關的設計里,MVC幾乎是在圖形交互處理上的不二選擇,這樣系統(tǒng)設計包括一些J2EE的應用服務器框架,最受歡迎的 Firefox瀏覽器,iOS,MacOSX等等。這些使用MVC模式的最顯著特點就是顯示與邏輯分離,在Android應用程序里我們看到了用于邏輯實 現(xiàn)的classes.dex,也看到用于顯示的res,于是我們也可以猜想到在UI上便肯定會使用MVC設計模式。
當然,所謂的Android應用程序編程,不會只有這些內容。到目前為止,我們也只是分析.apk文件,于是我們可以回過頭來看看Android應用被編譯出來的過程。
2. Android編程
從編程角度來說,Android應用程序編程幾乎只與Java相關,而Java平臺本身是出了名跨平臺利器,理論上來說,所有Java環(huán)境里使用的 編程工具、IDE工具,皆可用于Android的編程。Android SDK環(huán)境里提供的編程工具,是基于標準的Java編譯工具ant的,但事實上,一些大型的Android軟件工程,更傾向于使用Maven這樣的并行化 編譯工具(maven.apache.org)。如果以前有過Java編程經(jīng)驗,會知道Java環(huán)境里的圖形化IDE(Integrated Development Environment)工具,并非只有Eclipse一種,實際上Java的官方IDE是NetBeans,而商用化的Java大型項目開發(fā)者,也可能 會比較鐘意于使用IntelliJ,而從底層開發(fā)角度來說,可能使用vim是更合適的選擇,可以靈活地在C/C++與Java代碼之間進行切換。總而言 之,幾乎所有的Java環(huán)境的編程工具都可以用于Android編程。
對于這些工具呢,熟悉工具的使用是件好事,所謂“磨刀不誤砍柴工”,為將來提升效率,這是件好事。但是要磨刀過多,柴沒砍著,轉型成“磨刀工”了。如果過多地在這些編程工具上糾結嘗試,反而忽視了所編代碼的本身,這倒會舍本逐末。
我們既然是研究Android編程,這時僅說明兩種Android官方提供的編程方法:使用Android SDK工具包編程,或是使用Eclipse + ADT插件編程。
2.1 使用Android SDK工具包
在Android開發(fā)過程中,如果Eclipse環(huán)境不可得的情況下,可以直接使用SDK來創(chuàng)建應用程序工程。首先需要安裝某一個版本的Android SDK開發(fā)包,這個工具包可以到http://developer.android.com/sdk/index.html這 個網(wǎng)址去下載,根據(jù)開發(fā)所用的主機是Windows、Linux還是MacOS X(MacOS僅支持Intel芯片,不支持之前的PowerPC芯片),下載對應的.zip文件,比如android-sdk_r19- linux.zip。下載完成后,解壓到一個固定的目錄,我們這里假定是通過環(huán)境變量$ANDROID_SDK_PATH指定的目錄。
下載的SDK包,默認是沒有Android開發(fā)環(huán)境支持的,需要通過tools目錄里的一個android工具來下載相應的SDK版本以用于開發(fā)。我們通過運行$ANDROID_SDK_PATH/tools/android會得到如下的界面:

在上面的安裝界面里選擇不同的開發(fā)工具包,其中Tools里包含一些開發(fā)用的工具,如我們的SDK包,實際上也會在這一界面里進行更新。而對于不同 的Android版本,1.5到4.1,我們必須選擇下載某個SDK版本來進行開發(fā)。而下載完之后的版本信息,我們既可以在這一圖形界面里看到,也可以通 過命令行來查看。
$ANDROID_SDK_PATH/tools/android list targets
id: 1 or "android-16"
Name: Android 4.1
Type: Platform
API level: 16
Revision: 1
Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in
ABIs : armeabi-v7a
----------
id: 2 or "Google Inc.:Google APIs:16"
Name: Google APIs
Type: Add-On
Vendor: Google Inc.
Revision: 1
Description: Android + Google APIs
Based on Android 4.1 (API level 16)
Libraries:
* com.google.android.media.effects (effects.jar)
Collection of video effects
* com.android.future.usb.accessory (usb.jar)
API for USB Accessories
* com.google.android.maps (maps.jar)
API for Google Maps
Skins: WVGA854, WQVGA400, WSVGA, WXGA800-7in, WXGA720, HVGA, WQVGA432, WVGA800 (default), QVGA, WXGA800
ABIs : armeabi-v7a
通過android list targets列出來的信息,可以用于后續(xù)的開發(fā)之用,比如對于不同的target,最后得到了id:1、id:2這樣的信息,則可以被用于應用程序工程 的創(chuàng)建。而細心一點的讀者會看到同一個4.1版本的SDK,實際可分為”android-16”和"Google Inc.:Google APIs:16",這樣的分界也還有有意義的,”android-16”用于“純”的android 4.1版的應用程序開發(fā),而“Google Inc.:Google APIs:16”則加入了Google的開發(fā)包。
配置好環(huán)境之后,如果我們需要創(chuàng)建Android應用程序。tools/android這個工具,同時也具備可以創(chuàng)建Android應用程序工程的能力。我們輸入:
$ANDROID_SDK_PATH/tools/android create project -n Hello -t 1 -k org.lianlab.hello -a Helloworld -p hello
這樣我們就在hello目錄里創(chuàng)建了一個Android的應用程序,名字是Hello,使用API16(Android 4.1的API版本),包名是org.lianlab.hello,而默認會被執(zhí)行到的Activity,會是叫Helloworld的Activity 類。
掌握Android工具的一些使用方法也是有意義的,比如當我們的Eclipse工程被破壞的情況下,我們依然可以手工修復這一Android應用程序工程?;蚴切枰薷脑摴こ痰腁PI版本的話,可以使用下面的命令:
$ANDROID_SDK_PATH/tools/android updateproject -t 2 -p .
在這個工程里,如果我們不加任何修改,會生成一個應用程序,這個應用程序運行的效果是生成一個黑色的圖形界面,打印出一行"Hello World, Helloworld"。如果我們需要對這一工程進行編譯等操作的話,剩下的事情就屬于標準的Java編譯了,標準的Java編譯,使用的是 ant(ant.apache.org)編譯工具。我們先改變當前目錄到hello,然后就可以通過” ant –projecthelp”來查看可以被執(zhí)行的Android編譯工程,
$ ant -projecthelp
Buildfile: /Users/wuhe/android/workspace/NotePad/bin/tmp/hello/build.xml
Main targets:
clean? ? ? Removes output files created by other targets.
debug? ? ? Builds the application and signs it with a debug key.
install? ? Installs the newly build package. Must be used in conjunction with a build target
(debug/release/instrument). If the application was previously installed, the application
is reinstalled if the signature matches.
installd? ? Installs (only) the debug package.
installi? ? Installs (only) the instrumented package.
installr? ? Installs (only) the release package.
installt? ? Installs (only) the test and tested packages.
instrument? Builds an instrumented packaged.
release? ? Builds the application in release mode.
test? ? ? ? Runs tests from the package defined in test.package property
uninstall? Uninstalls the application from a running emulator or device.
Default target: help
但如果只是編譯,我們可以使用antdebug生成Debug的.apk文件,這時生成的文件,會被放到bin/Hello-debug.apk。 此時生成的Hello-debug.apk,已經(jīng)直接可以安裝到Android設備上進行測試運行。我們也可以使用ant release來生成一個bin/Hello-release-unsigned.apk,而這時的.apk文件,則需要通過jarsigner對文件進 行驗證才能進行安裝。
通過antdebug這一編譯腳本,我們可以看到詳細的編譯過程。我們可以看到,一個Android的工程,最后會是通過如圖所示的方式生成最后的.apk文件。

把一個Android的源代碼工程編譯成.apk的Android應用程序,其過程如下:
1) 所有的資源文件,都會被aapt進行處理。所有的XML文件,都會被aapt解析成二進制格式,準確地說,這樣的二進制格式,是可以被直接映射到內存里的 二進制樹。做過XML相關開發(fā)的工程師,都會知道,XML的驗證與解析是非常消耗時間與內存的,而通過編譯時進行XML解析,則節(jié)省了運行時的開銷。當然 解析的結果最后會被aapt通過一個R.java保存一個二進制樹的索引,編程時可通過這個R.java文件進行XML的訪問。aapt會處理所有的資源 文件,也就是Java代碼之外的任何靜態(tài)性文件,這樣處理既保證了資源文件間的互相索引得到了驗證,也確保了R.java可以索引到這個應用程序里所有的 資源。
2) 所有的Java文件,都會被JDK里的javac工具編譯成bin目錄下按源代碼包結構組織的.class文件(.class是標準的Java可解析執(zhí)行 的格式),比如我們這個例子里生成的bin/classes/org/lianlab/hello/*.class文件。然后這些文件,會通過SDK里提 供的一個dx工具轉換成classes.dex文件。這一文件,就是會被Dalvik虛擬機所解析執(zhí)行的
3) 最后我們得到的編譯過的二進制資源文件和classes.dex可執(zhí)行文件,會通過一個apkbuilder工具,通過zip壓縮算法打包到一個文件里,生成了我們所常見的.apk文件。
4) 最后,.apk文件,會通過jarsigner工具進行校驗,這一校驗值會需要一個數(shù)字簽名。如果我們申請了Android開發(fā)者帳號,這一數(shù)字簽名就是 Android所分發(fā)的那個數(shù)字證書;如果沒有,我們則使用debug模式,使用本地生成的一個隨機的數(shù)字證書,這一文件位于~/.android /debug.keystore。
雖然我們只是下載了SDK,通過一行腳本創(chuàng)建了Android應用程序工程,通過另一行完成了編譯。但也許還是會被認為過于麻煩,因為需要進行字符界面的 操作,而且這種開發(fā)方式也不是常用的方式,在Java環(huán)境下,我們有Eclipse可用。我們可以使用Eclipse的圖形化開發(fā)工具,配合ADT插件使 用。
2.2 使用Eclipse+ADT插件
在Android環(huán)境里可以使用Java世界里幾乎一切的開發(fā)工具,比如NetBeans等,但Eclipse是Android官方標準的開發(fā)方 式。使用Eclipse開發(fā),前面提到的開發(fā)所需SDK版本下載,也是必須的,然后還需要在Eclipse環(huán)境里加裝ADT插件,Android Development Toolkit。
我們在Eclipse的菜單里,選擇”Help” à “Install New Software…”,然后在彈出的對話框里的Workwith:輸入ADT的發(fā)布地址:https://dl-ssl.google.com/android.eclipse,回車,則會得到下面的軟件列表。選擇Select All,將這些插件全都裝上,則得到了可用于Android應用程序開發(fā)的環(huán)境。

這里還需要指定SDK的地址,Windows或是Linux里,會是在菜單“Window” à “Preferences”,在MacOS里,則會是”Eclipse” à“Preferences” 。在彈出的對話框里,選擇Android,然后填入Android SDK所保存的位置。

點擊OK之后,則可以進行Android開發(fā)了。選擇”File” à “New”à “Project” à “Android”,在Eclipse 3.x版本里,會是“Android Project”,在Eclipse 4.x版本里,會是“Android Application Project”。如果我們需要創(chuàng)建跟前面字符界面下一模一樣的應用程序工程,則在彈出的創(chuàng)建應用程序對話框里填入如下的內容:

然后我們選擇Next,一直到彈出最后界面提示,讓我們選擇默認Activity的名字,最后點擊”Finish”,我們就得到一個Android 應用程序工程,同時在Eclipse環(huán)境里,我們既可以通過圖形化界面編輯Java代碼,也可以通過圖形化的界面編輯工具來繪制圖形界面。
(注意: 如果Android工程本身比較龐大,則最好將Eclipse里的內存相關的配置改大。在Windows和Linux里,是修改eclipse里的 eclipse.ini文件,而在MacOS里,則是修改Eclipse.app/Contents/MacOS/eclipse.ini。一般會將如下 的值加大成兩倍:
--launcher.XXMaxPermSize
512m
-vmargs
-Xms80m
-Xmx1024m
)
我們得到工程目錄,在Eclipse環(huán)境里會是如下圖所示的組織方式。代碼雖然是使用一模一樣的編譯方式,唯一的改變是,我們不再需要使用腳本來完 成編譯,我們可以直接使用Eclipse的”Project”à“Build project”來完成編譯過程。如果我們使用默認設置,則代碼是使用自動編譯的,我們的每次修改都會觸發(fā)增量式的編譯。

我們從這些android編程的過程,看不出來android跟別的Java編程模式有什么區(qū)別,倒至少驗證了我們前面對android編程特點的 猜想,就是很簡單。如果同樣我們使用Eclipse開發(fā)Java的圖形界面程序,需要大量地時間去熟悉API,而在Android這里學習的曲線被大大降 低,如果我們只是要畫幾個界面,建立起簡單的交互,我們幾乎無須學習編程。
而從上面的步驟,我們大概也可以得到Android開發(fā)的另一個好處,就是極大的跨平臺性,它的開發(fā)流程里除了JDK沒有提及任何的第三方環(huán)境需 求,于是這樣的開發(fā)環(huán)境,肯定可以在各種不同的平臺執(zhí)行。這也是Android上進行開發(fā)的好處之一,跨平臺,支持Windows,Linux與 MacOS三種。
我們再來看一個,我們剛才創(chuàng)建的這個工程里,我們怎么樣進行下一步的開發(fā)。在Android開發(fā)里,決定我們應用程序表現(xiàn)的,也就是我們從一個.apk文件里看到的,我們實際上只需要:
修改AndroidManifest.xml文件。AndroidManifest.xml是Android應用程序的主控文件,類型于Windows里的注冊表,我們通過它來配置我們的應用程序與系統(tǒng)相關的一些屬性。
修改UI顯示。在Android世界里,我們可以把UI編程與Java編程分開對待,處理UI控件 的語言,我們可以叫它UI語言,或是layout語言,因為它們總是以layout類型的資源文件作為主入口的。Android編程里嚴格地貫徹MVC的 設計思路,使我們得到了一個好處,就是我們的UI跟要實現(xiàn)的邏輯沒有任何必然聯(lián)系,我們可先去調整好UI顯示,而UI顯示后臺的實現(xiàn)邏輯,則可以在后續(xù)的 步驟里完成。
改寫處理邏輯的Java代碼。也就是我們MVC里的Controller與Model部分,這些部 分的內容,如果與UI沒有直接交互,則我們可以放心大膽的改寫,存在交互的部分,比如處理按鈕的點擊,取回輸入框里的文字等。作為一個定位于拓展能力要求 最高的智能手機操作系統(tǒng),android肯定不會只實現(xiàn)畫畫界面而已,會有強大的可開發(fā)能力,在android系統(tǒng)里,我們可以開發(fā)企業(yè)級應用,大型游 戲,以及完整的Office應用。
無論是通過tools/android工具生成的Android源代碼目錄,還是通過Eclipse來生成的Android源代碼工程,都需要進一 步去自定義這個步驟來完成一個Android應用程序。當然,還有一種特殊的情況就是,這個源代碼工程并非直接是一個Android應用程序,只是 Unit Test工程或是庫文件工作,并不直接使用.apk文件,這里則可能后續(xù)的編程工作會變得不同。我們這里是分析Android應用程序,于是后面分別來看 應用程序編程里的這三部分的工作如何進行。
2.3 AndroidManifest.xml
先來看看AndroidManifest.xml文件,一般出于方便,這一文件有可能也被稱為manifest文件。像我們前面的例子里的創(chuàng)建的Android工程,得到的AndroidManifest.xml文件就很簡單:
package="org.lianlab.hello"
android:versionCode="1"
android:versionName="1.0">
android:label="@string/app_name">
作為xml文件,所有的<>都會是成對的,比如我們看到的,這被 稱為標簽(Tag)。標簽可以包含子標簽,從而可以形成樹型的結點關系。如果沒有子標簽,則我們也可以使用來進行標識,比如我們上面看 到的。
,是主標簽,每個文件只會有一個,這是定義該應用程序屬性的主入口,包含應用程序的一切信息,比如我們的例子里定義了xml的命名空間,這個應用程序的包名,以及版本信息。
,則是用于定義應用程序屬性的標簽,理論上可以有多個,但多個不具有意義,一般我們一個應用程序只會有一個,在這個標簽里我們可以定義圖標,應用程序顯示出來的名字等。在這一標簽里定義的屬性一般也只是輔助性的。
,這是用來定義界面交互的信息。我們在稍后一點的內容介紹Android編程細節(jié)時會描述到這些信息,這一標簽里的屬性定義會決定應用程序可顯示效果。比如在啟動界面里的顯示出來的名字,使用什么樣的圖標等。
,這一標簽則用來控制應用程序的能力的,比如該圖形界面可以完成什么樣的功能。我們這里的處理比較簡單,我們只是能夠讓這個應用程序的HelloWorld可以被支持點擊到執(zhí)行。
從這個最簡單的AndroidManifest.xml文件里,我們可以看到Android執(zhí)行的另一個特點,就是可配置性強。它跟別的編程模型很不一樣的地方是,它沒有編程式規(guī)定的main()函數(shù)或是方法,而應用程序的表現(xiàn)出來的形態(tài),完全取決于字段是如何定義它的。
2.4 圖形界面(res/layout/main.xml)
我們可以再來看android的UI構成。UI也是基于XML的,是通過一種layout的資源引入到系統(tǒng)里的。在我們前面看到的最簡單的例子里,我們會得到的圖形界面是res/layout/main.xml,在這一文件里,我們會看到打開顯示,并在顯示區(qū)域里打印出Hello World, Helloworld。
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="@dimen/padding_medium"
android:text="@string/hello_world"
tools:context=".HelloWorld" />
在這個圖形界面的示例里,我們可以看到,這樣的圖形編程方式,比如傳統(tǒng)的方式里要學習大量的API要方便得多。
>,這一標簽會決定應用程序如何在界面里擺放相應的控件
,則是用于顯示字符串的圖形控件
使用這種XML構成的UI界面,是MVC設計的附屬產(chǎn)品,但更大的好處是,有了標準化的XML結構,就可以創(chuàng)建可以用來畫界面的IDE工具。一流的系統(tǒng)提供工具,讓設計師來設計界面、工程師來邏輯,這樣生產(chǎn)出來的軟件產(chǎn)品顯示效果與用戶體驗會更佳,比如iOS;二流的系統(tǒng),界面與邏輯都由工程師來完成,在這種系統(tǒng)上開發(fā)出來的軟件,不光界面不好看,用戶體驗也會不好。我們比如在Eclipse里的工程里查看,我們會發(fā)現(xiàn),我們打開res/layout/main.xml,會自動彈出來下面的窗口,讓我們有機會使圖形工具來操作界面。

在上面IDE工具里,左邊是控件列表,中間是進行繪制的工作區(qū),右邊會是控件一些微調窗口。一般我們可以從左邊控制列表里選擇合適的控件,拖到中間的工作區(qū)來組織界面,原則上的順序是layout à 復合控件 à 簡單控件。中間區(qū)域可以上面還有選擇項用于控制顯示屬性,在工作區(qū)域里我們可以進一步對界面進行微調,也可以選擇控件點擊左鍵,于是會出來上下文菜單來操作控件的屬性。到于右邊的操作界面,上部分則是整個界面構成的樹形結構,而下部分則是當我們選擇了某個界面元素時,會顯示上下文的屬性。最后,我們還可以在底部的Graphic Layout與main.xml進行圖形操作界面與源代碼編輯兩種操作方式的切換。
有了這種工具,就有可能實現(xiàn)設計師與工程師合作來構建出美觀與交互性更好的Android應用程序。但可惜的是,Android的這套UI設計工具,太過于編程化,而且由于版本變動頻繁的原因,非常復雜化,一般設計師可能也不太容易學好。更重要的一點,Android存在碎片化,屏幕尺寸與顯示精度差異性非常大,使實現(xiàn)像素級精度的界面有技術上的困難。也這是Android上應用程序不如iOS上漂亮的原因之一。但這種設計至少也增強了界面上的可設計性,使Android應用程序在觀感上也有不俗表現(xiàn)。
我們可以再回過頭來看看應用程序工程里的res目錄,res目錄里包含了Android應用程序里的可使用的資源,而資源文件本身是可以索引的,比如layout會引用drawable與values里的資源。對于我們例子里使用的,我們可以使用資源來進行引用,,然后在res/values/strings.xml里加入hello_string的定義。
.Hello world!
從通過這種方式,我們可以看另外一些特點,就是Android應用程序在多界面、多環(huán)境下的自適應性。對于上面的字符串修改的例子,我們如果像下面的示例環(huán)境那樣定義了res/layout-zh/strings.xml,并提供hello_string的定義:
歡迎使用!
最后,得到的應用程序,在英文環(huán)境里會顯示‘Hello world!’,而如果系統(tǒng)當前的語言環(huán)境是中文的話,就會顯示成‘歡迎使用!’。這種自適應方式,則是不需要我們進行編程的,系統(tǒng)會自動完成對這些顯示屬性的適配。
當然,這時可能會有人提疑問,如果這時是韓文或是日文環(huán)境會出現(xiàn)什么情況呢?在Android里,如果不能完成相應的適配,就會使用默認值,比如即使是我們創(chuàng)建了res/values-zh/strings.xml資源,在資源沒有定義我們需要使用的字符串,這時會使用英文顯示。不管如何,Android提供自適應顯示效果,但也保證總是不是會出錯。
這些也體現(xiàn)出,一旦Android應用程序寫出來,如果對多語言環(huán)境不滿意,這時,我們完全可以把.apk按zip格式解開,然后加入新的資源文件定義,再把文件重新打包,也就達到了我們可能會需要的漢化的效果。

在res目錄里,我們看到,對于同一種類型的資源,比如drawable、values,都可以在后面加一個后綴,像mdpi,hdpi, ldpi是用于適配分辨率的,zh是用來適配語言環(huán)境的,large則是用來適配屏幕大小的。對于這種顯示上的自適應需求,我們可以直接在Eclipse里通過創(chuàng)建Android XML文件里得到相應的提示,也可以參考http://developer.android.com/training/basics/supporting-devices/查看具體的使用方式。
于是,透過資源文件,我們進一步驗證了我們對于Android MVC的猜想,在Android應用程序設計里,也跟iOS類似,可以實現(xiàn)界面與邏輯完全分離。而另一點,就是Android應用程序天然具備屏幕自適應的能力,這一方面帶來的影響是Android應用程序天生具備很強的適應性,另一方面的影響是Android里實現(xiàn)像素精度顯示的應用程序是比較困難的,維護的代價很高。
我們可以再通過應用程序的代碼部分來看看應用程序是如何將顯示與邏輯進行綁定的。
2.5 Java編程(src/org/lianlab/hello/HelloWorld.java)
在Android編程里,實現(xiàn)應用程序的執(zhí)行邏輯,幾乎就是純粹的Java編程。但在編程上,由于Android的特殊性,這種Java編程也還是被定制過的
我們看到我們例子里的源代碼,如果寫過Java代碼,看到這樣的源代碼存放方式,就可以了解到Android為什么被稱為Java操作系統(tǒng)的原因了,像這種方式,就是標準的Java編程了。事實上,在Android的代碼被轉義成Dalvik代碼之前,Android編程都可被看成標準的Java編程。我們來看這個HelloWorld.java的源代碼。
package org.lianlab.hello;
import android.os.Bundle;
import android.app.Activity;
public class HelloWorld extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
代碼結構很簡單,我們所謂的HelloWorld,就是繼承了Activity的基類,然后再覆蓋了Acitivity基于的onCreate()方法。
Activity類,是Android系統(tǒng)設計思路里的很重要的一部分,所有與界面交互相關的操作類都是Activity,是MVC框架里的Controller部分。那Model部分由誰來提供呢?這是由Android系統(tǒng)層,也就是Framework來提供的功能。當界面失去焦點時,當界面完全變得不可見時,這些都屬于Framework層才會知道的狀態(tài),F(xiàn)ramework會記錄下這些狀態(tài)變更的信息,然后再回調到Activity類提供的相應狀態(tài)的回調方法。關于Activity我們后面來詳細說明,而見到Activity類的最簡單構成,我們大體上就可以形成Android世界里的完整MVC框架構成完整印象了。
我們繼承了Activity類之后,就會覆蓋其onCreate()回調方法。這里我們使用了”@Override”標識,這是一種Java語言里的Annotation(代碼注釋)技術,相當于C語言里的pragma,用于告訴編譯器一些相應參數(shù)。我們的Override則告訴javac編譯器,下面的方法在構建對象時會覆蓋掉父類方法,從而提高構建效率。Activity類里提供的onXXX()系列的都可以使用這種方法進行覆蓋,從而來實現(xiàn)自定義的方法,切入到Android應用程序不同狀態(tài)下的自定義實現(xiàn)。
我們覆蓋掉的onCreate()方法,使用了一個參數(shù),savedInstanceState,這個參數(shù)的類型是Bundle。Bundle是構建在Android的Binder IPC之上的一種特殊數(shù)據(jù)結構,用于實現(xiàn)普通Java代碼里的Serialization/Deserializaiton功能,序列化與反序列化功能。在Java代碼里,我們如果需要保存一些應用程序的上下文,如果是字符串或是數(shù)據(jù)值等原始類型,則可以直接寫到文件里,下次執(zhí)行時再把它讀出來就可以了。但假設我們需要保存的是一個對象,比如是界面的某個狀態(tài)點,像下面的這樣的數(shù)據(jù)結構:
class ViewState {
public int focusViewID;
public Long layoutParams ;
public String textEdited;
…
}
這時,我們就無法存取這樣的結構了,因為這樣的對象只是內存里的一些標識,存進時是一個進程上下文環(huán)境,取回來時會是另一種,就會出錯。為了實現(xiàn)這樣的功能,就需要序列化與反序列化,我們讀寫時都不再是以對象為單位,而是以類似于如下結構的一種字典類型的結構,最后進行操作的是一個個的鍵值對, ViewState[‘focusViewID’]的值會是valueOfViewID,一個整形值。
‘ViewState’ {
‘focusViewID’: valueOfViewID,
‘LayoutParams’:valueOfLayoutParams,
‘textEdited’:? ‘User input’,
}
我們按這種類似的格式寫到文件里,當再讀取出來時,我們就可以新建一個ViewState對象,再使用這些保存過的值對這一對象進行初始化。這樣就可以實現(xiàn)對象的保存與恢復,這是我們onCreate()方法里使用Bundle做序列化操作的主要目的,我們的Activity會有不同生存周期,當我們有可能需要在進程退出后再次恢復現(xiàn)象時,我們就會在退出前將上下文環(huán)境保存到一個onSavedInstance的Bundle對象里,而在onCreate()將顯示的上下文恢復成退出時的狀態(tài)。
而另一個必須要使用Bundle的理由是,我們的Activity與實現(xiàn)Activity管理的Framework功能部件ActivityManager,是構建在不同進程空間上的,Activity將運行在自己獨立的進程空間里,而Framework則是運行在另一個系統(tǒng)級進程SystemServer之上。我們的Bundle是一種進行過序列化操作的對象,于是相應的操作是系統(tǒng)進程會觸發(fā)Activity的進行onCreate()回調操作,而同時會轉回一個上下文環(huán)境的Bundle,可將Activity恢復到系統(tǒng)指定的某種圖形界面狀態(tài)。Bundle也可能為空,比如Activity是第一個被啟動的情況下,這個空的onSavedInstance則會被忽略掉。
我們進入到onCreate()方法之后,第一行便是
super.onCreate(savedInstanceState);
從字面上看,這種方式相當于我們繼承了父類方法,然后又回調到父類的onCreate()來進行處理。這種方式貌似很怪,但這是設計模式(Design Pattern)里鼎鼎大名的一種,叫IoC ( Inversion of Control)。通過這樣的設計模式,我們可以同時提供可維護性與可調試性,我們可以在通過覆蓋的方法提供功能更豐富的子類,實際上每次調用子類的onCreate()方法,都將調用到各個Activity拓展類的onCreate()方法。而這個方法一旦進入,又會回調到父類的onCreate()方法,在父類的onCreate()方法里,我們可以提供更多針對諸多子類的通用功能(比如啟動時顯示的上下文狀態(tài)的恢復,關閉時一些清理性工作),以及在這里面插入調試代碼。
然后,我們可以加載顯示部分的代碼的UI,
setContentView(R.layout.main);
這一行,就會使我們想要顯示的圖形界面被輸出到屏幕上。我們可以隨意地修改我們的main.xml文件,從而使setContentView()之后顯示出來的內容隨之發(fā)生變化。當然,作為XML的UI,最終是在內存里構成的樹形結構,我們也可以在調用setContentView()之前通過編程來修改這個樹形結構,于是也可以改變顯示效果。
到目前為止,我們也只是實現(xiàn)了將內容顯示到屏幕上,而沒有實現(xiàn)交互的功能。如果要實現(xiàn)交互的功能,我們也只需要很簡單的代碼就可以做到,我們可以將HelloWorld.java改成如下的內容,從而使用我們的”Hello world”字符串可以響應點擊事件:
package org.lianlab.hello;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
public class HelloWorld extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((TextView)findViewById(R.id.textView1)).setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
我們使用Activity類的findViewById()方法,則可以找到任何被R.java所索引起來的資源定義。我們在這里使用了R.id.textView1作為參數(shù),是因為我們在main.xml就是這么定義TextView標簽的:android:id="@+id/textView1"。
而我們找到字段之后,會調用TextView對象的setOnClickListener()方法,給TextView注冊一個onClickListener對象。這樣的對象,是我們在Android世界里遇到的第二次設計模式的使用(事實上Android的實現(xiàn)幾乎使用到所有的Java世界里的通用設計模式),Listener本身也會被作為Observer設計模式的一種別稱,主要是用于實現(xiàn)被動調用邏輯,比如事件回饋。
Observer(Listener)設計模式的思路,跟我們數(shù)據(jù)庫里使用到的Trigger功能類似,我們可對需要跟蹤的數(shù)據(jù)操作設置一個Trigger,當這類數(shù)據(jù)操作進行時,就會觸發(fā)數(shù)據(jù)庫自動地執(zhí)行某些操作代碼。而Observer(Listener)模式也是類似的,監(jiān)聽端通過注冊Observer來處理事件的回調,而真正的事件觸發(fā)者則是Observer,它的工作就是循環(huán)監(jiān)聽事件,然后再調用相應監(jiān)聽端的回調。
這樣的設計,跟效率沒有必然聯(lián)系,太可以更大程度地降低設計上的復雜度,同時提高設計的靈活性。一般Observer作為接口類,被監(jiān)聽則會定位成具體的Subject,真正的事件處理,則是通過實現(xiàn)某個Observer接口來實現(xiàn)的。對于固定的事件,Subject對象與Observer接口是無須變動的,而Observer的具體實現(xiàn)則可以很靈活地被改變與擴充。如下圖所示:

如果我們對于監(jiān)聽事件部分的處理,也希望能加入這樣的靈活性,于是我們可以繼續(xù)抽象,將Subject泛化成一個Observable接口,然后可以再提供不同的Observable接口的實現(xiàn)來設計相應的事件觸發(fā)端。

針對于我們的Android里的OnClickListener對象,則是什么情況呢?其實不光是OnClickListener,在Android里所有的事件回調,都有類似于Observer的設計技巧,這樣的回調有OnLongClickListener,OnTouchListener,OnKeyListener,OnContextMenuListener,以及OnSetOnFocusChangeListener等。但Android在使用設計模式時很簡潔,并不過大地提供靈活性,這樣可以保證性能,也可以減小出錯的概率(基本上所有的設計復雜到難以理解的系統(tǒng),可維護性遠比簡單易懂但設計粗糙的系統(tǒng)更差,因為大部分情況下人的智商也是有限的資源)。于是,從OnClickLister的角度,我們可以得到下圖所示的對象結構。

Click事件的觸發(fā)源是Touch事件,而當前View的Touch事件在屬于點擊事件的情況下,會生成一個performClick的Runnable對象(可交由Thread對象來運行其run()回調方法)。在這個Runnable對象的run()方法里會調用注冊過的OnClickListener對象的OnClick()方法,也就是圖示中的mOnClickListener::onClick()。當這個對象被post()操作發(fā)送到主線程時(作為Message發(fā)送給UI線程的Hander進行處理),我們覆蓋過的OnClick()回調方法就由主線程執(zhí)行到了。
我們注冊的Click處理,只有簡單的一行,finish(),也就是通過點擊事件,我們會將當前的Activity關閉掉。如果我們覺得這樣不過癮,我們也可通過這次點擊觸發(fā)另一個界面的執(zhí)行,比如直接搜索這個字符串。這樣的改動代碼量很小,首先,我們需要在HelloWorld.java的頭部引入所需要的Java包,
import android.app.SearchManager;
import android.content.Intent;
然后可以將我們的OnClick()方法改寫成啟動一個搜索的網(wǎng)頁界面,查找這個字符串,而當前界面的Activity則退出。這時,我們新的OnClick()方法則會變成這個樣子:
public void onClick(View v) {
Intent query = new Intent(Intent.ACTION_WEB_SEARCH);
query.putExtra(SearchManager.QUERY,
((TextView)v).getText());
startActivity(query);
finish();
}
但是可能還是無法解決我們對于Android應用程序與Java環(huán)境的區(qū)別的疑問:
Android有所謂的MVC,將代碼與顯示處理分享,但這并非是標準Java虛擬機環(huán)境做不到。一些J2EE的軟件框架也有類似的特征
AndroidManifest.xml與On*系列回調,這樣的機制在JAVA ME也有,JAVA ME也是使用類似的機制來運行的,難道Android是JAVA ME的加強版?
至于Listener模式的使用,眾所周知,Java是幾乎所有高級設計模式的實驗田,早就在使用Listener這樣模式在處理輸入處理。唯一不同的是ClickListener,難道Android也像是可愛的觸摸版Ubuntu手機一樣,只在是桌面Java界面的基礎加入了觸摸支持?
Activity從目前的使用上看,不就是窗口(Window)嗎?Android開發(fā)者本就有喜歡取些古怪名字的嗜好,是不是他們只是標新立異地取了個Activity的名字?
對于類似這樣的疑問,則是從代碼層面看不清楚了,我們得回歸到Android的設計思想這一層面來分析,Android應用程序的執(zhí)行環(huán)境是如何與眾不同的。
不過,我們可以從最后的那行Activity調用另一個Activity的例子里看出一些端倪,在這次調用里,我們并沒有顯式地創(chuàng)建新的Activity,如果從代碼直接去猜含義的話,我們只是發(fā)出了個執(zhí)行某種操作的請求,而這個請求并沒有指定有誰來完成。這就是Android編程思想的基礎,一種全開放的“無界化”編程模型。