1. 程式人生 > >Android應用開發以及設計思想深度剖析(1)

Android應用開發以及設計思想深度剖析(1)

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

特別宣告:本系列文章LiAnLab.org著作權所有,轉載請註明出處。作者系LiAnLab.org資深Android技術顧問吳赫老師。

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交換介面狀態資訊:

在 現代的圖形互動相關的設計裡,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會得到如下的介面:

在 上面的安裝介面裡選擇不同的開發工具包,其中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的原始碼工程編譯成.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應用程式開發的環境。

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

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

然 後我們選擇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跟別的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>

作為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,會自動彈出來下面的視窗,讓我們有機會使圖形工具來操作介面。

在 上面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格式解開,然後加入新的資原始檔定義,再把檔案重新打包,也就達到了我們可能會需要的漢化的效果。

在 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標籤的:android:id="@+id/textView1"

而 我們找到欄位之後,會呼叫TextView物件的setOnClickListener()方法,給TextView註冊一個 onClickListener物件。這樣的物件,是我們在Android世界裡遇到的第二次設計模式的使用(事實上Android的實現幾乎使用到所有 的Java世界裡的通用設計模式),Listener本身也會被作為Observer設計模式的一種別稱,主要是用於實現被動呼叫邏輯,比如事件回饋。

Observer(Listener) 設計模式的思路,跟我們資料庫裡使用到的Trigger功能類似,我們可對需要跟蹤的資料操作設定一個Trigger,當這類資料操作進行時,就會觸發數 據庫自動地執行某些操作程式碼。而Observer(Listener)模式也是類似的,監聽端通過註冊Observer來處理事件的回撥,而真正的事件觸 發者則是Observer,它的工作就是迴圈監聽事件,然後再呼叫相應監聽端的回撥。

這樣的設計,跟效率沒有必然聯絡,太可以更大程度地降低設計 上的複雜度,同時提高設計的靈活性。一般Observer作為介面類,被監聽則會定位成具體的Subject,真正的事件處理,則是通過實現某個 Observer介面來實現的。對於固定的事件,Subject物件與Observer介面是無須變動的,而Observer的具體實現則可以很靈活地被 改變與擴充。如下圖所示:

如果我們對於監聽事件部分的處理,也希望能加入這樣的靈活性,於是我們可以繼續抽象,將Subject泛化成一個Observable介面,然後可以再提供不同的Observable介面的實現來設計相應的事件觸發端。

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

Click 事件的觸發源是Touch事件,而當前View的Touch事件在屬於點選事件的情況下,會生成一個performClick的Runnable物件(可 交由Thread物件來執行其run()回撥方法)。在這個Runnable物件的run()方法裡會呼叫註冊過的OnClickListener物件的 OnClick()方法,也就是圖示中的mOnClickListener::onClick()。當這個物件被post()操作傳送到主執行緒時(作為 Message傳送給UI執行緒的Hander進行處理),我們覆蓋過的OnClick()回撥方法就由主執行緒執行到了。

我們註冊的 Click處理,只有簡單的一行,finish(),也就是通過點選事件,我們會將當前的Activity關閉掉。如果我們覺得這樣不過癮,我們也可通過 這次點選觸發另一個介面的執行,比如直接搜尋這個字串。這樣的改動程式碼量很小,首先,我們需要在HelloWorld.java的頭部引入所需要的 Java包,

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

然後可以將我們的OnClick()方法改寫成啟動一個搜尋的網頁介面,查詢這個字串,而當前介面的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環境的區別的疑問:

Þ Android有所謂的MVC,將程式碼與顯示處理分享,但這並非是標準Java虛擬機器環境做不到。一些J2EE的軟體框架也有類似的特徵。

Þ AndroidManifest.xml與On*系列回撥,這樣的機制在JAVA ME也有,JAVA ME也是使用類似的機制來執行的,難道Android是JAVA ME的加強版?

Þ 至 於Listener模式的使用,眾所周知,Java是幾乎所有高階設計模式的實驗田,早就在使用Listener這樣模式在處理輸入處理。唯一不同的是 ClickListener,難道Android也像是可愛的觸控版Ubuntu手機一樣,只在是桌面Java介面的基礎加入了觸控支援?

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

對於類似這樣的疑問,則是從程式碼層面看不清楚了,我們得迴歸到Android的設計思想這一層面來分析,Android應用程式的執行環境是如何與眾不同的。

不 過,我們可以從最後的那行Activity呼叫另一個Activity的例子裡看出一些端倪,在這次呼叫裡,我們並沒有顯式地建立新的Activity, 如果從程式碼直接去猜含義的話,我們只是發出了個執行某種操作的請求,而這個請求並沒有指定有誰來完成。這就是Android程式設計思想的基礎,一種全開放的 “無界化”程式設計模型。

相關推薦

Android應用開發以及設計思想深度剖析1

本文內容,主題是透過應用程式來分析Android系統的設計原理與構架。我們先會簡單介紹一下Android裡的應用程式程式設計,然後以這些應用程 序在執行環境上的需求來分析出,為什麼我們的Android系統需要今天這樣的設計方案,這樣的設計會有怎樣的意義, Android究竟

Android應用開發以及設計思想深度剖析

1. 支撐應用程式的Android系統 分析一個系統的構成,可以有多個出發點。從不同出發點,我們可從不同側面分析這個系統的設計,以及為什麼要這樣設計: T 從系統結構出發,Android系統給我們的感覺就是一種簡潔的,分層式的構架實現,從這種分層式的構架實現角度,我們可以理解這個系統是如何被組織到一起。

Android應用開發:網絡工具——Volley

respond sid 開發 多少 called creat miss 相等 eal 引言 在Android應用開發:網絡工具——Volley(一)中結合Cloudant服務介紹了Volley的一般使用方法,當中包括了兩種請求類型StringRequest和JsonOb

Android應用開發:網絡工具——Volley

要求 com 庫文件 urn welcom 順序 之前 air tin 引言 網絡一直是我個人的盲點,前一陣子抽空學習了一下Volley網絡工具的用法,也透過源代碼進行了進一步的學習,有一些心得想分享出來。在Android開發中,成熟的網絡工具不少,And

Spark核心原始碼深度剖析1 - Spark整體流程 和寬依賴和窄依賴

1 Spark 整體流程 2 寬依賴和窄依賴 2.1 窄依賴 Narrow Dependency,一個RDD對它的父RDD,只有簡單的一對一的依賴關係。即RDD的每個 partition僅僅依賴於父RDD中的一個 partition。父RDD和子RDD的

android應用開發-從設計到實現 3-4 靜態原型的狀態欄

不同的 討論 group 手把手教你 copy lac csdn article statusbar 靜態原型的狀態欄 狀態欄Symbol 狀態欄似乎非常復雜,有wifi信號、手機信號、時間、電量等信息,幸好Sketch原生就自帶的現成組件,你能

【IOT APP】 車聯網應用開發技術及過程深度剖析

app 客戶端 位置 targe ket 來源 底層 協議封裝 通道  在上篇文章新興的IoT行業風口,能夠把握的機會有哪些?中,我們介紹了目前六大常見的IOT移動應用開發類型。今天以APICloud開發的車聯網項目為例,剖析其開發過程中的相關項目經驗和通信技術架構!  

android應用開發-從設計到實現 4-8 天氣預報的佈局

天氣預報的佈局 現在我們開始進行天氣預報區域的佈局。 可以看出,這個區域,由5個完全一樣的元件組合而成。只要我們完成一個元件-天氣預報項的佈局,再把這個佈局複製貼上,很容易就完成了。 天氣預報項 在layout目錄上點選右鍵,選擇New -&

android應用開發-從設計到實現 1-1 創意

前言 當我們面對生活中各種讓你感動的創意、作品之時,一定有過這樣的想法:假如這是我自己設計並做出來的,那該多好啊。 是啊,假如有一件完全由自己產生的作品,能夠帶給別人方便與快樂,能夠受到別人的欣賞,那該是一件多麼幸福的事情。 作為設計師的我們,作為程式設計

android應用開發-從設計到實現 1-2 功能的確定

功能的確定 產品的功能並不是在確定了產品之後才開始考慮的,功能與創意選擇其實是一個“你中有我,我中有你”的關係。為了體現產品設計的階段性,我才將它單獨提出來分析。 這裡討論的產品功能,是建立在選定了某個基礎之上的。比如我們之前選定了天氣預報,那麼就將產品功能

android應用開發-從設計到實現 3-1 原型設計

原型設計 通過前面幾個章節,我們已經掌握了安卓系統Material Design設計的大致原則。接下來,我們開始嘗試將這些原則和方式運用到實際的專案當中。 效果圖與互動原型 產品原型的設計又可以分成兩個階段, 高保真效果圖:這是產品的靜態效果圖,圖

【Win 10 應用開發】UI Composition 劄記:與 XAML 集成

單獨使用 切換 column gif 頂部 tel border 靜態 ons 除了 DirectX 遊戲開發,我們一般很少單獨使用 UI Composition ,因此,與 XAML 互動並集成是必然結果。這樣能夠把兩者的優勢混合使用,讓UI布局能夠更靈活。 說到與 X

【Win 10 應用開發】UI Composition 劄記:繪制圖形

圖形 package 記得 aml 3.5 平時 surf 繪圖 str 使用 Win 2D 組件,就可以很輕松地繪制各種圖形,哪怕你沒有 D2D 相關基礎,也不必寫很復雜的 C++ 代碼。 先來說說如何獲取 Win 2D 組件。很簡單,創建 UWP 應用項目後,你打開&

【Win 10 應用開發】UI Composition 劄記:燈光

傳播 目標 spa 速度 review sta sset ext 集合 UI Composition 除了能夠為 UI 元素建立三維空間外,還有相當重要的一個部件——燈光。宇宙萬物的精彩繽紛,皆源於光明,光,使我們看到各種東西,除了黑洞之外的世界都是

【Win 10 應用開發】UI Composition 劄記:動畫

onclick 相對 行修改 log review asset 是你 express iteration 動畫在 XAML 中也有,而且基本上與 WPF 中的用法一樣。不過,在 UWP 中,動畫還有一種表現方式—— 通過 UI Composition

【Win 10 應用開發】UI Composition 劄記:基於表達式的動畫

eat seconds fin ima 旋轉 range align 綁定 true 上一篇爛文中,老周給大夥伴們介紹過了幾個比較好玩的動畫。本篇咱們深化主題,說一說基於表達式的動畫。這名字好理解,就是你可以用公式 / 等式來產生動畫的目標值。比如,你想讓某個可視化對象的高

《Flask Web開發——基於Python的Web應用開發實踐》一字一句上機實踐

屬性 一個用戶 臨時 target 說明 實戰 分享圖片 ace 庫文件 目錄 前言 第8章 用戶認證 第9章 用戶角色 第10章 用戶資料 第11章 博客文章 第12章 關註者 第13章 用戶評論 第14章 應用編程接口 前言

知識圖譜實戰開發案例剖析1

get n) exp nat lar ani fat 前言 image 一、前言 這是系列博文《知識圖譜實戰開發案例剖析》第1部分:知識圖譜基礎,第一節:知識圖譜完整案例演示。該系列內容同時已經錄制成視頻課程,感興趣的可以訪問網易雲課堂。作者:張子良,版權所有,轉載請註明

Android安全/開發基礎--8--Java本地介面JNI

8-1、JNI概述 JNI的本意是Java Native Interface(Java本地介面),是為了方便Java和C/C++等原生代碼所封裝的一層介面,使用JNI技術可以對Java層遮蔽不同作業系統平臺之間的差異,從而實現Java本身的平臺無關特性。JNI和

《Spring設計思想》AOP設計思想與原理圖文並茂 侵立刪

轉自:https://mp.weixin.qq.com/s/1p2XSfhM1V5CcWro6PZEwQ   前言   Spring 提供了AOP(Aspect Oriented Programming) 的支援, 那麼,什麼是AOP呢?本文將通過一個另外一個角度