日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Android應(yīng)用程序開發(fā)以及背后的設(shè)計思想深度剖析(1)

 orion360doc 2013-07-10

Android應(yīng)用程序開發(fā)以及背后的設(shè)計思想深度剖析(1)

分類: Android系統(tǒng)架構(gòu) 5361人閱讀 評論(6) 收藏 舉報

目錄(?)[+]

本文內(nèi)容,主題是透過應(yīng)用程序來分析Android系統(tǒng)的設(shè)計原理與構(gòu)架。我們先會簡單介紹一下Android里的應(yīng)用程序編程,然后以這些應(yīng)用程序在運行環(huán)境上的需求來分析出,為什么我們的Android系統(tǒng)需要今天這樣的設(shè)計方案,這樣的設(shè)計會有怎樣的意義, Android究竟是基于怎樣的考慮才變成今天的這個樣子,所以本文更多的分析Android應(yīng)用程序設(shè)計背后的思想,品味良好架構(gòu)設(shè)計的魅力。分五次連載完成,第一部分是最簡單的部分,解析Android應(yīng)用程序的開發(fā)流程。

特別聲明:本系列文章LiAnLab.org著作權(quán)所有,轉(zhuǎn)載請注明出處。作者系LiAnLab.org資深A(yù)ndroid技術(shù)顧問吳赫老師。本系列文章交流與討論:@宋寶華Barry

1.  Android應(yīng)用程序

在目前Android大紅大紫的情況下,很多人對編寫Android應(yīng)用程序已經(jīng)有了足夠深入的了解。即便是沒有充分的認(rèn)識,在現(xiàn)在Android手機(jī)已經(jīng)相當(dāng)普及的情況下,大家至少也會知道Android的應(yīng)用程序會是一個以.apk為后綴名的文件(在Windows系統(tǒng)里,還會是一個帶可愛機(jī)器人圖標(biāo)的文件)。那這個apk包又有什么樣的含義呢?

如果您正在使用Linux操作系統(tǒng),可以使用命令file命令來查看這一文件的類型。比如我們下載了一個Sample.apk的文件,則使用下面的命令:

  1. $file Sample.apk  
  2. Sample.apk: Zip archive data, at least v1.0 to extract  

對,沒有看錯,只一個簡單的zip文件。要是做過Java開發(fā)的人,可以對這種格式很親切,因為傳說中的.jar、.war格式,都是Zip壓縮格式的文件。我們可繼續(xù)使用unzip命令將這一文件解壓(或是任何的解壓工具,zip是人類歷史是最會古老最為普及的壓縮格式之一,幾乎所有壓縮工具都支持)。通過解壓,我們就得到了下面的文件內(nèi)容:

  1. AndroidManifest.xml,  
  2. classes.dex,  
  3. resources.arsc,  
  4. META-INF,  
  5. res,  

到這里,我們就可以看到一個Android應(yīng)用程序結(jié)構(gòu)其實是異常簡單的。這五部分內(nèi)容(其中META-INF和res是目錄,其他是文件)除了META-INF是這一.apk文件的校驗信息,resources.arsc是資源的索引文件,其他三部分則構(gòu)成了Android應(yīng)用程序的全部。

T     AndroidManifest.xml,這是每個Android應(yīng)用程序包的配置文件,這里會保存應(yīng)用程序名字、作者、所實現(xiàn)的功能、以及一些權(quán)限驗證信息。但很可惜,在編譯完成的.apk文件里,這些文件都被編譯成了二進(jìn)制版本,我們暫時沒有辦法看到內(nèi)容,后面我們可以再看看具體的內(nèi)容。

T     classes.dex,這則是Android應(yīng)用程序?qū)崿F(xiàn)的邏輯部分,也就是通過Java編程寫出來而被編譯過的代碼。這種特殊的格式,是Android里特定可執(zhí)行格式,是可由Dalvik虛擬機(jī)所執(zhí)行的代碼,這部分內(nèi)容我們也會在后續(xù)的介紹Dalvik虛擬機(jī)的章節(jié)里介紹。

T     res,這一目錄里則保存了Android所有圖形界面設(shè)計相關(guān)的內(nèi)容,比如界面應(yīng)該長成什么樣子、支持哪些語言顯示等等。

從一個android應(yīng)用程序的包文件內(nèi)容,我們可以看到android應(yīng)用程序的特點,這也是Android編程上的一些特征:

1      簡單:最終生成的結(jié)果是如些簡單的三種組成,則他們的編程上也不會有太大的困難性。這并不是說Android系統(tǒng)里無法實現(xiàn)很復(fù)雜的應(yīng)用程序,事實上Android系統(tǒng)擁有世界上僅次于iOS的應(yīng)用程序生態(tài)環(huán)境,也擁有復(fù)雜的辦公軟件、大型3D游戲。而只是說,如果要實現(xiàn)和構(gòu)成同樣的邏輯,它必然會擁有其他格式混雜的系統(tǒng)更簡化的編程模式。

2      Java操作系統(tǒng):既然我們編譯得到的結(jié)果,classes.dex文件,是用于Java虛擬機(jī)(雖然是Dalvik虛擬機(jī),但實際上這一虛擬機(jī)只是一種特定的Java解析器和虛擬機(jī)執(zhí)行環(huán)境 )解析執(zhí)行的,于是我們也可以猜想到,我們的Android系統(tǒng),必然是一個Java操作系統(tǒng)。我們在后面會解釋,如果把Android系統(tǒng)直接看成Linux內(nèi)核和Java語言組合到一起的操作系統(tǒng)很不準(zhǔn)確,但事實上Android,也還是Java操作系統(tǒng),Java是唯一的系統(tǒng)入口。

使用MVC設(shè)計模式:所謂的MVC,就是Model,View,Controller的首字母組合起來的一種設(shè)計模式,主要思想就是把顯示與邏輯實現(xiàn)分離。Model用于保存上下文狀態(tài)、View用于顯示、而Controller則是用于處理用戶交互。三者之間有著如下圖所示的交互模型,交互只到Controller,而顯示更新只通過View進(jìn)行,這兩者再與Model交換界面狀態(tài)信息:


在現(xiàn)代的圖形交互相關(guān)的設(shè)計里,MVC幾乎是在圖形交互處理上的不二選擇,這樣系統(tǒng)設(shè)計包括一些J2EE的應(yīng)用服務(wù)器框架,最受歡迎的Firefox瀏覽器,iOS,MacOSX等等。這些使用MVC模式的最顯著特點就是顯示與邏輯分離,在Android應(yīng)用程序里我們看到了用于邏輯實現(xiàn)的classes.dex,也看到用于顯示的res,于是我們也可以猜想到在UI上便肯定會使用MVC設(shè)計模式。

            當(dāng)然,所謂的Android應(yīng)用程序編程,不會只有這些內(nèi)容。到目前為止,我們也只是分析.apk文件,于是我們可以回過頭來看看Android應(yīng)用被編譯出來的過程。

2.  Android編程

從編程角度來說,Android應(yīng)用程序編程幾乎只與Java相關(guān),而Java平臺本身是出了名跨平臺利器,理論上來說,所有Java環(huán)境里使用的編程工具、IDE工具,皆可用于Android的編程。Android SDK環(huán)境里提供的編程工具,是基于標(biāo)準(zhǔ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代碼之間進(jìn)行切換??偠灾瑤缀跛械腏ava環(huán)境的編程工具都可以用于Android編程。

對于這些工具呢,熟悉工具的使用是件好事,所謂“磨刀不誤砍柴工”,為將來提升效率,這是件好事。但是要磨刀過多,柴沒砍著,轉(zhuǎn)型成“磨刀工”了。如果過多地在這些編程工具上糾結(jié)嘗試,反而忽視了所編代碼的本身,這倒會舍本逐末。

我們既然是研究Android編程,這時僅說明兩種Android官方提供的編程方法:使用Android SDK工具包編程,或是使用Eclipse + ADT插件編程。

2.1 使用Android SDK工具包

在Android開發(fā)過程中,如果Eclipse環(huán)境不可得的情況下,可以直接使用SDK來創(chuàng)建應(yīng)用程序工程。首先需要安裝某一個版本的Android SDK開發(fā)包,這個工具包可以到http://developer./sdk/index.html這個網(wǎng)址去下載,根據(jù)開發(fā)所用的主機(jī)是Windows、Linux還是MacOS X(MacOS僅支持Intel芯片,不支持之前的PowerPC芯片),下載對應(yīng)的.zip文件,比如android-sdk_r19-linux.zip。下載完成后,解壓到一個固定的目錄,我們這里假定是通過環(huán)境變量$ANDROID_SDK_PATH指定的目錄。

下載的SDK包,默認(rèn)是沒有Android開發(fā)環(huán)境支持的,需要通過tools目錄里的一個android工具來下載相應(yīng)的SDK版本以用于開發(fā)。我們通過運行$ANDROID_SDK_PATH/tools/android會得到如下的界面:


在上面的安裝界面里選擇不同的開發(fā)工具包,其中Tools里包含一些開發(fā)用的工具,如我們的SDK包,實際上也會在這一界面里進(jìn)行更新。而對于不同的Android版本,1.5到4.1,我們必須選擇下載某個SDK版本來進(jìn)行開發(fā)。而下載完之后的版本信息,我們既可以在這一圖形界面里看到,也可以通過命令行來查看。

  1. $ANDROID_SDK_PATH/tools/android list targets  
  2. id: 1 or "android-16"  
  3.      Name: Android 4.1  
  4.      Type: Platform  
  5.      API level: 16  
  6.      Revision: 1  
  7.      Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in  
  8.      ABIs : armeabi-v7a  
  9. ----------  
  10. id: 2 or "Google Inc.:Google APIs:16"  
  11.      Name: Google APIs  
  12.      Type: Add-On  
  13.      Vendor: Google Inc.  
  14.      Revision: 1  
  15.      Description: Android + Google APIs  
  16.      Based on Android 4.1 (API level 16)  
  17.      Libraries:  
  18.       * com.google.android.media.effects (effects.jar)  
  19.           Collection of video effects  
  20.       * com.android.future.usb.accessory (usb.jar)  
  21.           API for USB Accessories  
  22.       * com.google.android.maps (maps.jar)  
  23.           API for Google Maps  
  24.      Skins: WVGA854, WQVGA400, WSVGA, WXGA800-7in, WXGA720, HVGA, WQVGA432, WVGA800 (default), QVGA, WXGA800  
  25.      ABIs : armeabi-v7a  

通過android list targets列出來的信息,可以用于后續(xù)的開發(fā)之用,比如對于不同的target,最后得到了id:1、id:2這樣的信息,則可以被用于應(yīng)用程序工程的創(chuàng)建。而細(xì)心一點的讀者會看到同一個4.1版本的SDK,實際可分為”android-16”和"Google Inc.:Google APIs:16",這樣的分界也還有有意義的,”android-16”用于“純”的android 4.1版的應(yīng)用程序開發(fā),而“Google Inc.:Google APIs:16”則加入了Google的開發(fā)包。

            配置好環(huán)境之后,如果我們需要創(chuàng)建Android應(yīng)用程序。tools/android這個工具,同時也具備可以創(chuàng)建Android應(yīng)用程序工程的能力。我們輸入:

  1. $ANDROID_SDK_PATH/tools/android create project -n Hello -t 1 -k org.lianlab.hello -a Helloworld -p hello  

這樣我們就在hello目錄里創(chuàng)建了一個Android的應(yīng)用程序,名字是Hello,使用API16(Android 4.1的API版本),包名是org.lianlab.hello,而默認(rèn)會被執(zhí)行到的Activity,會是叫Helloworld的Activity類。

掌握Android工具的一些使用方法也是有意義的,比如當(dāng)我們的Eclipse工程被破壞的情況下,我們依然可以手工修復(fù)這一Android應(yīng)用程序工程?;蚴切枰薷脑摴こ痰腁PI版本的話,可以使用下面的命令:

$ANDROID_SDK_PATH/tools/android updateproject -t 2 -p .

            在這個工程里,如果我們不加任何修改,會生成一個應(yīng)用程序,這個應(yīng)用程序運行的效果是生成一個黑色的圖形界面,打印出一行"Hello World, Helloworld"。如果我們需要對這一工程進(jìn)行編譯等操作的話,剩下的事情就屬于標(biāo)準(zhǔn)的Java編譯了,標(biāo)準(zhǔn)的Java編譯,使用的是ant(ant.apache.org)編譯工具。我們先改變當(dāng)前目錄到hello,然后就可以通過” ant –projecthelp”來查看可以被執(zhí)行的Android編譯工程,

  1. $ ant -projecthelp  
  2. Buildfile: /Users/wuhe/android/workspace/NotePad/bin/tmp/hello/build.xml  
  3.   
  4. Main targets:  
  5.   
  6.  clean       Removes output files created by other targets.  
  7.  debug       Builds the application and signs it with a debug key.  
  8.  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.  
  9.  installd    Installs (only) the debug package.  
  10.  installi    Installs (only) the instrumented package.  
  11.  installr    Installs (only) the release package.  
  12.  installt    Installs (only) the test and tested packages.  
  13.  instrument  Builds an instrumented packaged.  
  14.  release     Builds the application in release mode.  
  15.  test        Runs tests from the package defined in test.package property  
  16.  uninstall   Uninstalls the application from a running emulator or device.  
  17. Default target: help  

但如果只是編譯,我們可以使用antdebug生成Debug的.apk文件,這時生成的文件,會被放到bin/Hello-debug.apk。此時生成的Hello-debug.apk,已經(jīng)直接可以安裝到Android設(shè)備上進(jìn)行測試運行。我們也可以使用ant release來生成一個bin/Hello-release-unsigned.apk,而這時的.apk文件,則需要通過jarsigner對文件進(jìn)行驗證才能進(jìn)行安裝。

通過antdebug這一編譯腳本,我們可以看到詳細(xì)的編譯過程。我們可以看到,一個Android的工程,最后會是通過如圖所示的方式生成最后的.apk文件。


把一個Android的源代碼工程編譯成.apk的Android應(yīng)用程序,其過程如下:

1)      所有的資源文件,都會被aapt進(jìn)行處理。所有的XML文件,都會被aapt解析成二進(jìn)制格式,準(zhǔn)確地說,這樣的二進(jìn)制格式,是可以被直接映射到內(nèi)存里的二進(jìn)制樹。做過XML相關(guān)開發(fā)的工程師,都會知道,XML的驗證與解析是非常消耗時間與內(nèi)存的,而通過編譯時進(jìn)行XML解析,則節(jié)省了運行時的開銷。當(dāng)然解析的結(jié)果最后會被aapt通過一個R.java保存一個二進(jìn)制樹的索引,編程時可通過這個R.java文件進(jìn)行XML的訪問。aapt會處理所有的資源文件,也就是Java代碼之外的任何靜態(tài)性文件,這樣處理既保證了資源文件間的互相索引得到了驗證,也確保了R.java可以索引到這個應(yīng)用程序里所有的資源。

2)      所有的Java文件,都會被JDK里的javac工具編譯成bin目錄下按源代碼包結(jié)構(gòu)組織的.class文件(.class是標(biāo)準(zhǔn)的Java可解析執(zhí)行的格式),比如我們這個例子里生成的bin/classes/org/lianlab/hello/*.class文件。然后這些文件,會通過SDK里提供的一個dx工具轉(zhuǎn)換成classes.dex文件。這一文件,就是會被Dalvik虛擬機(jī)所解析執(zhí)行的

3)      最后我們得到的編譯過的二進(jìn)制資源文件和classes.dex可執(zhí)行文件,會通過一個apkbuilder工具,通過zip壓縮算法打包到一個文件里,生成了我們所常見的.apk文件。

4)      最后,.apk文件,會通過jarsigner工具進(jìn)行校驗,這一校驗值會需要一個數(shù)字簽名。如果我們申請了Android開發(fā)者賬號,這一數(shù)字簽名就是Android所分發(fā)的那個數(shù)字證書;如果沒有,我們則使用debug模式,使用本地生成的一個隨機(jī)的數(shù)字證書,這一文件位于~/.android/debug.keystore。

            雖然我們只是下載了SDK,通過一行腳本創(chuàng)建了Android應(yīng)用程序工程,通過另一行完成了編譯。但也許還是會被認(rèn)為過于麻煩,因為需要進(jìn)行字符界面的操作,而且這種開發(fā)方式也不是常用的方式,在Java環(huán)境下,我們有Eclipse可用。我們可以使用Eclipse的圖形化開發(fā)工具,配合ADT插件使用。

2.2 使用Eclipse+ADT插件

在Android環(huán)境里可以使用Java世界里幾乎一切的開發(fā)工具,比如NetBeans等,但Eclipse是Android官方標(biāo)準(zhǔn)的開發(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應(yīng)用程序開發(fā)的環(huán)境。


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


點擊OK之后,則可以進(jìn)行Android開發(fā)了。選擇”File” à “New”à “Project” à “Android”,在Eclipse 3.x版本里,會是“Android Project”,在Eclipse 4.x版本里,會是“Android Application Project”。如果我們需要創(chuàng)建跟前面字符界面下一模一樣的應(yīng)用程序工程,則在彈出的創(chuàng)建應(yīng)用程序?qū)υ捒蚶锾钊肴缦碌膬?nèi)容:


然后我們選擇Next,一直到彈出最后界面提示,讓我們選擇默認(rèn)Activity的名字,最后點擊”Finish”,我們就得到一個Android應(yīng)用程序工程,同時在Eclipse環(huán)境里,我們既可以通過圖形化界面編輯Java代碼,也可以通過圖形化的界面編輯工具來繪制圖形界面。

(注意: 如果Android工程本身比較龐大,則最好將Eclipse里的內(nèi)存相關(guān)的配置改大。在Windows和Linux里,是修改eclipse里的eclipse.ini文件,而在MacOS里,則是修改Eclipse.app/Contents/MacOS/eclipse.ini。一般會將如下的值加大成兩倍:

  1. --launcher.XXMaxPermSize  
  2. 512m  
  3. -vmargs  
  4. -Xms80m  
  5. -Xmx1024m  
  6. )  

我們得到工程目錄,在Eclipse環(huán)境里會是如下圖所示的組織方式。代碼雖然是使用一模一樣的編譯方式,唯一的改變是,我們不再需要使用腳本來完成編譯,我們可以直接使用Eclipse的”Project”à“Build project”來完成編譯過程。如果我們使用默認(rèn)設(shè)置,則代碼是使用自動編譯的,我們的每次修改都會觸發(fā)增量式的編譯。


我們從這些android編程的過程,看不出來android跟別的Java編程模式有什么區(qū)別,倒至少驗證了我們前面對android編程特點的猜想,就是很簡單。如果同樣我們使用Eclipse開發(fā)Java的圖形界面程序,需要大量地時間去熟悉API,而在Android這里學(xué)習(xí)的曲線被大大降低,如果我們只是要畫幾個界面,建立起簡單的交互,我們幾乎無須學(xué)習(xí)編程。

而從上面的步驟,我們大概也可以得到Android開發(fā)的另一個好處,就是極大的跨平臺性,它的開發(fā)流程里除了JDK沒有提及任何的第三方環(huán)境需求,于是這樣的開發(fā)環(huán)境,肯定可以在各種不同的平臺執(zhí)行。這也是Android上進(jìn)行開發(fā)的好處之一,跨平臺,支持Windows,Linux與MacOS三種。

我們再來看一個,我們剛才創(chuàng)建的這個工程里,我們怎么樣進(jìn)行下一步的開發(fā)。在Android開發(fā)里,決定我們應(yīng)用程序表現(xiàn)的,也就是我們從一個.apk文件里看到的,我們實際上只需要:

T     修改AndroidManifest.xml文件。AndroidManifest.xml是Android應(yīng)用程序的主控文件,類型于Windows里的注冊表,我們通過它來配置我們的應(yīng)用程序與系統(tǒng)相關(guān)的一些屬性。

T     修改UI顯示。在Android世界里,我們可以把UI編程與Java編程分開對待,處理UI控件的語言,我們可以叫它UI語言,或是layout語言,因為它們總是以layout類型的資源文件作為主入口的。Android編程里嚴(yán)格地貫徹MVC的設(shè)計思路,使我們得到了一個好處,就是我們的UI跟要實現(xiàn)的邏輯沒有任何必然聯(lián)系,我們可先去調(diào)整好UI顯示,而UI顯示后臺的實現(xiàn)邏輯,則可以在后續(xù)的步驟里完成。

T     改寫處理邏輯的Java代碼。也就是我們MVC里的Controller與Model部分,這些部分的內(nèi)容,如果與UI沒有直接交互,則我們可以放心大膽的改寫,存在交互的部分,比如處理按鈕的點擊,取回輸入框里的文字等。作為一個定位于拓展能力要求最高的智能手機(jī)操作系統(tǒng),android肯定不會只實現(xiàn)畫畫界面而已,會有強(qiáng)大的可開發(fā)能力,在android系統(tǒng)里,我們可以開發(fā)企業(yè)級應(yīng)用,大型游戲,以及完整的Office應(yīng)用。

無論是通過tools/android工具生成的Android源代碼目錄,還是通過Eclipse來生成的Android源代碼工程,都需要進(jìn)一步去自定義這個步驟來完成一個Android應(yīng)用程序。當(dāng)然,還有一種特殊的情況就是,這個源代碼工程并非直接是一個Android應(yīng)用程序,只是Unit Test工程或是庫文件工作,并不直接使用.apk文件,這里則可能后續(xù)的編程工作會變得不同。我們這里是分析Android應(yīng)用程序,于是后面分別來看應(yīng)用程序編程里的這三部分的工作如何進(jìn)行。

2.3 AndroidManifest.xml

先來看看AndroidManifest.xml文件,一般出于方便,這一文件有可能也被稱為manifest文件。像我們前面的例子里的創(chuàng)建的Android工程,得到的AndroidManifest.xml文件就很簡單:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas./apk/res/android"  
  3.       package="org.lianlab.hello"  
  4.       android:versionCode="1"  
  5.       android:versionName="1.0">  
  6.     <application android:label="@string/app_name">  
  7.         <activity android:name=".Helloworld"  
  8.                   android:label="@string/app_name">  
  9.             <intent-filter>  
  10.                 <action android:name="android.intent.action.MAIN" />  
  11.                 <category android:name="android.intent.category.LAUNCHER" />  
  12.             </intent-filter>  
  13.         </activity>  
  14.     </application>  
  15. </manifest>  

作為xml文件,所有的<>都會是成對的,比如我們看到的<manifest></manifest>,這被稱為標(biāo)簽(Tag)。標(biāo)簽可以包含子標(biāo)簽,從而可以形成樹型的結(jié)點關(guān)系。如果沒有子標(biāo)簽,則我們也可以使用</>來進(jìn)行標(biāo)識,比如我們上面看到的<actionandroid:name=”android.intent.action.MAIN” />。

<manifest>,是主標(biāo)簽,每個文件只會有一個,這是定義該應(yīng)用程序?qū)傩缘闹魅肟?,包含?yīng)用程序的一切信息,比如我們的例子里定義了xml的命名空間,這個應(yīng)用程序的包名,以及版本信息。

<application>,則是用于定義應(yīng)用程序?qū)傩缘臉?biāo)簽,理論上可以有多個,但多個不具有意義,一般我們一個應(yīng)用程序只會有一個,在這個標(biāo)簽里我們可以定義圖標(biāo),應(yīng)用程序顯示出來的名字等。在這一標(biāo)簽里定義的屬性一般也只是輔助性的。

<activity>,這是用來定義界面交互的信息。我們在稍后一點的內(nèi)容介紹Android編程細(xì)節(jié)時會描述到這些信息,這一標(biāo)簽里的屬性定義會決定應(yīng)用程序可顯示效果。比如在啟動界面里的顯示出來的名字,使用什么樣的圖標(biāo)等。

<intent-filter>,這一標(biāo)簽則用來控制應(yīng)用程序的能力的,比如該圖形界面可以完成什么樣的功能。我們這里的處理比較簡單,我們只是能夠讓這個應(yīng)用程序的HelloWorld可以被支持點擊到執(zhí)行。

從這個最簡單的AndroidManifest.xml文件里,我們可以看到Android執(zhí)行的另一個特點,就是可配置性強(qiáng)。它跟別的編程模型很不一樣的地方是,它沒有編程式規(guī)定的main()函數(shù)或是方法,而應(yīng)用程序的表現(xiàn)出來的形態(tài),完全取決于<activity>字段是如何定義它的。

2.4 圖形界面(res/layout/main.xml)

我們可以再來看android的UI構(gòu)成。UI也是基于XML的,是通過一種layout的資源引入到系統(tǒng)里的。在我們前面看到的最簡單的例子里,我們會得到的圖形界面是res/layout/main.xml,在這一文件里,我們會看到打開顯示,并在顯示區(qū)域里打印出Hello World, Helloworld。

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas./apk/res/android"  
  3.     xmlns:tools="http://schemas./tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" >  
  6.     <TextView  
  7.         android:id="@+id/textView1"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:layout_centerInParent="true"  
  11.         android:padding="@dimen/padding_medium"  
  12.         android:text="@string/hello_world"  
  13.         tools:context=".HelloWorld" />  
  14. </RelativeLayout>  

在這個圖形界面的示例里,我們可以看到,這樣的圖形編程方式,比如傳統(tǒng)的方式里要學(xué)習(xí)大量的API要方便得多。

<LinearLayout>,這一標(biāo)簽會決定應(yīng)用程序如何在界面里擺放相應(yīng)的控件

<TextView>,則是用于顯示字符串的圖形控件

使用這種XML構(gòu)成的UI界面,是MVC設(shè)計的附屬產(chǎn)品,但更大的好處是,有了標(biāo)準(zhǔn)化的XML結(jié)構(gòu),就可以創(chuàng)建可以用來畫界面的IDE工具。一流的系統(tǒng)提供工具,讓設(shè)計師來設(shè)計界面、工程師來邏輯,這樣生產(chǎn)出來的軟件產(chǎn)品顯示效果與用戶體驗會更佳,比如iOS;二流的系統(tǒng),界面與邏輯都由工程師來完成,在這種系統(tǒng)上開發(fā)出來的軟件,不光界面不好看,用戶體驗也會不好。我們比如在Eclipse里的工程里查看,我們會發(fā)現(xiàn),我們打開res/layout/main.xml,會自動彈出來下面的窗口,讓我們有機(jī)會使圖形工具來操作界面。


在上面IDE工具里,左邊是控件列表,中間是進(jìn)行繪制的工作區(qū),右邊會是控件一些微調(diào)窗口。一般我們可以從左邊控制列表里選擇合適的控件,拖到中間的工作區(qū)來組織界面,原則上的順序是layout à 復(fù)合控件 à 簡單控件。中間區(qū)域可以上面還有選擇項用于控制顯示屬性,在工作區(qū)域里我們可以進(jìn)一步對界面進(jìn)行微調(diào),也可以選擇控件點擊左鍵,于是會出來上下文菜單來操作控件的屬性。到于右邊的操作界面,上部分則是整個界面構(gòu)成的樹形結(jié)構(gòu),而下部分則是當(dāng)我們選擇了某個界面元素時,會顯示上下文的屬性。最后,我們還可以在底部的Graphic Layout與main.xml進(jìn)行圖形操作界面與源代碼編輯兩種操作方式的切換。

有了這種工具,就有可能實現(xiàn)設(shè)計師與工程師合作來構(gòu)建出美觀與交互性更好的Android應(yīng)用程序。但可惜的是,Android的這套UI設(shè)計工具,太過于編程化,而且由于版本變動頻繁的原因,非常復(fù)雜化,一般設(shè)計師可能也不太容易學(xué)好。更重要的一點,Android存在碎片化,屏幕尺寸與顯示精度差異性非常大,使實現(xiàn)像素級精度的界面有技術(shù)上的困難。也這是Android上應(yīng)用程序不如iOS上漂亮的原因之一。但這種設(shè)計至少也增強(qiáng)了界面上的可設(shè)計性,使Android應(yīng)用程序在觀感上也有不俗表現(xiàn)。

我們可以再回過頭來看看應(yīng)用程序工程里的res目錄,res目錄里包含了Android應(yīng)用程序里的可使用的資源,而資源文件本身是可以索引的,比如layout會引用drawable與values里的資源。對于我們例子里使用的<TextView  … android:text="Hello World,Helloworld" …>,我們可以使用資源來進(jìn)行引用,<TextView …android:text=” @string/hello_string” …>,然后在res/values/strings.xml里加入hello_string的定義。

  1. <string name="hello_world">Hello world!</string>  

從通過這種方式,我們可以看另外一些特點,就是Android應(yīng)用程序在多界面、多環(huán)境下的自適應(yīng)性。對于上面的字符串修改的例子,我們?nèi)绻裣旅娴氖纠h(huán)境那樣定義了res/layout-zh/strings.xml,并提供hello_string的定義:

  1. <string name="hello_world">歡迎使用!</string>  

最后,得到的應(yīng)用程序,在英文環(huán)境里會顯示‘Hello world!’,而如果系統(tǒng)當(dāng)前的語言環(huán)境是中文的話,就會顯示成‘歡迎使用!’。這種自適應(yīng)方式,則是不需要我們進(jìn)行編程的,系統(tǒng)會自動完成對這些顯示屬性的適配。

當(dāng)然,這時可能會有人提疑問,如果這時是韓文或是日文環(huán)境會出現(xiàn)什么情況呢?在Android里,如果不能完成相應(yīng)的適配,就會使用默認(rèn)值,比如即使是我們創(chuàng)建了res/values-zh/strings.xml資源,在資源沒有定義我們需要使用的字符串,這時會使用英文顯示。不管如何,Android提供自適應(yīng)顯示效果,但也保證總是不是會出錯。

這些也體現(xiàn)出,一旦Android應(yīng)用程序?qū)懗鰜?,如果對多語言環(huán)境不滿意,這時,我們完全可以把.apk按zip格式解開,然后加入新的資源文件定義,再把文件重新打包,也就達(dá)到了我們可能會需要的漢化的效果。


在res目錄里,我們看到,對于同一種類型的資源,比如drawable、values,都可以在后面加一個后綴,像mdpi,hdpi, ldpi是用于適配分辨率的,zh是用來適配語言環(huán)境的,large則是用來適配屏幕大小的。對于這種顯示上的自適應(yīng)需求,我們可以直接在Eclipse里通過創(chuàng)建Android XML文件里得到相應(yīng)的提示,也可以參考http://developer./training/basics/supporting-devices/查看具體的使用方式。

于是,透過資源文件,我們進(jìn)一步驗證了我們對于Android MVC的猜想,在Android應(yīng)用程序設(shè)計里,也跟iOS類似,可以實現(xiàn)界面與邏輯完全分離。而另一點,就是Android應(yīng)用程序天然具備屏幕自適應(yīng)的能力,這一方面帶來的影響是Android應(yīng)用程序天生具備很強(qiáng)的適應(yīng)性,另一方面的影響是Android里實現(xiàn)像素精度顯示的應(yīng)用程序是比較困難的,維護(hù)的代價很高。

我們可以再通過應(yīng)用程序的代碼部分來看看應(yīng)用程序是如何將顯示與邏輯進(jìn)行綁定的。

2.5  Java編程(src/org/lianlab/hello/HelloWorld.java)

在Android編程里,實現(xiàn)應(yīng)用程序的執(zhí)行邏輯,幾乎就是純粹的Java編程。但在編程上,由于Android的特殊性,這種Java編程也還是被定制過的。

            我們看到我們例子里的源代碼,如果寫過Java代碼,看到這樣的源代碼存放方式,就可以了解到Android為什么被稱為Java操作系統(tǒng)的原因了,像這種方式,就是標(biāo)準(zhǔn)的Java編程了。事實上,在Android的代碼被轉(zhuǎn)義成Dalvik代碼之前,Android編程都可被看成標(biāo)準(zhǔn)的Java編程。我們來看這個HelloWorld.java的源代碼。

  1. package org.lianlab.hello;  
  2. import android.os.Bundle;  
  3. import android.app.Activity;  
  4.   
  5. public class HelloWorld extends Activity {  
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.     }  
  11. }  

代碼結(jié)構(gòu)很簡單,我們所謂的HelloWorld,就是繼承了Activity的基類,然后再覆蓋了Acitivity基于的onCreate()方法。

            Activity類,是Android系統(tǒng)設(shè)計思路里的很重要的一部分,所有與界面交互相關(guān)的操作類都是Activity,是MVC框架里的Controller部分。那Model部分由誰來提供呢?這是由Android系統(tǒng)層,也就是Framework來提供的功能。當(dāng)界面失去焦點時,當(dāng)界面完全變得不可見時,這些都屬于Framework層才會知道的狀態(tài),F(xiàn)ramework會記錄下這些狀態(tài)變更的信息,然后再回調(diào)到Activity類提供的相應(yīng)狀態(tài)的回調(diào)方法。關(guān)于Activity我們后面來詳細(xì)說明,而見到Activity類的最簡單構(gòu)成,我們大體上就可以形成Android世界里的完整MVC框架構(gòu)成完整印象了。

            我們繼承了Activity類之后,就會覆蓋其onCreate()回調(diào)方法。這里我們使用了”@Override”標(biāo)識,這是一種Java語言里的Annotation(代碼注釋)技術(shù),相當(dāng)于C語言里的pragma,用于告訴編譯器一些相應(yīng)參數(shù)。我們的Override則告訴javac編譯器,下面的方法在構(gòu)建對象時會覆蓋掉父類方法,從而提高構(gòu)建效率。Activity類里提供的onXXX()系列的都可以使用這種方法進(jìn)行覆蓋,從而來實現(xiàn)自定義的方法,切入到Android應(yīng)用程序不同狀態(tài)下的自定義實現(xiàn)。

            我們覆蓋掉的onCreate()方法,使用了一個參數(shù),savedInstanceState,這個參數(shù)的類型是Bundle。Bundle是構(gòu)建在Android的Binder IPC之上的一種特殊數(shù)據(jù)結(jié)構(gòu),用于實現(xiàn)普通Java代碼里的Serialization/Deserializaiton功能,序列化與反序列化功能。在Java代碼里,我們?nèi)绻枰4嬉恍?yīng)用程序的上下文,如果是字符串或是數(shù)據(jù)值等原始類型,則可以直接寫到文件里,下次執(zhí)行時再把它讀出來就可以了。但假設(shè)我們需要保存的是一個對象,比如是界面的某個狀態(tài)點,像下面的這樣的數(shù)據(jù)結(jié)構(gòu):

  1. class ViewState {  
  2.     public int focusViewID;  
  3.     public Long layoutParams ;  
  4.     public String textEdited;  
  5. …  
  6. }  
這時,我們就無法存取這樣的結(jié)構(gòu)了,因為這樣的對象只是內(nèi)存里的一些標(biāo)識,存進(jìn)時是一個進(jìn)程上下文環(huán)境,取回來時會是另一種,就會出錯。為了實現(xiàn)這樣的功能,就需要序列化與反序列化,我們讀寫時都不再是以對象為單位,而是以類似于如下結(jié)構(gòu)的一種字典類型的結(jié)構(gòu),最后進(jìn)行操作的是一個個的鍵值對, ViewState[‘focusViewID’]的值會是valueOfViewID,一個整形值。

  1. ‘ViewState’ {  
  2.   
  3.             ‘focusViewID’: valueOfViewID,  
  4.   
  5.             ‘LayoutParams’:valueOfLayoutParams,  
  6.   
  7.             ‘textEdited’:  ‘User input’,  
  8.   
  9. }  

我們按這種類似的格式寫到文件里,當(dāng)再讀取出來時,我們就可以新建一個ViewState對象,再使用這些保存過的值對這一對象進(jìn)行初始化。這樣就可以實現(xiàn)對象的保存與恢復(fù),這是我們onCreate()方法里使用Bundle做序列化操作的主要目的,我們的Activity會有不同生存周期,當(dāng)我們有可能需要在進(jìn)程退出后再次恢復(fù)現(xiàn)象時,我們就會在退出前將上下文環(huán)境保存到一個onSavedInstance的Bundle對象里,而在onCreate()將顯示的上下文恢復(fù)成退出時的狀態(tài)。

            而另一個必須要使用Bundle的理由是,我們的Activity與實現(xiàn)Activity管理的Framework功能部件ActivityManager,是構(gòu)建在不同進(jìn)程空間上的,Activity將運行在自己獨立的進(jìn)程空間里,而Framework則是運行在另一個系統(tǒng)級進(jìn)程SystemServer之上。我們的Bundle是一種進(jìn)行過序列化操作的對象,于是相應(yīng)的操作是系統(tǒng)進(jìn)程會觸發(fā)Activity的進(jìn)行onCreate()回調(diào)操作,而同時會轉(zhuǎn)回一個上下文環(huán)境的Bundle,可將Activity恢復(fù)到系統(tǒng)指定的某種圖形界面狀態(tài)。Bundle也可能為空,比如Activity是第一個被啟動的情況下,這個空的onSavedInstance則會被忽略掉。

            我們進(jìn)入到onCreate()方法之后,第一行便是

  1. super.onCreate(savedInstanceState);  

從字面上看,這種方式相當(dāng)于我們繼承了父類方法,然后又回調(diào)到父類的onCreate()來進(jìn)行處理。這種方式貌似很怪,但這是設(shè)計模式(Design Pattern)里鼎鼎大名的一種,叫IoC ( Inversion of Control)。通過這樣的設(shè)計模式,我們可以同時提供可維護(hù)性與可調(diào)試性,我們可以在通過覆蓋的方法提供功能更豐富的子類,實際上每次調(diào)用子類的onCreate()方法,都將調(diào)用到各個Activity拓展類的onCreate()方法。而這個方法一旦進(jìn)入,又會回調(diào)到父類的onCreate()方法,在父類的onCreate()方法里,我們可以提供更多針對諸多子類的通用功能(比如啟動時顯示的上下文狀態(tài)的恢復(fù),關(guān)閉時一些清理性工作),以及在這里面插入調(diào)試代碼。

            然后,我們可以加載顯示部分的代碼的UI,

  1. setContentView(R.layout.main);  

這一行,就會使我們想要顯示的圖形界面被輸出到屏幕上。我們可以隨意地修改我們的main.xml文件,從而使setContentView()之后顯示出來的內(nèi)容隨之發(fā)生變化。當(dāng)然,作為XML的UI,最終是在內(nèi)存里構(gòu)成的樹形結(jié)構(gòu),我們也可以在調(diào)用setContentView()之前通過編程來修改這個樹形結(jié)構(gòu),于是也可以改變顯示效果。

            到目前為止,我們也只是實現(xiàn)了將內(nèi)容顯示到屏幕上,而沒有實現(xiàn)交互的功能。如果要實現(xiàn)交互的功能,我們也只需要很簡單的代碼就可以做到,我們可以將HelloWorld.java改成如下的內(nèi)容,從而使用我們的”Hello world”字符串可以響應(yīng)點擊事件:
  1. package org.lianlab.hello;  
  2.   
  3. import android.os.Bundle;  
  4. import android.app.Activity;  
  5. import android.view.View;  
  6. import android.view.View.OnClickListener;  
  7. import android.widget.TextView;  
  8.   
  9. public class HelloWorld extends Activity {  
  10.     @Override  
  11.     public void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.main);  
  14.       ((TextView)findViewById(R.id.textView1)).setOnClickListener(  
  15.                 new OnClickListener() {  
  16.                    @Override  
  17.                    public void onClick(View v) {  
  18.                            finish();  
  19.                    }  
  20.            });  
  21.     }  
  22. }  

我們使用Activity類的findViewById()方法,則可以找到任何被R.java所索引起來的資源定義。我們在這里使用了R.id.textView1作為參數(shù),是因為我們在main.xml就是這么定義TextView標(biāo)簽的:android:id="@+id/textView1"

而我們找到字段之后,會調(diào)用TextView對象的setOnClickListener()方法,給TextView注冊一個onClickListener對象。這樣的對象,是我們在Android世界里遇到的第二次設(shè)計模式的使用(事實上Android的實現(xiàn)幾乎使用到所有的Java世界里的通用設(shè)計模式),Listener本身也會被作為Observer設(shè)計模式的一種別稱,主要是用于實現(xiàn)被動調(diào)用邏輯,比如事件回饋。

Observer(Listener)設(shè)計模式的思路,跟我們數(shù)據(jù)庫里使用到的Trigger功能類似,我們可對需要跟蹤的數(shù)據(jù)操作設(shè)置一個Trigger,當(dāng)這類數(shù)據(jù)操作進(jìn)行時,就會觸發(fā)數(shù)據(jù)庫自動地執(zhí)行某些操作代碼。而Observer(Listener)模式也是類似的,監(jiān)聽端通過注冊O(shè)bserver來處理事件的回調(diào),而真正的事件觸發(fā)者則是Observer,它的工作就是循環(huán)監(jiān)聽事件,然后再調(diào)用相應(yīng)監(jiān)聽端的回調(diào)。

這樣的設(shè)計,跟效率沒有必然聯(lián)系,太可以更大程度地降低設(shè)計上的復(fù)雜度,同時提高設(shè)計的靈活性。一般Observer作為接口類,被監(jiān)聽則會定位成具體的Subject,真正的事件處理,則是通過實現(xiàn)某個Observer接口來實現(xiàn)的。對于固定的事件,Subject對象與Observer接口是無須變動的,而Observer的具體實現(xiàn)則可以很靈活地被改變與擴(kuò)充。如下圖所示:


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


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


Click事件的觸發(fā)源是Touch事件,而當(dāng)前View的Touch事件在屬于點擊事件的情況下,會生成一個performClick的Runnable對象(可交由Thread對象來運行其run()回調(diào)方法)。在這個Runnable對象的run()方法里會調(diào)用注冊過的OnClickListener對象的OnClick()方法,也就是圖示中的mOnClickListener::onClick()。當(dāng)這個對象被post()操作發(fā)送到主線程時(作為Message發(fā)送給UI線程的Hander進(jìn)行處理),我們覆蓋過的OnClick()回調(diào)方法就由主線程執(zhí)行到了。

我們注冊的Click處理,只有簡單的一行,finish(),也就是通過點擊事件,我們會將當(dāng)前的Activity關(guān)閉掉。如果我們覺得這樣不過癮,我們也可通過這次點擊觸發(fā)另一個界面的執(zhí)行,比如直接搜索這個字符串。這樣的改動代碼量很小,首先,我們需要在HelloWorld.java的頭部引入所需要的Java包,

  1. import android.app.SearchManager;  
  2. import android.content.Intent;  

然后可以將我們的OnClick()方法改寫成啟動一個搜索的網(wǎng)頁界面,查找這個字符串,而當(dāng)前界面的Activity則退出。這時,我們新的OnClick()方法則會變成這個樣子:

  1. public void onClick(View v) {  
  2.        Intent query = new Intent(Intent.ACTION_WEB_SEARCH);  
  3.        query.putExtra(SearchManager.QUERY,  
  4. ((TextView)v).getText());  
  5.        startActivity(query);  
  6.        finish();  
  7.   }             

但是可能還是無法解決我們對于Android應(yīng)用程序與Java環(huán)境的區(qū)別的疑問:

T     Android有所謂的MVC,將代碼與顯示處理分享,但這并非是標(biāo)準(zhǔn)Java虛擬機(jī)環(huán)境做不到。一些J2EE的軟件框架也有類似的特征。

T     AndroidManifest.xml與On*系列回調(diào),這樣的機(jī)制在JAVA ME也有,JAVA ME也是使用類似的機(jī)制來運行的,難道Android是JAVA ME的加強(qiáng)版?

T     至于Listener模式的使用,眾所周知,Java是幾乎所有高級設(shè)計模式的實驗田,早就在使用Listener這樣模式在處理輸入處理。唯一不同的是ClickListener,難道Android也像是可愛的觸摸版Ubuntu手機(jī)一樣,只在是桌面Java界面的基礎(chǔ)加入了觸摸支持?

T     Activity從目前的使用上看,不就是窗口(Window)嗎?Android開發(fā)者本就有喜歡取些古怪名字的嗜好,是不是他們只是標(biāo)新立異地取了個Activity的名字?

對于類似這樣的疑問,則是從代碼層面看不清楚了,我們得回歸到Android的設(shè)計思想這一層面來分析,Android應(yīng)用程序的執(zhí)行環(huán)境是如何與眾不同的。

不過,我們可以從最后的那行Activity調(diào)用另一個Activity的例子里看出一些端倪,在這次調(diào)用里,我們并沒有顯式地創(chuàng)建新的Activity,如果從代碼直接去猜含義的話,我們只是發(fā)出了個執(zhí)行某種操作的請求,而這個請求并沒有指定有誰來完成。這就是Android編程思想的基礎(chǔ),一種全開放的“無界化”編程模型。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多