1. 程式人生 > >Android應用程式和其設計思想--轉載----做記錄

Android應用程式和其設計思想--轉載----做記錄

轉載一篇Android 的先關文章,無論什麼時候看都覺得有收穫

文章來源地址

http://www.open-open.com/lib/view/open1466070376316.html

以下是正文:

本文內容,主題是透過應用程式來分析Android系統的設計原理與構架。我們先會簡單介紹一下Android裡的應用程式程式設計,然後以這些應用程 序在執行環境上的需求來分析出,為什麼我們的Android系統需要今天這樣的設計方案,這樣的設計會有怎樣的意義, Android究竟是基於怎樣的考慮才變成今天的這個樣子,所以本文更多的分析Android應用程式設計背後的思想,品味良好架構設計的魅力。分五次連 載完成,第一部分是最簡單的部分,解析Android應用程式的開發流程。

Android應用程式開發以及背後的設計思想深度剖析 1

1. Android應用程式

在目前Android大紅大紫的情況下,很多人對編寫Android應用程式已經有了足夠深入的瞭解。即便是沒有充分的認識,在現在Android 手機已經相當普及的情況下,大家至少也會知道Android的應用程式會是一個以.apk為字尾名的檔案(在Windows系統裡,還會是一個帶可愛機器 人圖示的檔案)。那這個apk包又有什麼樣的含義呢?

如果您正在使用Linux作業系統,可以使用命令file命令來檢視這一檔案的型別。比如我們下載了一個Sample.apk的檔案,則使用下面的命令:

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

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

AndroidManifest.xml,  
classes.dex,  
resources.arsc,  
META-INF,  
res, 

到這裡,我們就可以看到一個Android應用程式結構其實是異常簡單的。這五部分內容(其中META-INF和res是目錄,其他是檔案)除了 META-INF是這一.apk檔案的校驗資訊,resources.arsc是資源的索引檔案,其他三部分則構成了Android應用程式的全部。

  • AndroidManifest.xml,這是每個Android應用程式包的配置檔案,這裡會儲存應用程式名字、作者、所實現的功能、以及一些許可權驗證資訊。但很可惜,在編譯完成的.apk檔案裡,這些檔案都被編譯成了二進位制版本,我們暫時沒有辦法看到內容,後面我們可以再看看具體的內容。
  • classes.dex,這則是Android應用程式實現的邏輯部分,也就是通過Java程式設計寫出來而被編譯過的程式碼。這種特殊的格式,是Android裡特定可執行格式,是可由Dalvik虛擬機器所執行的程式碼,這部分內容我們也會在後續的介紹Dalvik虛擬機器的章節裡介紹。
  • res,這一目錄裡則儲存了Android所有圖形介面設計相關的內容,比如介面應該長成什麼樣子、支援哪些語言顯示等等。

從一個android應用程式的包檔案內容,我們可以看到android應用程式的特點,這也是Android程式設計上的一些特徵:

1 簡單:最終生成的結果是如些簡單的三種組成,則他們的程式設計上也不會有太大的困難性。這並不是說 Android系統裡無法實現很複雜的應用程式,事實上Android系統擁有世界上僅次於iOS的應用程式生態環境,也擁有複雜的辦公軟體、大型3D遊 戲。而只是說,如果要實現和構成同樣的邏輯,它必然會擁有其他格式混雜的系統更簡化的程式設計模式。

2 Java作業系統:既然我們編譯得到的結果,classes.dex檔案,是用於Java虛擬機器 (雖然是Dalvik虛擬機器,但實際上這一虛擬機器只是一種特定的Java解析器和虛擬機器執行環境 )解析執行的,於是我們也可以猜想到,我們的Android系統,必然是一個Java作業系統。我們在後面會解釋,如果把Android系統直接看成 Linux核心和Java語言組合到一起的作業系統很不準確,但事實上Android,也還是Java作業系統,Java是唯一的系統入口。

使用MVC設計模式:所謂的MVC,就是Model,View,Controller的首字母組合起來的一種設計模 式,主要思想就是把顯示與邏輯實現分離。Model用於儲存上下文狀態、View用於顯示、而Controller則是用於處理使用者互動。三者之間有著如 下圖所示的互動模型,互動只到Controller,而顯示更新只通過View進行,這兩者再與Model交換介面狀態資訊:

Android應用程式開發以及背後的設計思想深度剖析

在現代的圖形互動相關的設計裡,MVC幾乎是在圖形互動處理上的不二選擇,這樣系統設計包括一些J2EE的應用伺服器框架,最受歡迎的 Firefox瀏覽器,iOS,MacOSX等等。這些使用MVC模式的最顯著特點就是顯示與邏輯分離,在Android應用程式裡我們看到了用於邏輯實 現的classes.dex,也看到用於顯示的res,於是我們也可以猜想到在UI上便肯定會使用MVC設計模式。

當然,所謂的Android應用程式程式設計,不會只有這些內容。到目前為止,我們也只是分析.apk檔案,於是我們可以回過頭來看看Android應用被編譯出來的過程。

2. Android程式設計

從程式設計角度來說,Android應用程式程式設計幾乎只與Java相關,而Java平臺本身是出了名跨平臺利器,理論上來說,所有Java環境裡使用的 程式設計工具、IDE工具,皆可用於Android的程式設計。Android SDK環境裡提供的程式設計工具,是基於標準的Java編譯工具ant的,但事實上,一些大型的Android軟體工程,更傾向於使用Maven這樣的並行化 編譯工具(maven.apache.org)。如果以前有過Java程式設計經驗,會知道Java環境裡的圖形化IDE(Integrated Development Environment)工具,並非只有Eclipse一種,實際上Java的官方IDE是NetBeans,而商用化的Java大型專案開發者,也可能 會比較鐘意於使用IntelliJ,而從底層開發角度來說,可能使用vim是更合適的選擇,可以靈活地在C/C++與Java程式碼之間進行切換。總而言 之,幾乎所有的Java環境的程式設計工具都可以用於Android程式設計。

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

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

2.1 使用Android SDK工具包

在Android開發過程中,如果Eclipse環境不可得的情況下,可以直接使用SDK來建立應用程式工程。首先需要安裝某一個版本的Android SDK開發包,這個工具包可以到http://developer.android.com/sdk/index.html這 個網址去下載,根據開發所用的主機是Windows、Linux還是MacOS X(MacOS僅支援Intel晶片,不支援之前的PowerPC晶片),下載對應的.zip檔案,比如android-sdk_r19- linux.zip。下載完成後,解壓到一個固定的目錄,我們這裡假定是通過環境變數$ANDROID_SDK_PATH指定的目錄。

下載的SDK包,預設是沒有Android開發環境支援的,需要通過tools目錄裡的一個android工具來下載相應的SDK版本以用於開發。我們通過執行$ANDROID_SDK_PATH/tools/android會得到如下的介面:

Android應用程式開發以及背後的設計思想深度剖析

在上面的安裝介面裡選擇不同的開發工具包,其中Tools裡包含一些開發用的工具,如我們的SDK包,實際上也會在這一介面裡進行更新。而對於不同 的Android版本,1.5到4.1,我們必須選擇下載某個SDK版本來進行開發。而下載完之後的版本資訊,我們既可以在這一圖形介面裡看到,也可以通 過命令列來檢視。

$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列出來的資訊,可以用於後續的開發之用,比如對於不同的target,最後得到了id:1、id:2這樣的資訊,則可以被用於應用程式工程 的建立。而細心一點的讀者會看到同一個4.1版本的SDK,實際可分為”android-16”和"Google Inc.:Google APIs:16",這樣的分界也還有有意義的,”android-16”用於“純”的android 4.1版的應用程式開發,而“Google Inc.:Google APIs:16”則加入了Google的開發包。
配置好環境之後,如果我們需要建立Android應用程式。tools/android這個工具,同時也具備可以建立Android應用程式工程的能力。我們輸入:

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

這樣我們就在hello目錄裡建立了一個Android的應用程式,名字是Hello,使用API16(Android 4.1的API版本),包名是org.lianlab.hello,而預設會被執行到的Activity,會是叫Helloworld的Activity 類。

掌握Android工具的一些使用方法也是有意義的,比如當我們的Eclipse工程被破壞的情況下,我們依然可以手工修復這一Android應用程式工程。或是需要修改該工程的API版本的話,可以使用下面的命令:

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

在這個工程裡,如果我們不加任何修改,會生成一個應用程式,這個應用程式執行的效果是生成一個黑色的圖形介面,打印出一行"Hello World, Helloworld"。如果我們需要對這一工程進行編譯等操作的話,剩下的事情就屬於標準的Java編譯了,標準的Java編譯,使用的是 ant(ant.apache.org)編譯工具。我們先改變當前目錄到hello,然後就可以通過” ant –projecthelp”來檢視可以被執行的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,已經直接可以安裝到Android裝置上進行測試執行。我們也可以使用ant release來生成一個bin/Hello-release-unsigned.apk,而這時的.apk檔案,則需要通過jarsigner對檔案進 行驗證才能進行安裝。

通過antdebug這一編譯指令碼,我們可以看到詳細的編譯過程。我們可以看到,一個Android的工程,最後會是通過如圖所示的方式生成最後的.apk檔案。 

Android應用程式開發以及背後的設計思想深度剖析

把一個Android的原始碼工程編譯成.apk的Android應用程式,其過程如下:

1) 所有的資原始檔,都會被aapt進行處理。所有的XML檔案,都會被aapt解析成二進位制格式,準確地說,這樣的二進位制格式,是可以被直接對映到記憶體裡的 二進位制樹。做過XML相關開發的工程師,都會知道,XML的驗證與解析是非常消耗時間與記憶體的,而通過編譯時進行XML解析,則節省了執行時的開銷。當然 解析的結果最後會被aapt通過一個R.java儲存一個二進位制樹的索引,程式設計時可通過這個R.java檔案進行XML的訪問。aapt會處理所有的資源 檔案,也就是Java程式碼之外的任何靜態性檔案,這樣處理既保證了資原始檔間的互相索引得到了驗證,也確保了R.java可以索引到這個應用程式裡所有的 資源。

2) 所有的Java檔案,都會被JDK裡的javac工具編譯成bin目錄下按原始碼包結構組織的.class檔案(.class是標準的Java可解析執行 的格式),比如我們這個例子裡生成的bin/classes/org/lianlab/hello/*.class檔案。然後這些檔案,會通過SDK裡提 供的一個dx工具轉換成classes.dex檔案。這一檔案,就是會被Dalvik虛擬機器所解析執行的

3) 最後我們得到的編譯過的二進位制資原始檔和classes.dex可執行檔案,會通過一個apkbuilder工具,通過zip壓縮演算法打包到一個檔案裡,生成了我們所常見的.apk檔案。

4) 最後,.apk檔案,會通過jarsigner工具進行校驗,這一校驗值會需要一個數字簽名。如果我們申請了Android開發者帳號,這一數字簽名就是 Android所分發的那個數字證書;如果沒有,我們則使用debug模式,使用本地生成的一個隨機的數字證書,這一檔案位於~/.android /debug.keystore。

雖然我們只是下載了SDK,通過一行指令碼建立了Android應用程式工程,通過另一行完成了編譯。但也許還是會被認為過於麻煩,因為需要進行字元介面的 操作,而且這種開發方式也不是常用的方式,在Java環境下,我們有Eclipse可用。我們可以使用Eclipse的圖形化開發工具,配合ADT外掛使 用。

2.2 使用Eclipse+ADT外掛

在Android環境裡可以使用Java世界裡幾乎一切的開發工具,比如NetBeans等,但Eclipse是Android官方標準的開發方 式。使用Eclipse開發,前面提到的開發所需SDK版本下載,也是必須的,然後還需要在Eclipse環境里加裝ADT外掛,Android Development Toolkit。

我們在Eclipse的選單裡,選擇”Help” à “Install New Software…”,然後在彈出的對話方塊裡的Workwith:輸入ADT的釋出地址:https://dl-ssl.google.com/android.eclipse,回車,則會得到下面的軟體列表。選擇Select All,將這些外掛全都裝上,則得到了可用於Android應用程式開發的環境。

Android應用程式開發以及背後的設計思想深度剖析

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

Android應用程式開發以及背後的設計思想深度剖析

點選OK之後,則可以進行Android開發了。選擇”File” à “New”à “Project” à “Android”,在Eclipse 3.x版本里,會是“Android Project”,在Eclipse 4.x版本里,會是“Android Application Project”。如果我們需要建立跟前面字元介面下一模一樣的應用程式工程,則在彈出的建立應用程式對話方塊裡填入如下的內容:

Android應用程式開發以及背後的設計思想深度剖析

然後我們選擇Next,一直到彈出最後介面提示,讓我們選擇預設Activity的名字,最後點選”Finish”,我們就得到一個Android 應用程式工程,同時在Eclipse環境裡,我們既可以通過圖形化介面編輯Java程式碼,也可以通過圖形化的介面編輯工具來繪製圖形介面。

(注意: 如果Android工程本身比較龐大,則最好將Eclipse裡的記憶體相關的配置改大。在Windows和Linux裡,是修改eclipse裡的 eclipse.ini檔案,而在MacOS裡,則是修改Eclipse.app/Contents/MacOS/eclipse.ini。一般會將如下 的值加大成兩倍: 

--launcher.XXMaxPermSize  
512m  
-vmargs  
-Xms80m  
-Xmx1024m  
)  

我們得到工程目錄,在Eclipse環境裡會是如下圖所示的組織方式。程式碼雖然是使用一模一樣的編譯方式,唯一的改變是,我們不再需要使用指令碼來完 成編譯,我們可以直接使用Eclipse的”Project”à“Build project”來完成編譯過程。如果我們使用預設設定,則程式碼是使用自動編譯的,我們的每次修改都會觸發增量式的編譯。

Android應用程式開發以及背後的設計思想深度剖析

我們從這些android程式設計的過程,看不出來android跟別的Java程式設計模式有什麼區別,倒至少驗證了我們前面對android程式設計特點的 猜想,就是很簡單。如果同樣我們使用Eclipse開發Java的圖形介面程式,需要大量地時間去熟悉API,而在Android這裡學習的曲線被大大降 低,如果我們只是要畫幾個介面,建立起簡單的互動,我們幾乎無須學習程式設計。

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

我們再來看一個,我們剛才建立的這個工程裡,我們怎麼樣進行下一步的開發。在Android開發裡,決定我們應用程式表現的,也就是我們從一個.apk檔案裡看到的,我們實際上只需要:

  • 修改AndroidManifest.xml檔案。AndroidManifest.xml是Android應用程式的主控檔案,型別於Windows裡的登錄檔,我們通過它來配置我們的應用程式與系統相關的一些屬性。
  • 修改UI顯示。在Android世界裡,我們可以把UI程式設計與Java程式設計分開對待,處理UI控制元件 的語言,我們可以叫它UI語言,或是layout語言,因為它們總是以layout型別的資原始檔作為主入口的。Android程式設計裡嚴格地貫徹MVC的 設計思路,使我們得到了一個好處,就是我們的UI跟要實現的邏輯沒有任何必然聯絡,我們可先去調整好UI顯示,而UI顯示後臺的實現邏輯,則可以在後續的 步驟裡完成。
  • 改寫處理邏輯的Java程式碼。也就是我們MVC裡的Controller與Model部分,這些部 分的內容,如果與UI沒有直接互動,則我們可以放心大膽的改寫,存在互動的部分,比如處理按鈕的點選,取回輸入框裡的文字等。作為一個定位於拓展能力要求 最高的智慧手機作業系統,android肯定不會只實現畫畫介面而已,會有強大的可開發能力,在android系統裡,我們可以開發企業級應用,大型遊 戲,以及完整的Office應用。

無論是通過tools/android工具生成的Android原始碼目錄,還是通過Eclipse來生成的Android原始碼工程,都需要進一 步去自定義這個步驟來完成一個Android應用程式。當然,還有一種特殊的情況就是,這個原始碼工程並非直接是一個Android應用程式,只是 Unit Test工程或是庫檔案工作,並不直接使用.apk檔案,這裡則可能後續的程式設計工作會變得不同。我們這裡是分析Android應用程式,於是後面分別來看 應用程式程式設計裡的這三部分的工作如何進行。

2.3 AndroidManifest.xml

先來看看AndroidManifest.xml檔案,一般出於方便,這一檔案有可能也被稱為manifest檔案。像我們前面的例子裡的建立的Android工程,得到的AndroidManifest.xml檔案就很簡單:

 <?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
      package="org.lianlab.hello"  
      android:versionCode="1"  
      android:versionName="1.0">  
    <application android:label="@string/app_name">  
        <activity android:name=".Helloworld"  
                  android:label="@string/app_name">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
    </application>  
</manifest>  

<div id="chart" style="width:360px;height:200px"/>
</body>
</html> 

作為xml檔案,所有的<>都會是成對的,比如我們看到的<manifest></manifest>,這被 稱為標籤(Tag)。標籤可以包含子標籤,從而可以形成樹型的結點關係。如果沒有子標籤,則我們也可以使用</>來進行標識,比如我們上面看 到的<actionandroid:name=”android.intent.action.MAIN” />。

<manifest>,是主標籤,每個檔案只會有一個,這是定義該應用程式屬性的主入口,包含應用程式的一切資訊,比如我們的例子裡定義了xml的名稱空間,這個應用程式的包名,以及版本資訊。

<application>,則是用於定義應用程式屬性的標籤,理論上可以有多個,但多個不具有意義,一般我們一個應用程式只會有一個,在這個標籤裡我們可以定義圖示,應用程式顯示出來的名字等。在這一標籤裡定義的屬性一般也只是輔助性的。

<activity>,這是用來定義介面互動的資訊。我們在稍後一點的內容介紹Android程式設計細節時會描述到這些資訊,這一標籤裡的屬性定義會決定應用程式可顯示效果。比如在啟動介面裡的顯示出來的名字,使用什麼樣的圖示等。

<intent-filter>,這一標籤則用來控制應用程式的能力的,比如該圖形介面可以完成什麼樣的功能。我們這裡的處理比較簡單,我們只是能夠讓這個應用程式的HelloWorld可以被支援點選到執行。

從這個最簡單的AndroidManifest.xml檔案裡,我們可以看到Android執行的另一個特點,就是可配置性強。它跟別的程式設計模型很不一樣的地方是,它沒有程式設計式規定的main()函式或是方法,而應用程式的表現出來的形態,完全取決於<activity>欄位是如何定義它的。

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

我們可以再來看android的UI構成。UI也是基於XML的,是通過一種layout的資源引入到系統裡的。在我們前面看到的最簡單的例子裡,我們會得到的圖形介面是res/layout/main.xml,在這一檔案裡,我們會看到開啟顯示,並在顯示區域裡打印出Hello World, Helloworld。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        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" />
</RelativeLayout> 

在這個圖形介面的示例裡,我們可以看到,這樣的圖形程式設計方式,比如傳統的方式裡要學習大量的API要方便得多。

<LinearLayout>,這一標籤會決定應用程式如何在介面裡擺放相應的控制元件

<TextView>,則是用於顯示字串的圖形控制元件

使用這種XML構成的UI介面,是MVC設計的附屬產品,但更大的好處是,有了標準化的XML結構,就可以建立可以用來畫介面的IDE工具。一流的系統提供工具,讓設計師來設計介面、工程師來邏輯,這樣生產出來的軟體產品顯示效果與使用者體驗會更佳,比如iOS;二流的系統,介面與邏輯都由工程師來完成,在這種系統上開發出來的軟體,不光介面不好看,使用者體驗也會不好。我們比如在Eclipse裡的工程裡檢視,我們會發現,我們開啟res/layout/main.xml,會自動彈出來下面的視窗,讓我們有機會使圖形工具來操作介面。

Android應用程式開發以及背後的設計思想深度剖析

在上面IDE工具裡,左邊是控制元件列表,中間是進行繪製的工作區,右邊會是控制元件一些微調視窗。一般我們可以從左邊控制列表裡選擇合適的控制元件,拖到中間的工作區來組織介面,原則上的順序是layout à 複合控制元件 à 簡單控制元件。中間區域可以上面還有選擇項用於控制顯示屬性,在工作區域裡我們可以進一步對介面進行微調,也可以選擇控制元件點選左鍵,於是會出來上下文選單來操作控制元件的屬性。到於右邊的操作介面,上部分則是整個介面構成的樹形結構,而下部分則是當我們選擇了某個介面元素時,會顯示上下文的屬性。最後,我們還可以在底部的Graphic Layout與main.xml進行圖形操作介面與原始碼編輯兩種操作方式的切換。

有了這種工具,就有可能實現設計師與工程師合作來構建出美觀與互動性更好的Android應用程式。但可惜的是,Android的這套UI設計工具,太過於程式設計化,而且由於版本變動頻繁的原因,非常複雜化,一般設計師可能也不太容易學好。更重要的一點,Android存在碎片化,螢幕尺寸與顯示精度差異性非常大,使實現畫素級精度的介面有技術上的困難。也這是Android上應用程式不如iOS上漂亮的原因之一。但這種設計至少也增強了介面上的可設計性,使Android應用程式在觀感上也有不俗表現。

我們可以再回過頭來看看應用程式工程裡的res目錄,res目錄裡包含了Android應用程式裡的可使用的資源,而資原始檔本身是可以索引的,比如layout會引用drawable與values裡的資源。對於我們例子裡使用的<TextView … android:text="Hello World,Helloworld" …>,我們可以使用資源來進行引用,<TextView …android:text=” @string/hello_string” …>,然後在res/values/strings.xml里加入hello_string的定義。

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

從通過這種方式,我們可以看另外一些特點,就是Android應用程式在多介面、多環境下的自適應性。對於上面的字串修改的例子,我們如果像下面的示例環境那樣定義了res/layout-zh/strings.xml,並提供hello_string的定義:

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

最後,得到的應用程式,在英文環境裡會顯示‘Hello world!’,而如果系統當前的語言環境是中文的話,就會顯示成‘歡迎使用!’。這種自適應方式,則是不需要我們進行程式設計的,系統會自動完成對這些顯示屬性的適配。

當然,這時可能會有人提疑問,如果這時是韓文或是日文環境會出現什麼情況呢?在Android裡,如果不能完成相應的適配,就會使用預設值,比如即使是我們建立了res/values-zh/strings.xml資源,在資源沒有定義我們需要使用的字串,這時會使用英文顯示。不管如何,Android提供自適應顯示效果,但也保證總是不是會出錯。

這些也體現出,一旦Android應用程式寫出來,如果對多語言環境不滿意,這時,我們完全可以把.apk按zip格式解開,然後加入新的資原始檔定義,再把檔案重新打包,也就達到了我們可能會需要的漢化的效果。

Android應用程式開發以及背後的設計思想深度剖析

在res目錄裡,我們看到,對於同一種類型的資源,比如drawable、values,都可以在後面加一個字尾,像mdpi,hdpi, ldpi是用於適配解析度的,zh是用來適配語言環境的,large則是用來適配螢幕大小的。對於這種顯示上的自適應需求,我們可以直接在Eclipse裡通過建立Android XML檔案裡得到相應的提示,也可以參考http://developer.android.com/training/basics/supporting-devices/檢視具體的使用方式。

於是,透過資原始檔,我們進一步驗證了我們對於Android MVC的猜想,在Android應用程式設計裡,也跟iOS類似,可以實現介面與邏輯完全分離。而另一點,就是Android應用程式天然具備螢幕自適應的能力,這一方面帶來的影響是Android應用程式天生具備很強的適應性,另一方面的影響是Android裡實現畫素精度顯示的應用程式是比較困難的,維護的代價很高。

我們可以再通過應用程式的程式碼部分來看看應用程式是如何將顯示與邏輯進行繫結的。

2.5 Java程式設計(src/org/lianlab/hello/HelloWorld.java)

在Android程式設計裡,實現應用程式的執行邏輯,幾乎就是純粹的Java程式設計。但在程式設計上,由於Android的特殊性,這種Java程式設計也還是被定製過的

我們看到我們例子裡的原始碼,如果寫過Java程式碼,看到這樣的原始碼存放方式,就可以瞭解到Android為什麼被稱為Java作業系統的原因了,像這種方式,就是標準的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系統設計思路里的很重要的一部分,所有與介面互動相關的操作類都是Activity,是MVC框架裡的Controller部分。那Model部分由誰來提供呢?這是由Android系統層,也就是Framework來提供的功能。當介面失去焦點時,當介面完全變得不可見時,這些都屬於Framework層才會知道的狀態,Framework會記錄下這些狀態變更的資訊,然後再回調到Activity類提供的相應狀態的回撥方法。關於Activity我們後面來詳細說明,而見到Activity類的最簡單構成,我們大體上就可以形成Android世界裡的完整MVC框架構成完整印象了。

我們繼承了Activity類之後,就會覆蓋其onCreate()回撥方法。這裡我們使用了”@Override”標識,這是一種Java語言裡的Annotation(程式碼註釋)技術,相當於C語言裡的pragma,用於告訴編譯器一些相應引數。我們的Override則告訴javac編譯器,下面的方法在構建物件時會覆蓋掉父類方法,從而提高構建效率。Activity類裡提供的onXXX()系列的都可以使用這種方法進行覆蓋,從而來實現自定義的方法,切入到Android應用程式不同狀態下的自定義實現。

我們覆蓋掉的onCreate()方法,使用了一個引數,savedInstanceState,這個引數的型別是Bundle。Bundle是構建在Android的Binder IPC之上的一種特殊資料結構,用於實現普通Java程式碼裡的Serialization/Deserializaiton功能,序列化與反序列化功能。在Java程式碼裡,我們如果需要儲存一些應用程式的上下文,如果是字串或是資料值等原始型別,則可以直接寫到檔案裡,下次執行時再把它讀出來就可以了。但假設我們需要儲存的是一個物件,比如是介面的某個狀態點,像下面的這樣的資料結構: 

class ViewState {
    public int focusViewID;
    public Long layoutParams ;
    public String textEdited;
…
}

這時,我們就無法存取這樣的結構了,因為這樣的物件只是記憶體裡的一些標識,存進時是一個程序上下文環境,取回來時會是另一種,就會出錯。為了實現這樣的功能,就需要序列化與反序列化,我們讀寫時都不再是以物件為單位,而是以類似於如下結構的一種字典型別的結構,最後進行操作的是一個個的鍵值對, ViewState[‘focusViewID’]的值會是valueOfViewID,一個整形值。 

‘ViewState’ {
            ‘focusViewID’: valueOfViewID,
            ‘LayoutParams’:valueOfLayoutParams,
            ‘textEdited’:  ‘User input’,

}

我們按這種類似的格式寫到檔案裡,當再讀取出來時,我們就可以新建一個ViewState物件,再使用這些儲存過的值對這一物件進行初始化。這樣就可以實現物件的儲存與恢復,這是我們onCreate()方法裡使用Bundle做序列化操作的主要目的,我們的Activity會有不同生存週期,當我們有可能需要在程序退出後再次恢復現象時,我們就會在退出前將上下文環境儲存到一個onSavedInstance的Bundle物件裡,而在onCreate()將顯示的上下文恢復成退出時的狀態。

而另一個必須要使用Bundle的理由是,我們的Activity與實現Activity管理的Framework功能部件ActivityManager,是構建在不同程序空間上的,Activity將執行在自己獨立的程序空間裡,而Framework則是執行在另一個系統級程序SystemServer之上。我們的Bundle是一種進行過序列化操作的物件,於是相應的操作是系統程序會觸發Activity的進行onCreate()回撥操作,而同時會轉回一個上下文環境的Bundle,可將Activity恢復到系統指定的某種圖形介面狀態。Bundle也可能為空,比如Activity是第一個被啟動的情況下,這個空的onSavedInstance則會被忽略掉。

我們進入到onCreate()方法之後,第一行便是 

super.onCreate(savedInstanceState); 

從字面上看,這種方式相當於我們繼承了父類方法,然後又回撥到父類的onCreate()來進行處理。這種方式貌似很怪,但這是設計模式(Design Pattern)裡鼎鼎大名的一種,叫IoC ( Inversion of Control)。通過這樣的設計模式,我們可以同時提供可維護性與可除錯性,我們可以在通過覆蓋的方法提供功能更豐富的子類,實際上每次呼叫子類的onCreate()方法,都將呼叫到各個Activity拓展類的onCreate()方法。而這個方法一旦進入,又會回撥到父類的onCreate()方法,在父類的onCreate()方法裡,我們可以提供更多針對諸多子類的通用功能(比如啟動時顯示的上下文狀態的恢復,關閉時一些清理性工作),以及在這裡面插入除錯程式碼。

然後,我們可以載入顯示部分的程式碼的UI,

setContentView(R.layout.main); 

這一行,就會使我們想要顯示的圖形介面被輸出到螢幕上。我們可以隨意地修改我們的main.xml檔案,從而使setContentView()之後顯示出來的內容隨之發生變化。當然,作為XML的UI,最終是在記憶體裡構成的樹形結構,我們也可以在呼叫setContentView()之前通過程式設計來修改這個樹形結構,於是也可以改變顯示效果。

到目前為止,我們也只是實現了將內容顯示到螢幕上,而沒有實現互動的功能。如果要實現互動的功能,我們也只需要很簡單的程式碼就可以做到,我們可以將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作為引數,是因為我們在main.xml就是這麼定義TextView標