InstallAndroid
安裝 Android 開發工具
Android 提供免費而且跨平台的整合開發環境,只要電腦能連接上網路,我們隨時都能下載相關工具下來,並開始開發 Android 應用程式。 有了輕鬆易用的開發工具,我們可以把心力專注於如何將想法實現到應用程式上。
系統需求
撰寫 Android 的應用程式,需要一套個人電腦系統。至於作業系統的部份,幾個主流作業系統都有支援。
支援的作業系統如下:
· Windows XP 或 Vista
· Mac OS X 10.4.8 或之後版本 (適用 x86 架構的 Intel Mac)
· Linux (官方於 Ubuntu 6.10 Dapper Drake 上測試)
我們需要安裝一些 Android 開發環境所需的程式工具,這些工具都是可以免費上網取得的:
· JDK 5 或 JDK 6
你需要安裝 Java 開發工具 (JDK 5 或 JDK 6)。 只安裝 Java 運行環境(JRE) 是不夠的,你需要安裝 Java 開發環境 (JDK)。 你可以在命令行上輸入 「java -version」 來查看目前系統上已安裝的 java 版本(java 版本需 >1.5)。 要注意的是 Android 與 Java Gnu 編譯器 (gcj) 還不相容。
· Eclipse IDE,一個多用途的開發工具平台。
你可以下載安裝 Eclipse 3.5 (代號 Galileo) 、 3.4 (代號 Ganymede) 或 Eclipse 3.3 (代號 Europa) 版。 請注意你選擇的版本需包含 Eclipse Java 開發工具擴充套件(Java Development Tool Plugin, JDT)。 大多數 Eclipse IDE 包中都已含有 JDT 擴充套件。若對 Eclipse 平台不熟悉的話,建議直接選擇 「for Java Developers」版本來下載。
· ADT,基於 Eclipse 的 Android 開發工具擴充套件 (Android Development Tools plugin)。
· Android SDK,Android 程式開發套件,包含 Android 手機模擬器(Emulator)。
· 其他開發環境工具(非必要安裝)
o Linux 和 Mac 環境上需要自動編譯的話可以自行安裝 Apache Ant 1.6.5 或之後版本,Windows 環境上則需要 Apache Ant 1.7 或之後版本。 o NetBeans、IDEA 等開發平台亦有推出自己的 Android 開發工具,但本書中還是以討論官方基於 Eclipse 平台的開發工具為準,其他平台不予涉及。
安裝
流程
快递问题件怎么处理流程 河南自建厂房流程下载 关于规范招聘需求审批流程 制作流程表下载 邮件下载流程设计
假設讀者已先安裝了 JDK 5 或 JDK 6。 那麼 Android 的安裝流程可以分為以下五個步驟
1. 下載 Eclipse
2. 安裝 Eclipse
3. 安裝 ADT 擴充套件
4. 下載 Android SDK
5. 設定 Android SDK
詳細的安裝流程如下:
1. 下載 Eclipse
首先我們需要下載 Android 開發時會用到的整合開發環境 Eclipse。 目前 Android 應用程式只支援使用「Java」程式語言來編寫 Android 應用程式。所以開發前必須先安裝 Java 開發套件(Java Development Kit, JDK)。 各平台的 JDK 可至 http://java.sun.com 下載。 Mac OS X 作業系統則已內建 JDK。 安裝好 JDK 後,我們可以前往 Eclipse 網站下載 Eclipse 這個方便的整合開發環境。 下載 Eclipse 時請選「Eclipse IDE for Java Developers」或「Eclipse IDE for Java EE Developers」這兩種版本,只有這兩種版本才會預裝 Eclipse JDT 擴充套件。 範例中所選擇的是「Eclipse IDE for Java Developers」版本。 下載完同樣先解壓縮到適當目錄下。
2. 安裝 Eclipse
Eclipse 不需要安裝,只要確認你的系統上有安裝 Java,即可直接開啟 Eclipse 資料夾,點擊 Eclipse 開始執行 Eclipse 整合開發環境。 第一次啟動 Eclipse 時會彈出視窗讓你決定預設的工作目錄。一般使用 Eclipse 預設的工作目錄即可。 進入到 Eclipse IDE 後,不必急著四處觀望。我們先來安裝 Android 開發工具擴充套件。
3. 安裝 ADT 擴充套件
我們將在 Eclipse 上 安裝 Android 開發工具 (ADT)。
Eclipse 3.5 版
找到螢幕上方的選單列, 選擇 「Help->Install New Softare」 選項,這選項會帶出一個新視窗。 選擇「Available Software」標籤,選擇右方的 「Add...」 (新增網站)按鈕,會彈出一個輸入框。
在輸入框中的"Location"欄位中輸入擴充套件的名稱(Name) 「ADT」 跟網址(URL) 「http://dl-ssl.google.com/android/eclipse/site.xml 」,按下 "OK" 按鈕離開。Eclipse 會花一點時間尋找合適的版本。
在視窗中全選「 https://dl-ssl.google.com/android/eclipse/site.xml 」項目「Developer Tools」中的的選項後,按下右方的「Install」按鈕。
按下 「Next」 (下一步)鍵。照著步驟就安裝完成。安裝完會提示需重新啟動 Eclipse,按下 「Yes」 重新啟動。
Eclipse 3.4 版
找到螢幕上方的選單列, 選擇 「Help->Software Updates」 選項,這選項會帶出一個新視窗。 選擇「Available Software」標籤,選擇右方的 「Add Site...」 (新增網站)按鈕,會彈出一個輸入框。
在輸入框中的"Location"欄位中輸入網址(URL) 「http://dl-ssl.google.com/android/eclipse/site.xml 」, 按下 "OK" 按鈕離開。Eclipse 會花一點時間尋找合適的版本。
在視窗中全選「 https://dl-ssl.google.com/android/eclipse/site.xml 」項目「Developer Tools」中的的選項後,按下右方的「Install」按鈕。
按下 「Next」 (下一步)鍵。照著步驟就安裝完成。安裝完會提示需重新啟動 Eclipse,按下 「Yes」 重新啟動。
Eclipse 3.3 版
找到螢幕上方的選單列, 選擇 「Help->Software Updates->Find and Install」 選項,這選項會帶出一個新視窗。
選擇 「Search for new features to install」 (搜尋新功能供安裝)選項,按下 「Next」 (下一步)鍵。出現設定畫面。
選擇右上角的 「New Remote Site」 (新增遠端網站)按鈕,會彈出一個 「New Update Site」 (新增更新網站)輸入框。
在輸入框中輸入擴充套件的名稱(Name) 「ADT」 跟網址(URL) 「http://dl-ssl.google.com/android/eclipse/site.xml 」,按下 「OK」 按鈕離開。
按下 「Finish」 按紐繼續下一步。Eclipse 會花一點時間尋找合適的版本。
接著我們要做的,就是等 Eclipse 顯示出選項時,勾選合適的版本安裝。
安裝完會提示需重新啟動 Eclipse,按下 「OK」 重新啟動。
離線安裝
已經安裝成功的讀者可以跳過這段。有些讀者因為網路環境的關係,無法順利地直接線上安裝 Android 開發工具。這時我們可以先前往http://developer.android.com/sdk/adt_download.html ,手動下載最新的開發工具版本來離線安裝。
下載完最新的ADT 擴充套件後,打開 Eclipse 開發環境,找到螢幕上方的選單列, 選擇 「Help->Software Updates」 選項,這選項會帶出一個新視窗。選擇「Available Software」標籤,選擇右方的 「Add Site...」 (新增網站)按鈕,會彈出一個輸入框。
選擇右上角的「Local...」按鈕,並選取剛下載的 Android 最新開發工具檔案,選到之後按下 "OK" 按鈕離開。 在視窗中全選新出現項目的所有選項後,按下右方的「Install」按鈕。Eclipse 會花一點時間開始安裝 ADT 擴充套件。
4. 下載 Android SDK
接著我們要從 http://developer.android.com/ Android 官方網站下載 「Android 軟體開發套件」 (Software Development Kit, SDK)。 下載下來的 SDK 檔案需要先解壓縮。Windows 平台需要先另行安裝解壓縮程式,如免費的 7-zip 解壓縮工具。 解壓縮後會出現一個資料夾。為了之後描述方便,我們將解壓縮後的 Android SDK 檔案夾命名為「android_sdk」。
5. 設定 Android SDK
打開偏好設定頁面(Preference),選擇 Android 標籤(請確認您已安裝好 ADT 擴充套件,Android 標籤才會出現在偏好設定頁面中),在 SDK Location 欄位按下 " Browse..."鍵,選擇剛剛解壓縮完的「android_sdk」檔案夾所在地,然後按下視窗右下角的套用(Apply) 按鈕。 這樣一來,Android SDK 就算是設定好啦。
註解:若您安裝過 SDK 1.5 版之前的版本,請先移除後再重新安裝一次 ADT 擴充套件,才能順利設定新版的 Android SDK。 方法是在螢幕上方的選單列,選擇「Help > Software Updates」選項,在彈出的視窗上方點選「Installed Software」頁籤,選擇「Android」開頭的選項,點選右側的「Uninstall..」按鈕移除這些相關的插件。
下一步
設定好 Android SDK 後,我們就擁有了一個完整的 Android 開發環境。 我們先來看看 Android SDK 中提供的一些範例,好了解 Android 到底能做些什麼。
ManageSDK
管理 SDK
管理 SDK
下載 SDK 組件
在選單上選擇「Window > Android SDK and AVD Manager」選項,開啟 Android SDK/AVD 管理工具。
在開啟的管理工具視窗中,切換到「Installed Packages」標籤頁,「Installed Packages」列
表
关于同志近三年现实表现材料 材料类招标技术评分表 图表与交易pdf 视力表打印pdf 用图表说话 pdf
中預設只裝了「Android SDK Tools」,不包含目標 SDK。我們需要先自行安裝對應的 SDK 組件。
切換到「Available Packages」 標籤頁,開始裝目標 SDK。點選預設的網址,可以看到目前可用的目標 SDK、文件、Add-On,要完成本書中範例,只要勾選對應版本的目標 SDK、文件、Add-On,然後按下「Install Selected」按鈕即可。
在下一個視窗中可確認剛勾選預備要安裝的 SDK 組件。勾選「Accept All」選項後,按下「Install Accepted」按鈕即開始自動下載並安裝。
在安裝完成後,切換到「Installed Packages」標籤頁,可以看到剛剛勾選的 SDK 組件都已經安裝到開發環境中。
刪除 SDK 組件
經過幾次 Android 版本升級後,我們的列表中可能會包含許多舊版本的 SDK。這時可以透過選取列表中的組件,點選下方的「Delete...」按鈕來刪除這些過時的組件。我們隨時還可以回到管理工具的「Available Packages」標籤頁,把組件下載回來。
安裝好 SDK 組件後,我們就可以開始來熟悉開發環境了。
OpenProject
開啟現有專案
開啟專案
我們回到 Eclipse 環境來。在螢幕上方的選單列上,選擇「File->New->Project」,會彈出「New Project」對話視窗。 Eclipse 是通用的編輯環境,可根據你所安裝的不同擴充套件而支援許多種類的專案。 點擊 「Android」 資料夾下的「Android Project」,會開啟「New Android Project」對話視窗。
我們將開啟「SDK 組件」中提供的 ApiDemos 範例。在「New Android Project」對話視窗中,點選 "Browse..."按鈕以選擇「開啟已經存在的專案」(Create project from existing source)。我們在此選擇位於「android_sdk/platforms/android-2.0/samples」目錄中的 Android 應用程式專案 (android_sdk/platforms/android-2.0/samples/ApiDemos)。
當我們選擇了現存的範例程式專案時,「New Android Project」對話視窗中的諸如專案名稱(Project Name)與屬性等內容都將被自動填寫好。這時我們可以按下 「Finish」按鈕,完成從現存專案新增專案到 Eclipse 環境的動作。
匯入專案
如果你的程式專案已位於工作環境(WorkSpace)資料夾下,想使用上述方法開啟專案時,會得到欲開啟的資料夾已在工作目錄下的警告。因此我們得用另一個方法:匯入專案。
在螢幕上方的選單列上,選擇「File->Import」選項,會跳出「Import」視窗。選擇「General->Existing Projects into WorkSpace」項目,然後按下「Next」按鈕帶到新一個畫面。在「Select Root Directory」欄位旁,按下右方的「Browse...」按鈕,選擇對應的專案。選擇好後,按下「Finish」按鈕完成從現存在工作環境(WorkSpace)資料夾下的專案匯入到 Eclipse 環境的動作。
不同 SDK 版本的範例專案會放在「android_sdk/platforms/android-版本/samples」目錄中,請自行根據要開發的版本來選擇範例。
修復專案
完成新增程式專案到 Eclipse 後,我們可以在左側的「Package Explorer」中找到我們新增的專案。
如果發現開啟後的資料夾圖示上有個小小的黃色驚嘆號,表示這個專案匯入後還有些問題,我們可以使用ADT內建的功能來試著修復專案屬性。 在「Package Explorer」的 「ApiDemos」 專案檔案夾圖示上點選右鍵,從「Android Tools」選單中選擇「修復專案屬性」(Fix Project Properties)。 (Android Tools->Fix Project Properties)
如果發現開啟後的資料夾圖示上有個小小的紅色叉號,表示這個專案開啟/匯入後遇到了無法編譯的問題。最常見的也是與無法正常生成「gen」目錄相關的問題。一般簡單的解決方式是打開專案中任一 XML 檔案(如AndroidManifest.xml 或是「res」目錄下附檔名為 .xml 的檔案),改變一下內容(如在檔案中多按一個空格)後存檔,這時開發工具會自動編譯生成「gen」目錄中新的內容。這樣無法編譯的問題往往就解決了。如何修改 XML 檔案在後面章節中會提到。
切換 SDK 版本
Android 在 SDK 1.5 版之後引入了支援多個版本 SDK 與模擬器的新特性,讓我們得以透過修改屬性設定畫面的設定,來切換用來編譯與運行這些專案的目標 SDK 版本。
在「Navigator」的「ApiDemos」專案檔案夾圖示上點選右鍵,選擇「properties」選項,會開啟專案屬性設定畫面。
在設定畫面中先選擇左方的 Android 標籤,選擇後會出現可勾選的「Project Build Target」選單。
在選單中選擇適當的目標版本,選擇好之後按下 OK 結束設定畫面,這時專案就已經切換成可使用目標版本編譯的狀態了。
PlayEmulator
操作 Android 虛擬機器
使用 Android 虛擬機器
我們已經透過「Eclipse」開發環境,開啟了「ApiDemos」專案。本章將講解如何設定和操作 Android 虛擬機器。
設定 Android 虛擬機器
現在我們還不忙著開始寫程式,先花點時間,來看看怎麼在開發環境中,透過「Android 虛擬機器」來執行應用程式吧。
由於在剛開始開發時,我們手邊並不一定已擁有 Android 設備。因此 Android 開發工具亦提供了相當強大的模擬器,能讓我們自由配置,模擬各種硬體規格的設備。在 Android 中一律把 Android 模擬器稱作「Android 虛擬機器」(Android Virtual Device),簡寫為「AVD」。
「Android 軟體開發套件」(SDK) 1.5 以上的版本提供了支援不同目標版本虛擬機器的功能,在使用虛擬機器之前,必須先建立一個虛擬機器後才可在 Eclipse 開發環境中使用。
SDK 中提供了一個「android」命令行工具(在 android-sdk/tools 中),可以用來建立新專案或是管理虛擬機器。在此我們使用「android」命令行工具來新建立一個虛擬機器。
列出虛擬機器類型
首先,把「android-sdk/tools 」目錄加入系統路徑,我們以後就可以在任何地方使用「android-sdk/tools 」目錄下的各種命令。
在 Windows 2000、XP、2003、Windows 7 這些作業系統裡,點選「控制台 > 系統 > 進階 > 環境變數」。在「系統變數(S)」欄中,選取「PATH」變數名稱後,再點選「編輯(I)」按鈕。
再此假設您安裝 Android SDK 的路徑是「C:\android-sdk\tools」,接著在彈出的視窗中將「;C:\android-sdk\tools」(注意要以分號隔開)這字串添在原本的字串之後,按下確定後重新啟動作業系統。
重開系統後選擇「開始 > 執行」,在彈出的輸入框中輸入「cmd」,即可開啟命令行工具並繼續以下的動作。
或是您也可以直接打開命令行,進入「android-sdk/tools 」目錄,輸入以下命令:
$ android list targets
在沒有將 Android SDK 加入路徑的情況下,在 Linux 或 Mac 環境中要輸入
$ ./android list targets
螢幕上會列出所有支援的虛擬機器類型
$ android list targets
Available Android targets:
id: 1
Name: Android 1.1
Type: Platform
API level: 2
Skins: HVGA (default), HVGA-L, HVGA-P, QVGA-L, QVGA-P
id: 2
Name: Android 1.5
Type: Platform
API level: 3
Skins: HVGA (default), HVGA-L, HVGA-P, QVGA-L, QVGA-P
id: 3
Name: Google APIs
Type: Add-On
Vendor: Google Inc.
Description: Android + Google APIs
Based on Android 1.5 (API level 3)
Libraries:
* com.google.android.maps (maps.jar)
API for Google Maps
Skins: HVGA (default), HVGA-L, QVGA-P, HVGA-P, QVGA-L
在這邊列出了三種虛擬機器類型。分別是編號(id)為 1、2 的 Android 1.1、1.5 虛擬機器,與編號(id)為 3 的「Google APIs」,Google 把自己提供的應用程式(如 Google Map)放在「Google APIs」這個虛擬機器類型中,因此要開發 Google Map 等 Google 專屬應用程式時,就必須先建立編號 3 這類型的虛擬機器,稍後才能在適當的虛擬機器上作驗證。
建立虛擬機器
我們現在來建立一個基本的 Android SDK 2.0 虛擬機器。
在命令行中輸入以下命令:
$ android create avd --target 1 --name eclair
這段命令的意思是:使用「android create avd」命令來建立一個新的虛擬機器,「 --target 1」參數的意思是這個虛擬機器使用 id 為 1 的 SDK 套件(Android 1.5),「--name eclair」參數的意思是將這個建立的虛擬機器命名為「eclair」。
產生的結果如下
$ android create avd --target 1 --name eclair
Android 2.0 is a basic Android platform.
Do you wish to create a custom hardware profile [no]
Created AVD 'cupcake' based on Android 2.0
列出已建立的虛擬機器
我們可以使用 「Android」命令行工具提供的「list avd」命令,來列出所有我們已經建立的模擬器。
在命令行中輸入以下命令:
$ android list avd
產生的結果如下:
$ android list avd
Available Android Virtual Devices:
Name: eclair
Path: /Users/mac/.android/avd/cupcake.avd
Target: Android 2.0 (API level 5)
Skin: HVGA
使用「 android list avd」命令看到有輸出,即表示已成功建立虛擬機器,可以回到 Eclipse 環境來,設定執行應用程式專案所需的環境參數了。
設定環境參數
要執行 ApiDemos 程式前,我們得在開發環境中,事先設定好一些用來執行 ApiDemos 程式的環境參數。 以後使用其他程式專案時,我們也能用同樣的方式,讓這些程式在我們的開發環境中運行。
首先,我們透過選單列上的「Run」(執行)選單,選擇「開啟執行參數設定」(Run-> Debug Configurations...) 進入運行環境參數設定畫面。
進入設定畫面後,在視窗左側會有一整排 Eclipse 支援的運行設定,我們從中找到 "Android Application"(Android 應用程式)項目,按下滑鼠右鍵,點選 "New"(新增)選項。
選擇 「New」 選項後,在「Android Application」項目下方會多出一筆執行項目。
我們可以在 Name 欄位上輸入一個代表這個環境參數的名稱,在此我們輸入與專案名稱相同的「ApiDemos」。
在「Project」欄位右方,點選「Browse...」按鈕,開啟「專案選擇」(Project Selection)視窗,選擇「ApiDemos」專案並點選「OK」按鈕,以選擇要執行的專案。
在 「Launch Action」 選單中,確認預設選擇的是「Launch Default Activity」。
至此我們就完成了模擬器環境參數的設定。 點選右下角的「Debug」按鈕,Eclipse 就會啟動 Android 虛擬機器。
小技巧:
在選單列中,也可以選擇設定「Run Configuration...」選項。這時我們得到的是一個幾乎完全相同的環境參數設定畫面,只是右下角的「Debug」按鈕變成了「Run」按鈕。「Debug」與「Run」模式的環境參數設定可以共用,差別在於「Debug」模式下可以使用在之後章節中會介紹的 logd,來顯示一些開發時所需的額外訊息。
再次啟動 Android 虛擬機器
當我們設定好之後,以後碰到要再次啟動虛擬機器的情況時,只要在螢幕左上角的「Debug」或「Run」圖示右側小箭頭上按一下,從彈出的選單中選擇剛剛設定的環境參數名稱,虛擬機器即開始執行,並安裝好我們所指定的專案應用程式。
操作虛擬機器
改變虛擬機器外觀
在建立虛擬機器的時候,我們可以透過「skin」欄位來選擇預設的虛擬機器外觀。「skin」欄位中會列出目標(Target)版本支援的所有外觀。「HVGA」(解析度 480x320)、「QVGA」(解析度 320x240)等分別代表著各種不同畫面的解析度
在命令列上執行「android list targets」命令後,我們可以看到螢幕上列出所有支援的模擬器類型。舉我們剛才建立過的(id 1)虛擬機器為例,列出訊息如下:
id: 1
Name: Android 2.0
Type: Platform
API level: 5
Skins: HVGA (default), QVGA, WQVGA400, WQVGA432, WVGA800, WVGA854
其中 Skins 欄位中會列出所有支援的模擬器佈景,預設有多種畫面配置選項可選擇。
要建立「QVGA」模式的模擬器,則在前一節「android create avd」命令後,附加上「--skin QVGA」選項即可。要將預設的「HVGA 直式」顯示改為橫式,則可以透過使用快速鍵,直接切換螢幕來達成。
切換螢幕方向
在 Windows 作業系統上按下 「Ctrl」和「F12」鍵 ,或是在 Mac OS X 作業系統上同時按下「fn」 和「7」鍵,螢幕就會從預設的直式顯示改成橫式顯示,再按一次則切換回原來的直式顯示。
移除程式
我們已經順利地啟動了虛擬機器,那麼,該怎麼移除安裝到虛擬機器上的程式哩?
Android SDK 中提供一個 adb (Android Debugger) 命令行工具 (在 android-sdk/tools 中),我們可以用裡面的 shell 工具連上虛擬機器來移除應用程式。在某些平台上,這些動作可能需要擁有 root 權限才能執行。
首先打開命令列,啟動 adb shell
$ adb shell
接著切換到 data/app 目錄中
$ cd data/app/
使用 ls 命令(等同 windows 上命令行的 dir 命令)來檢視檔案列表
# ls
com.example.android.apis.apk
接著使用 rm 命令來刪除 ApiDemos 應用程式
# rm com.example.android.apis.apk
# ls
移除虛擬機器
我們可以使用「android list avd」命令來列出所有的虛擬機器
$ android list avd
Available Android Virtual Devices:
Name: eclair
Path: /Users/mac/.android/avd/cupcake.avd
Target: Android 2.0 (API level 5)
Skin: HVGA
表示現在系統中有一個名為 eclair 的虛擬機器。 我們可以使用「android delete avd --name eclair」命令來刪除名稱為「eclair」的虛擬機器。
$ android delete avd --name eclair
AVD 'eclair' deleted.
刪除後再次執行「android list avd」命令,得到的結果為
$ android list avd
Available Android Virtual Devices:
表示系統中已經不存在任何模擬器,我們真的已經將虛擬機器刪除了。
ReadSource
建立一個 Android 程式
在前幾章我們已經學到怎麼開啟現有的專案,也導覽過了整個模擬器的設定流程。 現在我們從設計一個簡單實用的身高體重指數計算(BMI)應用程式開始,學習設計一個 Android 應用程式所需的基礎。
維基百科上這麼介紹 BMI 身高體重指數:
身高體重指數(又稱身體質量指數,英文為Body Mass Index,簡稱BMI)是一個計算值。
...當我們需要比較及
分析
定性数据统计分析pdf 销售业绩分析模板 建筑结构震害分析 销售进度分析表 京东商城竞争战略分析
一個人的體重對於不同高度的人所帶來的健康影響時,
BMI值是一個中立而可靠的指標。
簡而言之,我們要設計的程式就是允許輸入身高體重,按下「計算 BMI」鍵後就在螢幕上顯示 BMI 值,並彈出「你應該節食囉」、或「你應該多吃點」...等健康建議。健康建議的判斷: 只要 BMI 值超過 「25」 時就算偏胖、BMI 值低於 「20」 就算偏瘦。 判斷寫得很簡單。畢竟我們要學習的關鍵知識,不是在於 BMI 值的算法或健康建議的內容,而是在於 Android 程式的運作方式。
參考資源 http://zh.wikipedia.org/wiki/身高體重指數
我們這就先從建立一個新的程式專案開始吧。
建立新程式專案
首先,我們照前面章節的教學,建立一個新的程式專案。並將新專案名稱命名為 BMI。 在「內容」欄裡,我們選擇「在工作區域中建立新專案」(Create new project in workspace)。 這時,如果在"選擇欄"取消掉勾選「使用預設目錄」(Use default location) 選項,我們就可以切換儲存專案的資料夾。 大部分的時候我們並不需去改動這個選項,而是直接使用預設的資料夾。
前面章節中都是開啟現有的專案,因此那些專案屬性 (Properties) 等內容都被自動填寫好了。這章中要從無到有新建一個專案,因此我們必須自行填寫專案相關的屬性。
在此對"New Android Project" 對話框中出現的這些欄位作些簡單的說明:
名稱
描述
Project Name
包含這個項目的資料夾的名稱
Application Name
顯示在應用程式上的標題
Package Name
套件(Package)名稱,JAVA 的習慣是用套件名稱來區分不同的類別(class)。依照專案的不同,我們會起不同的路徑名稱。
Create Activity
使否建立這個是項目的主要類別,勾選後可以指定這個類別的名稱。這個類別是一個 Activity 類別的子類別。我們可以在「Activity」中啟動程式和控制程式流程,或是根據需要控制螢幕、界面。
Build Target
選擇用來編譯專案的 SDK 版本。 當選定了 Build Target 後,開發工具會在 Min SDK Version 欄位中自動填入對應的值
Min SDK Version
本應用程式所支援的最低 SDK 版本代號。
我們在欄位中分別填入以下的值:
名稱
值
Project Name
BMI
Application Name
BMI
Package Name
com.demo.android.bmi
Create Activity
Bmi
Min SDK Version
5(自動填入)
填好值後按下「Finish」 按鈕,就建立好新專案了。
注意 Package Name 的設定,必須至少由兩個部分所構成,例如:com.android。「Activity Name」是指定用來產生預設 java 程式碼的文件名稱,與文件中預設 Activity 類別 (class) 的名稱。依照 java 語言的命名習慣,「Activity Name」最好採用開頭大寫的形式。
回到 Eclipse 主畫面,我們注意到在左側 Package Explorer 視窗中已順利新增加了一個 BMI 目錄。
程式專案架構
乍看之下,Android 插件已幫我們建立了不少檔案。 檢視新建立的 BMI 檔案夾中的內容,我們可以將一個 Android 應用程式基本的檔案結構歸納成如下:
我們來看看 Android 應用程式的基本檔案結構,以及各自所負責的內容。
src/ 原始碼(source)目錄
src 目錄中包含了一個 Android 應用程式中所需的各個程式碼檔案。這些檔案被包在對應 package 的子目錄下。(如本章的 BMI 例子中,子目錄指的就是 /src/com/demo/android/bmi/)
src 目錄中比較關鍵的程式有:
1. Bmi.java 這個檔案的檔名會隨著你在建立一個新的程式專案畫面中所填入「Create Activity」欄位值的不同而改變。這是新程式專案中的主要程式區塊。我們開發 Android 程式的多數時間,都是在 src 目錄下和 Android 打交道。
gen/ 自動生成(Generate)目錄
gen 目錄中存放所有自動生成的檔案。
gen 目錄中最關鍵的程式就是 R.java 檔。
1. R.java 這個檔是自動產生的。會由 ADT 插件自動根據你放入 res 目錄的 XML 描述文件、圖像等資源,同步更新修改 'R.java' 這個中介檔案。所有的 Android 程式中都會有以 R.java 為名的這個檔案,你完全不需要,也應避免手工修改 R.java 這個檔案。
R.java 中自動產生的 「R」 類別就像是個字典一樣,包含了使用者介面、圖像、字串等各式的資源與相應的編號(id)。Android 應用程式中很多時候會需要透過 R 類別調用資源。 編譯時編譯器也會查看這個資源列表,沒有使用到的資源就不會編譯進去,為手機應用程式節省不必要佔用的空間。
res/ 資源(Resource)目錄
「res」 目錄中存放所有程式中用到的資源檔案。"資源檔案"指的是資料檔案,或編譯時會被轉換成程式一部分的 XML 描述檔。Android 針對放在 「res」 目錄下的不同子目錄的資源,會有各自不同處理方式。因此我們寫程式時,最好能搞清楚各目錄下分別可放置的內容。
res/ 中的程式:
3. layout/ 版面配置(layout)目錄 「layout」目錄包含所有使用 XML 格式的介面描述檔。「layout」 中的 XML 介面描述檔就像寫網頁時用到的 HTML 檔案一樣,用來描述螢幕上的版面編排與使用的介面元件。XML 介面描述檔描述的內容可以是整張螢幕,也可以只描述一部分的介面(例如描述用來產生對話框的介面元件)。
雖然你也能直接通過 Java 來建立使用者介面,不過透過 XML 描述檔來建立使用者介面相對更簡單,架構也更清晰,以後維護時更容易釐清問題。要使用這些介面元件,應透過 「R.java」 檔中自動產生的 「R」 類別來調用。
4. values/ 參數值(value)目錄 「values」 目錄包含所有使用 XML 格式的參數值描述檔,可以在此添加一些額外的資源如字串(很常用)、顏色、風格等。使用時也是透過 「R」 類別來調用。
Android 功能清單
5. AndroidManifest.xml
「AndroidManifest.xml」 是 Android 程式的功能清單,應用程式在這裡列出該程式所提供的功能。當應用程式開啟時,會提供諸如內容提供者(ContentProvider)、處理的資料類型、實際運行的類別、跨應用程式的資訊等等訊息。 你可以在此指定你的應用程式會使用到的服務(諸如電話功能、網路功能、GPS功能等)。 當你新增一個頁面行為類別 (Activity) 時,你也需要先在此註冊這個新增的 Activity 類別後,才能順利調用。
AndroidUI
描述使用者介面
將一份創意落實到可執行的應用程式,背後需要的是從閱讀與寫作程式碼中累積的經驗,並有堅持理念、直到完成的耐心。
表達使用者介面
我們可以先用前幾章教的方法設定並執行模擬器,看看模擬器運作後的結果。
我們看到一個文字欄位,上面有一串文字 「Hello World, Bmi!」。這就是 Android 預設程式架構的範例囉。
由於才剛開始實際接觸到 Android 應用程式,我們先從簡單的開始:這一節中,我們的目標是將 「Hello World, Bmi!」 換成別的文字。
那麼,「Hello World, Bmi!」,這串字串藏在哪裡呢?
先打開 「res/layout/main.xml」
1
2
7
12
原來「Hello World, Bmi!」字串就藏在「res/layout/main.xml」這個檔案的第 10 行中。我們只要簡單地將第 10 行修改成如下
android:text="Hello World, Bmi!”
再執行一次模擬器,就可以得到一個相似的應用程式,只是內文變成了我們剛剛修改的內容。
既然找到了「Hello World, Bmi!」字串,我們就試著將「android:text」屬性值從「Hello World, Bmi!」改成「哈囉,BMI」,然後執行看看吧。
android:text="哈囉,BMI"
結果發現 Android 模擬器中文嘛也通,字型也相當漂亮。
要開始學習 Android 應用程式確實很簡單吧?不過為了顯示「Hello World, Bmi」,也用到了許多程式碼。到底這些程式碼有什麼含意呢?
我們馬上來學習 「main.xml」這個 XML 介面描述檔的內涵吧。
Android 平台裡,使用者介面都是透過 ViewGroup 或 View 類別來顯示。ViewGroup 和 View 是 Android 平台上最基本的使用者介面表達單元。我們可以透過程式直接呼叫的方法,調用描繪使用者介面,將螢幕上顯示的介面元素,與構成應用程式主體的程式邏輯,混合在一起編寫。或是,也可以將介面顯示與程式邏輯分離,照著 Android 提供的這個較優雅的方式,使用 XML 描述檔,來描述介面元件的組織。
講解
我們看到的「Hello World, Bmi」就包含在「main.xml」 這個檔案中。 接著,我們就直接分部份來講解這個「main.xml」 檔案裡的內容:
第 1 行
XML (Extensible Markup Language) 是一種標記描述語言,不管是語法還是看起來的樣子,都相當類似網頁所使用的 HTML 標記語言。XML 被廣泛地運用在 Java 程式的設定中。「main.xml」 文件裡,第一行是每個 XML 描述檔固定的開頭內容,用來指示這個文字檔案是以 XML格式描述的。
第 2, 6 與 12 行
接著我們看到第一個標籤,與 HTML 網頁標籤相當類似。
"線性版面配置"(LinearLayout)標籤,使用了兩個「LinearLayout」標籤,來表示一個介面元件的區塊。後頭的標籤前加上一個「/」符號來表示結束標籤。"線性版面配置" 所指的是包含在 「LinearLayout」 標籤中,所有元件的配置方式,是將一個接一個元件由上而下排隊排下來的意思。
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns 開頭的這串敘述,是用來宣告這個 XML 描述檔案的的名稱空間(NameSpace),後面接的URL(網址),表示這個描述檔案會參照到 Android 名稱空間提供的定義。 所有 Android 版面配置檔案的最外層標籤中,都必須包含這個屬性。
注意標籤需要兩兩對稱。一個標籤「
」在一串敘述的前頭,另一個標籤「
」在敘述的末尾。 如果你修改過的標籤沒有閉合(忘了加 <、/、> 等符號),Eclipse 畫面上也會出現小小的警示符號來提醒你。
第 3-5 行
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
這些包含在「
」標籤中的敘述被稱為「LinearLayout」標籤的「屬性」。Android 應用程式在 layout 目錄中的標籤,大多數的屬性前都有一個「android:」前綴。同一個介面元件的屬性之間,是以空白做區隔,因此事實上你也能將多個屬性寫在同一行。當然,將屬性排成多行更易於閱讀。我們應該保持這個好習慣。
介面元件都有許多共同的屬性,例如介面元件的長,寬度設定屬性。Android 介面元件的寬度、長度設定屬性分別叫做「android:layout_width」、「android:layout_height」。兩個都設定為 「fill_parent」參數值。「fill_parent」 如其名,所表達的的意思就是"填滿整個上層元件"。預設 LinearLayout 介面元件就會佔滿整個螢幕空間。
介面元件彼此間也會有一些不同的屬性,例如 「LinearLayout」(線性版面配置) 標籤的「android:orientation」(版面走向) 屬性。在此填入 「vertical」 (垂直)屬性值,表示這個介面的版面配置方式是從上而下垂直地排列其內含的介面元件。
「android.view.ViewGroup」 是各種佈局配置(layout)和視圖(View)元件的基礎類別。常見的實現有
LinearLayout(線性版面配置)、FrameLayout(框架版面配置)、TableLayout(表格版面配置)、AbsoluteLayout(絕對位置版面配置)、RelativeLayout(相對位置版面配置)等。
雖然有這麼多種版面配置方式可以選用,但大多數的應用程式並不需特地去改變預設的 LinearLayout 的配置,只要專注在其中填入需要的介面元件即可。所以從第 7 行之後的內容才是一般應用程式開發時較常修改之處。
第 7 和 11 行
TextView (文字檢視)是我們看到的第一個熟悉的介面元件。其作用是顯示文字到螢幕上。你可能注意到這個標籤結尾使用了 「/>」 符號。 「/>」符號表示這個XML敘述中沒有內文,亦即此介面元件描述中不再包含其他介面元件,也表示這個介面元件就是這個螢幕中最小的組成單元了。
第 8-10 行
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, bmi"
我們來看看 TextView 介面元件中包含了哪些屬性。
「android: layout_width」和「android:layout_height」我們剛剛已經學過了,分別代表寬度跟長度。「android: layout_width」 的 「fill_parent」 參數值表示寬度填滿整個上層介面元件(即 LinearLayout 介面元件)。「android:layout_height」則是用上一個新的參數值「wrap_content」(包住內容),亦即隨著文字欄位行數的不同而改變這個介面元件的高度。最後的 「android:text」 屬性則是 TextView 介面元件的主要屬性,亦即文字欄位中顯示的文字內容。至於「@string/hello 」這段字串所代表的意義,馬上會接著在後面章節說明。我們現在已知道是:只要將「android:text」屬性內容替換成我們想要文字,在預覽畫面或在模擬器中就會顯示對應的文字。
將以上的 XML 描述綜合起來,我們就可以得知「main.xml」 想表達的介面。
BmiUI
設計使用者介面
視圖(View)
軟體設計的過程中,常常會遇到需要頻繁修改使用者介面的情境。改著改著程式設計師們就累積起了一些經驗,也歸納出了許多應對之道。如著名的 MVC(Model-View-Controller) 模式。Google Android 為我們考慮了介面修改問題。Android 為了單純化介面修改方式,採用了目前比較流行的解決
方案
气瓶 现场处置方案 .pdf 气瓶 现场处置方案 .doc 见习基地管理方案.doc 关于群访事件的化解方案 建筑工地扬尘治理专项方案下载
--即將介面描述部份的程式碼,抽取到程式外部的 XML 描述文件中。
我們在前面的過程裡已經學到,如何在 Android 應用程式中替換 TextView 介面元件所顯示的純文字內容。那麼...這個經驗能不能直接用到 BMI 應用程式的設計上呢?
我們先回過頭來想想, BMI 應用程式最少應該需要什麼些什麼元件。
為了輸入 BMI 程式所需的身高體重值,大致上我們需要兩個 TextView 元件用來提示填入身高體重數字,另外也需要兩個文字輸入欄位用來填入身高體重數字。我們還需要一個按鈕來開始計算,而計算完也需要一個 TextView 元件來顯示計算結果。於是初版的 BMI 應用程式介面的樣子就浮現出來了。
查閱文件
話說回來,我們從哪得知各種可用的介面元件呢?其中一個方法是查閱文件。
Android 文件網站上找到各種可用介面元件列表。
http://developer.android.com/guide/tutorials/views/index.html
例如我們想查看 EditText 的內容,我們可以點進 EditText 連結查看其內容。 http://developer.android.com/reference/android/widget/EditText.html
你會看到一個詳細地驚人的網頁。
這邊舉其中常用到的 EditText 介面元件為例。EditText 介面元件的作用就是提供一個文字輸入欄位。EditText 繼承自另一個叫 TextView 的介面元件,TextView 介面元件的作用是提供文字顯示,所以 EditText 介面元件也擁有所有 TextView 介面元件的特性。 此外,文件中你也可以查找到 EditText 欄位的一些特殊屬性,如 「android:numeric="integer"」(僅允許輸入整數數字)、「android:phoneNumber="true"」(僅允許輸入電話號碼),或「android:autoLink="all"」(自動將文字轉成超連結)。 例如要限制 EditText 中只允許輸入數字的話,我們可以在 XML 描述檔中,透過將 EditText 的參數「android:numeric」 指定為 「true」,以限制使用者只能在 EditText 文字欄位中輸入數字。
離線文件
當你處於沒有網路連接的情況下時,也可以找到 Android 文件參考。 在下載了 android-sdk 後,將之解壓縮,你可以在「android-sdk/docs」 目錄中 (android_sdk/docs/reference/view-gallery.html) ,找到一份與線上文件相同的文件參考。
開始設計
我們從實例來開始,定義一個基本 BMI 程式所需的身高(Height)輸入欄位,就會用到 EditText,與 TextView 介面元件,其描述如下:
1
6
可以看到 EditText 介面元件描述的基本的組成與 TextView 介面元件相似,都用到了「android:layout_width」與「android:layout_height」屬性。 另外,指定的另外兩個屬性「android:numeric」、「android:text」則是 EditText 介面元件的特別屬性。「android:text」屬性是繼承自 TextView 介面元件的屬性。
android:numeric="integer"
android:text=""
將 「android:numeric」 指定為 「integer」,可以限制使用者只能在 EditText 文字欄位中輸入整數數字。「android:text」屬性則是指定 EditText 介面元件預設顯示的文字(數字)。
我們再來看看 Button (按鈕)介面元件
Button 介面元件同樣有 「android:layout_width」與「android:layout_height」屬性,另外一個「android:text」屬性則用來顯示按鈕上的文字。
整合
我們這就從文件中挑出我們需要的 TextView(文字檢視)、EditText(編輯文字)、Button(按鈕) 三種介面元件,照前面的設計擺進 LinearLayout (線性版面配置)元件中。
完整的「main.xml」介面描述檔如下:
我們可以啟動模擬器檢視執行結果。或是在頁面標籤下選擇「Layout」標籤,來預覽頁面配置。
啟動模擬器之後,模擬器畫面上出現了兩個輸入欄位。欄位上方分別標示著「身高 (cm)」、「體重 (kg)」。在兩個輸入欄位下方,是一個標示著「計算 BMI 值」的按鈕。 當你在欄位中試著輸入文字或數字(你可以直接用電腦鍵盤輸入,或按著模擬器上的虛擬鍵盤輸入)時,你也會發現,正 XML 描述檔的描述中對兩個 EditText 欄位所規定的,欄位中只能輸入數字。
我們在上面XML描述檔中定義的最後兩個 TextView 介面元件,由於並未替這兩個介面元件指定「android:text」屬性,所以在螢幕上並未顯示。這兩個介面元件在後面章節中會派上用場。
革命的路還長
高興了沒多久,你發現按下"計算 BMI 值" 按鈕後,應用程式完全沒反應。
這是正常的,因為我們還沒處理從介面輸入取得身高體重、將數值導入 BMI 計算方式、將結果輸出到螢幕上...等等 BMI 應用程式的關鍵內容。 不過在進入了解程式流程之前,我們還有一個「android:id」屬性尚未解釋哩。 接著我們將透過講解「android:id」屬性,來進一步了解 Android UI。
視覺化的介面開發工具
目前的 ADT 版本提供了預覽介面的功能,但尚未提供方便地視覺化拖拉介面元件的開發工具。以後也許 ADT 會加入完整的 GUI 拖拉設計工具。
但在 ADT 加入完整的 GUI 拖拉設計工具之前,已經有人寫出來了對應 Android 的 GUI 拖拉設計工具,可供使用。
DroidDraw - Android GUI 拖拉設計工具 http://code.google.com/p/droiddraw/
XmlR
存取識別符號
在上一章談了 XML描述檔中介面元件的各種「android:」開頭的屬性。要使用一個介面元件,第一件事就是定義出介面描述檔。大部分的介面元件(如 LinearLayout、TextView)不需要在程式中作後續處理,因此可以直接描述(例如我們前面定義過的 TextView 文字顯示元件「身高(cm)」)。但對於那些將在程式中被參考(reference)到的介面元件(如按鈕 Button、文字輸入欄位 EditText),我們需要先行在 XML描述檔中,定義該介面元件的「android:id」識別符號屬性。這麼一來在程式碼中我們要操作這個介面元件(取出欄位中輸入的資料、取得按下按鈕事件...)時,就能透過「android:id」識別符號來調用這個介面元件。
前面章節提過,寫作時最好將 XML 描述檔屬性分行列出,以易於閱讀(增加可讀性)。而我們的範例卻將 android:id 屬性直接擺在 EditText 標籤後。其實這麼做同樣是基於易於閱讀的考量。當然你也可以將「android:id」屬性分行列出,或是將「android:id」屬性放在屬性列表的中間或最後頭,這些作法都是允許的,本書中一律採用將 android:id 屬性直接擺在介面元件標籤後的寫法。
android:id 屬性的內容長得比較特別:
@+id/height
「height」是這個介面元件的 android:id。以後的程式中會使用「R.id.height」來取得這個介面元件。「@+id」 的意思是我們可以通過這個識別符號來控制所對應的介面元件,「R」類別會自動配置一個位址給這個介面元件。 「R」類別的內容則可以透過查看 R.java 得知。
XML 描述檔與 R.java 檔
在 Android 系統中,我們使用 XML 來定義 UI。但是有些稍微有經驗的開發者可能會有疑問:
「用 XML 來描述介面固然方便,但是對於手機程式來說,直接用 XML 檔案是不是太占空間了?」。
沒錯,如果 Android 是直接使用 XML 來儲存介面描述到手機上的話,一定會佔用比起現在大的多的檔案空間。解決的方法是Android 並不直接使用 XML 檔案,而是透過 Android 開發工具,自動將 XML 描述檔轉換成資源檔案。一旦應用程式要操作某個介面元件,或是使用任何種類的資源(字串、圖片、圖示、音效...),都使用索引來查詢。
當你建立一個 BMI 新專案,打開位於 「gen/com/demo/android/bmi」 目錄下的 「R.java」檔,你可以看到如下的程式碼:
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package com.demo.android.bmi;
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040000;
}
}
在照著前一章新增了 XML 描述後,再次打開打開 「gen/com/demo/android/bmi」 目錄下的 「R.java」 檔 ,你可以看到如下的程式碼:
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package com.demo.android.bmi;
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class id {
public static final int height=0x7f050000;
public static final int result=0x7f050003;
public static final int submit=0x7f050002;
public static final int suggest=0x7f050004;
public static final int weight=0x7f050001;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040000;
}
}
我們看到在 R.java 檔案中,分別有 attr (屬性)、drawable (圖片、圖示)、id (識別符號)、layout (介面描述)、string (文字) 這幾種資源型態,就 XML 描述檔中的 id 來說,開發工具會根據 XML 描述檔中指定的 id,生成對應的資源,並自動指定一個位址。
Google 官方文件是這麼解釋「R.java」檔案的作用的:
A project's R.java file is an index into all the resources defined in the file.
You use this class in your source code as a sort of short-hand way to refer to
resources you've included in your project. This is particularly powerful with the
code-completion features of IDEs like Eclipse because it lets you quickly and
interactively locate the specific reference you're looking for.
The important thing to notice for now is the inner class named "layout", and
its member field "main". The Eclipse plugin noticed that you added a new XML
layout file and then regenerated this R.java file. As you add other resources to
your projects you'll see R.java change to keep up.
有了「R.java」做中介,在 XML 描述檔中,我們可以透過
@[類型]/[識別符號]
這樣的語法來為某個介面元件提供識別符號,以供程式控制。
例如,我們可以用 「@+id/height」來為對應供輸入身高數字的 EditText 元件提供識別符號。
將字串抽離 XML
當我們在 res 資料夾中新增各種一個 XML 檔案,或是一張圖片時,開發工具會從 res 資料夾中蒐集,並將各種資源彙整成一個索引,自動產生出 R.java 檔。
透過這個特性,我們可以進一步加工我們的 XML 描述檔,讓介面更易於維護。
開啟 res/values/strings.xml
原始的內容為
BMI
裡面只定義了一個字串「app_name」,用來表示應用程式名稱(在之後講解 AndroidManifest.xml 檔案時將會用到)。 我們看到表示字串的格式為
文字敘述
我們將上一章中的敘述抽取出來,整理進 strings.xml 檔案。
完整的 strings.xml 檔案如下:
BMI
身高 (cm)
體重 (kg)
計算 BMI 值
你的 BMI 值是
在 strings.xml 檔案中,我們在原本的 app_name 字串外,自行定義了另外幾個字串。如果我們再次開啟 「R.java」檔,我們會發現檔案中的 string 類別中也自動索引了上面定義好的字串:
public static final class string {
public static final int app_name=0x7f040000;
public static final int bmi_btn=0x7f040003;
public static final int bmi_result=0x7f040004;
public static final int bmi_height=0x7f040001;
public static final int bmi_weight=0x7f040002;
}
接著,我們把這些字串應用到之前定義好的 XML 描述檔中。透過使用
@string/[識別符號]
這樣存取 string 類型的格式,來取代 main.xml 檔案中原本寫死的文字敘述。
完整的程式碼如下:
再次運行 Android 虛擬機器,我們看到與前一節完全相同的介面。但就介面而言,透過將描述字串統一集中在 「string.xml」 中,我們以後要修改介面時更有彈性了。
至此我們已經完成了 BMI 應用程式負責「顯示 (View)」的部份。
新增 XML 檔案
我們在前面都只修改到開發工具幫我們產生的檔案, 而事實上,我們所有在 「res」 目錄中所做的修改,開發工具都會自動搜尋,將之整理到「R.java」中。因此我們也可以在「src/values」中建立自己的文字描述檔案。
我們這就在「res/values」目錄中建立一個 「advice.xml」檔,裡面將包含 BMI 程式算出 BMI 值後將給予的建議文字,完整的檔案如下:
你該多吃點
體型很棒喔
你該節食了
打開「R.java」檔,我們發現「advice_light」、「advice_average」、「advice_heavy」也已整理進「R.java」檔的索引中,以供程式調用。
Android 中所有的資源檔案(圖片、XML等)命名都必須使用英文小寫,檔名中間只允許加上底線「」符號。其他的檔名都會造成無法正常產生R.java 檔,讓你的程式無法編譯。
那麼接下來,我們就開始進入到了解 Android 程式流程的部分吧。
AndroidLogic
解讀程式流程
接著要觀察主要程式邏輯的內容囉。打開 「src/com/demo/android/bmi」 目錄下的 「Bmi.java」檔案,Eclipse+Android 開發工具已經幫我們預先建立好了基本的程式邏輯。其預設的內容如下:
1 package com.demo.android.bmi;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5
6 public class Bmi extends Activity {
7 /** Called when the activity is first created. */
8 @Override
9 public void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.main);
12 }
13 }
講解
比起什麼標籤都對稱的 XML 介面描述檔來說,這個以 Java 程式語言寫成的檔案雖然篇幅短,但反而要難讀得多。
我們將程式直接拆開,分成幾個部份來講解這個「Bmi.java」 檔案的內容:
第 1 行:
package com.demo.android.bmi;
這一行的作用是指出這個檔案所在的名稱空間。「package」(套件)是其關鍵字。使用名稱空間的原因是程式一旦擴展到擴展到某個大小,程式中的變數名稱、方法名稱、類別名稱難免重複, 這時就可以將定義的名稱區隔管理在 package 下,以避免相互衝突的情形發生。Java 的 package 設計成與檔案系統結構相對應,如我們的 package 設定是 「package com.demo.android.bmi」,則這個類別就該在指定目錄的「com/demo/android/bmi」路徑下可以找到。
同時也別忘了 Java 程式語言每段敘述語句的結尾處,與大部分的程式語言一樣需加上一個分號「;」,以表示一行程式敘述的結束。
第 3,4 行:
import android.app.Activity;
import android.os.Bundle;
程式中預設導入了 「android.app.Activity」跟「android.os.Bundle」兩個 Package,在所有的 Android 應用程式中都會用到這兩個 Package。「import」(導入)是用作導入 Package 的關鍵字。在 Java 語言中,使用到任何 API 前都要事先導入相對應的 Package。我們馬上將學到這兩個 Package 的用途。
Android 支援的 Package 與標準的 Java(j2se) 不盡相同。在寫 Android 應用程式時,你偶而可能需要參考可用的 API 列表,以確認使用到的 Package 是否有內建支援。後續章節中也將講解如何透過新增「jar」檔來呼叫額外的 Package。
完整的 API 可查閱官方的 package 列表: http://code.google.com/android/reference/packages.html
第 6,13 行:
public class Bmi extends Activity {
}
第6行開始了程式的主體。其組成是這樣的:
public class Bmi
「Bmi」是這個類別的名稱。「class」則是用作宣告類別關鍵字。「public」關鍵字是用來修飾「Bmi」這個類別。表示「Bmi」是個「公開」的類別,可以從 package 外部取用。
「public class Bmi」後面再加上「extends Activity」敘述,則表示 「Bmi」 這個類別的功能、型別等全繼承自「Activity」類別。「extends」是繼承(Inherit)類別的關鍵字。「Activity」是來自於我們在第3行剛導入的Package。
因此整句話的含意即:「宣告一個公開的 Bmi 類別。這個 Bmi 類別繼承了程式開頭導入的 Activity 類別」。
「{}」大括號規範了一個程式區塊。大括號中的程式表達的這個程式區塊的主要內容。
第 7 行:
/** Called when the activity is first created. */
第 7 行提供了位於其下的函式的註釋。「/* */」 是 Java 語言的多行註解符號,位於其中的文字內容不會被編譯。「/*」敘述後多出來的一個「*」號被視為內文。順便提醒一下,Java 程式語言中兩個斜線「//」表示的是單行註解符號。單行註解符號「//」與多行註解符號「/* */」不同的地方是,只有與「//」符號同行的文字才不會被編譯。
第 8-9, 12 行:
@Override
public void onCreate(Bundle savedInstanceState) {
}
第9行開始了這個方法(Method)的主體。其組成是這樣的:
public void onCreate(Bundle savedInstanceState) {
}
「onCreate」是這個方法的名稱。「void」則是宣告了這個方法的回傳值的型別(type)。「public」關鍵字是用來修飾「onCreate」這個方法。表示「onCreate」是個「公開」的方法,可以由 bmi 類別外部取用。
方法的回傳值的型別,即是這個方法的型別。「onCreate」這個方法使用「void」型別,表示「onCreate」這個方法不需回傳值。
同時,這個方法傳入了一個名為「savedInstanceState」的「Bundle」型別參數,「Bundle」型別正是來自我們前面所導入的 Package 之一。我們並不需要知道太多「Bundle」型別或「savedInstanceState」實體的細節,只要知道「Bundle」的內容與手機平台的記憶體管理有關。
當 Android 應用程式啟動、換到背景等待、關閉時,都會用到 「savedInstanceState」 這個實體來處理記憶體相關的事宜。當然,你也可以用其他名稱來代替它。還好「onCreate」這個方法永遠都是傳入「Bundle savedInstanceState」這個參數,寫應用程式時只要正確照規定傳入即可,你可以不用太去在意它。
給對 Bundle 是什麼有興趣的讀者:
「Bundle」可以保存程式上一次關閉(凍結)時的狀態。你可以透過覆寫 onFreeze 方法(與 onCreate 方法的作用類似) 來保存凍結前的狀態。 當程式啟動(Activity 重新初始化)時,會再次呼叫 onCreate 方法,你就能從 savedInstanceState 中得到前一次凍結的狀態。我們也可以透過「Bundle」來將這個 Activity 的內容傳到下一個 Activity 中。 之後講 Activity 時,也會講解 onCreate/onFreeze 等方法的關係。
「{}」大括號規範了一個程式區塊。大括號中的程式表達 onCreate 這個程式區塊的主要內容。
@Override
public void onCreate(Bundle savedInstanceState)
從前面的講解中,我們學到了在任何一個 Android 專案目錄裡,只要打開「Referenced Libraries」 目錄的「android.app」 分類,都可以找到 「Activity.class」這個類別。現在我們再深入一些查看「Activity.class」 類別。你要做的,只是依照圖示,找到 Android 工具中的「Referenced Libraries」 目錄,從「android.app」 分類裡找到「Activity.class」類別,並按下「Activity.class」 類別左側的三角形圖示,如此即可展開這個類別的屬性/方法列表。
我們在Activity類別的屬性/方法列表中,發現了現在正要講解的 onCreate 方法(Method)。
因為「bmi」 類別繼承自 Activity 類別,所以預設「bmi」 類別中其實已經有「onCreate」方法了。
事實上,「onCreate」 方法正是每個 Activity 類別初始化時都會去呼叫的方法。「@」開頭的語句表示裝飾子(decorator)語句,「@Override」語句的作用是告訴程式我們要覆寫這個「onCreate」方法。當我們打開程式時,程式不再使用從「bmi」 類別中繼承來的「onCreate」方法,而是使用我們在程式中自訂的行為。
@Override
public void onCreate(Bundle savedInstanceState) {
}
我們講解了整段程式,其含意是「覆寫 bmi 類別中公開的 onCreate 方法。這個 「onCreate」 方法無回傳值型別,而且這個方法傳入了一個名為 「savedInstanceState」 的 Bundle 型別參數。 現在來看看「onCreate」方法中包含的程式內容。
第 10, 11 行:
super.onCreate(savedInstanceState);
「super」是關鍵字。代表著這個 「Bmi」 類別的上層類別(Activity)。「super.onCreate(savedInstanceState);」的意思就是:「執行 Activity 類別中 onCreate 方法的內容」。這麼做的目的是什麼呢?
Google Android 將其應用程式的介面稱為視圖(View),而負責控制各種動作行為的程式主體(Controller),則稱為活動(Activity)。因此一個 Android 應用程式,必定會對應到一個以上的 Activity。 「onCreate」 方法則是每個 Activity 類別初始化時都會去呼叫的方法。我們想做的事,是保持原本「onCreate」 方法預設的動作,然後在其中加入我們想要的內容。
而 Android 產生的程式預設卻覆載(@Override)了「Bmi」 類別的「onCreate」 方法。原本繼承自 「Activity」類別的「onCreate」方法,其原本內容都被覆載掉了。因此想將原本的「onCreate」方法內容保留,並在其中加入我們的內容的話,就要使用「super」語句。當程式運行到我們覆寫的「onCreate」 方法時,透過「super.onCreate(savedInstanceState);」語句,會先將原本「Activity」類別中的「onCreate」方法執行一次,然後再執行我們覆寫的「onCreate」方法裡面其他的程式內容。
我們要執行原本的「onCreate」 方法時,仍然需要提供原本「onCreate」方法所需的傳入參數。因此「super.onCreate(savedInstanceState);」語句中,我們將「savedInstanceState」這個參數傳入原本的「onCreate」函式中。「savedInstanceState」是我們在「public void onCreate(Bundle savedInstanceState)」語句中所宣告的傳入參數。
setContentView(R.layout.main);
透過螢幕顯示的各種元素是按照介面層次結構來描述的。要將一個顯示元素的層次結構轉換顯示到一個螢幕上,Activity 會呼叫它用來設定 View 的 「setContentView」 方法,並傳入想引用的 XML 描述文件。當 Activity 被啟動並需要顯示到螢幕上時,系統會通知 Activity,並根據引用的 XML 文件敘述來描繪出使用者介面。上一章中我們定義好的 res/layout/main.xml 描述檔,就是透過這個機制繪出到螢幕上。
setContentView 方法也可以在 Activity 類別中找到。
你可能也注意到 「setContentView」 方法確實是透過 「R.layout.main」來引用 XML 文件描述檔的資源,而不是直接透過 res 目錄來引用。
AndroidLogic
解讀程式流程
接著要觀察主要程式邏輯的內容囉。打開 「src/com/demo/android/bmi」 目錄下的 「Bmi.java」檔案,Eclipse+Android 開發工具已經幫我們預先建立好了基本的程式邏輯。其預設的內容如下:
1 package com.demo.android.bmi;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5
6 public class Bmi extends Activity {
7 /** Called when the activity is first created. */
8 @Override
9 public void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.main);
12 }
13 }
講解
比起什麼標籤都對稱的 XML 介面描述檔來說,這個以 Java 程式語言寫成的檔案雖然篇幅短,但反而要難讀得多。
我們將程式直接拆開,分成幾個部份來講解這個「Bmi.java」 檔案的內容:
第 1 行:
package com.demo.android.bmi;
這一行的作用是指出這個檔案所在的名稱空間。「package」(套件)是其關鍵字。使用名稱空間的原因是程式一旦擴展到擴展到某個大小,程式中的變數名稱、方法名稱、類別名稱難免重複, 這時就可以將定義的名稱區隔管理在 package 下,以避免相互衝突的情形發生。Java 的 package 設計成與檔案系統結構相對應,如我們的 package 設定是 「package com.demo.android.bmi」,則這個類別就該在指定目錄的「com/demo/android/bmi」路徑下可以找到。
同時也別忘了 Java 程式語言每段敘述語句的結尾處,與大部分的程式語言一樣需加上一個分號「;」,以表示一行程式敘述的結束。
第 3,4 行:
import android.app.Activity;
import android.os.Bundle;
程式中預設導入了 「android.app.Activity」跟「android.os.Bundle」兩個 Package,在所有的 Android 應用程式中都會用到這兩個 Package。「import」(導入)是用作導入 Package 的關鍵字。在 Java 語言中,使用到任何 API 前都要事先導入相對應的 Package。我們馬上將學到這兩個 Package 的用途。
Android 支援的 Package 與標準的 Java(j2se) 不盡相同。在寫 Android 應用程式時,你偶而可能需要參考可用的 API 列表,以確認使用到的 Package 是否有內建支援。後續章節中也將講解如何透過新增「jar」檔來呼叫額外的 Package。
完整的 API 可查閱官方的 package 列表: http://code.google.com/android/reference/packages.html
第 6,13 行:
public class Bmi extends Activity {
}
第6行開始了程式的主體。其組成是這樣的:
public class Bmi
「Bmi」是這個類別的名稱。「class」則是用作宣告類別關鍵字。「public」關鍵字是用來修飾「Bmi」這個類別。表示「Bmi」是個「公開」的類別,可以從 package 外部取用。
「public class Bmi」後面再加上「extends Activity」敘述,則表示 「Bmi」 這個類別的功能、型別等全繼承自「Activity」類別。「extends」是繼承(Inherit)類別的關鍵字。「Activity」是來自於我們在第3行剛導入的Package。
因此整句話的含意即:「宣告一個公開的 Bmi 類別。這個 Bmi 類別繼承了程式開頭導入的 Activity 類別」。
「{}」大括號規範了一個程式區塊。大括號中的程式表達的這個程式區塊的主要內容。
第 7 行:
/** Called when the activity is first created. */
第 7 行提供了位於其下的函式的註釋。「/* */」 是 Java 語言的多行註解符號,位於其中的文字內容不會被編譯。「/*」敘述後多出來的一個「*」號被視為內文。順便提醒一下,Java 程式語言中兩個斜線「//」表示的是單行註解符號。單行註解符號「//」與多行註解符號「/* */」不同的地方是,只有與「//」符號同行的文字才不會被編譯。
第 8-9, 12 行:
@Override
public void onCreate(Bundle savedInstanceState) {
}
第9行開始了這個方法(Method)的主體。其組成是這樣的:
public void onCreate(Bundle savedInstanceState) {
}
「onCreate」是這個方法的名稱。「void」則是宣告了這個方法的回傳值的型別(type)。「public」關鍵字是用來修飾「onCreate」這個方法。表示「onCreate」是個「公開」的方法,可以由 bmi 類別外部取用。
方法的回傳值的型別,即是這個方法的型別。「onCreate」這個方法使用「void」型別,表示「onCreate」這個方法不需回傳值。
同時,這個方法傳入了一個名為「savedInstanceState」的「Bundle」型別參數,「Bundle」型別正是來自我們前面所導入的 Package 之一。我們並不需要知道太多「Bundle」型別或「savedInstanceState」實體的細節,只要知道「Bundle」的內容與手機平台的記憶體管理有關。
當 Android 應用程式啟動、換到背景等待、關閉時,都會用到 「savedInstanceState」 這個實體來處理記憶體相關的事宜。當然,你也可以用其他名稱來代替它。還好「onCreate」這個方法永遠都是傳入「Bundle savedInstanceState」這個參數,寫應用程式時只要正確照規定傳入即可,你可以不用太去在意它。
給對 Bundle 是什麼有興趣的讀者:
「Bundle」可以保存程式上一次關閉(凍結)時的狀態。你可以透過覆寫 onFreeze 方法(與 onCreate 方法的作用類似) 來保存凍結前的狀態。 當程式啟動(Activity 重新初始化)時,會再次呼叫 onCreate 方法,你就能從 savedInstanceState 中得到前一次凍結的狀態。我們也可以透過「Bundle」來將這個 Activity 的內容傳到下一個 Activity 中。 之後講 Activity 時,也會講解 onCreate/onFreeze 等方法的關係。
「{}」大括號規範了一個程式區塊。大括號中的程式表達 onCreate 這個程式區塊的主要內容。
@Override
public void onCreate(Bundle savedInstanceState)
從前面的講解中,我們學到了在任何一個 Android 專案目錄裡,只要打開「Referenced Libraries」 目錄的「android.app」 分類,都可以找到 「Activity.class」這個類別。現在我們再深入一些查看「Activity.class」 類別。你要做的,只是依照圖示,找到 Android 工具中的「Referenced Libraries」 目錄,從「android.app」 分類裡找到「Activity.class」類別,並按下「Activity.class」 類別左側的三角形圖示,如此即可展開這個類別的屬性/方法列表。
我們在Activity類別的屬性/方法列表中,發現了現在正要講解的 onCreate 方法(Method)。
因為「bmi」 類別繼承自 Activity 類別,所以預設「bmi」 類別中其實已經有「onCreate」方法了。
事實上,「onCreate」 方法正是每個 Activity 類別初始化時都會去呼叫的方法。「@」開頭的語句表示裝飾子(decorator)語句,「@Override」語句的作用是告訴程式我們要覆寫這個「onCreate」方法。當我們打開程式時,程式不再使用從「bmi」 類別中繼承來的「onCreate」方法,而是使用我們在程式中自訂的行為。
@Override
public void onCreate(Bundle savedInstanceState) {
}
我們講解了整段程式,其含意是「覆寫 bmi 類別中公開的 onCreate 方法。這個 「onCreate」 方法無回傳值型別,而且這個方法傳入了一個名為 「savedInstanceState」 的 Bundle 型別參數。 現在來看看「onCreate」方法中包含的程式內容。
第 10, 11 行:
super.onCreate(savedInstanceState);
「super」是關鍵字。代表著這個 「Bmi」 類別的上層類別(Activity)。「super.onCreate(savedInstanceState);」的意思就是:「執行 Activity 類別中 onCreate 方法的內容」。這麼做的目的是什麼呢?
Google Android 將其應用程式的介面稱為視圖(View),而負責控制各種動作行為的程式主體(Controller),則稱為活動(Activity)。因此一個 Android 應用程式,必定會對應到一個以上的 Activity。 「onCreate」 方法則是每個 Activity 類別初始化時都會去呼叫的方法。我們想做的事,是保持原本「onCreate」 方法預設的動作,然後在其中加入我們想要的內容。
而 Android 產生的程式預設卻覆載(@Override)了「Bmi」 類別的「onCreate」 方法。原本繼承自 「Activity」類別的「onCreate」方法,其原本內容都被覆載掉了。因此想將原本的「onCreate」方法內容保留,並在其中加入我們的內容的話,就要使用「super」語句。當程式運行到我們覆寫的「onCreate」 方法時,透過「super.onCreate(savedInstanceState);」語句,會先將原本「Activity」類別中的「onCreate」方法執行一次,然後再執行我們覆寫的「onCreate」方法裡面其他的程式內容。
我們要執行原本的「onCreate」 方法時,仍然需要提供原本「onCreate」方法所需的傳入參數。因此「super.onCreate(savedInstanceState);」語句中,我們將「savedInstanceState」這個參數傳入原本的「onCreate」函式中。「savedInstanceState」是我們在「public void onCreate(Bundle savedInstanceState)」語句中所宣告的傳入參數。
setContentView(R.layout.main);
透過螢幕顯示的各種元素是按照介面層次結構來描述的。要將一個顯示元素的層次結構轉換顯示到一個螢幕上,Activity 會呼叫它用來設定 View 的 「setContentView」 方法,並傳入想引用的 XML 描述文件。當 Activity 被啟動並需要顯示到螢幕上時,系統會通知 Activity,並根據引用的 XML 文件敘述來描繪出使用者介面。上一章中我們定義好的 res/layout/main.xml 描述檔,就是透過這個機制繪出到螢幕上。
setContentView 方法也可以在 Activity 類別中找到。
你可能也注意到 「setContentView」 方法確實是透過 「R.layout.main」來引用 XML 文件描述檔的資源,而不是直接透過 res 目錄來引用。
BmiRefactor
重構程式
偉大的創意少之又少,多數時候只是一些小改進。小的改進也是好的。
什麼是重構
可以運作的程式跟可以維護的程式之間,還有一道難以言說的鴻溝。
一個程式設計之初,是用來解決特定問題。就像在前面章節的學習中,我們也已經寫好了一個可以運作的 BMI 程式。但是對程式設計來說,當我們寫越多程式,我們會希望可以從這些程式之中,找到一個更廣泛適用的法則,讓每個程式都清晰易讀,從而變得更好修改與維護。
讓程式清晰易讀有什麼好處呢?當一段程式被寫出來,之後我們所要做的事,就是修改它與維護它。一旦程式越長越複雜,混亂到無法維護的境界時,就只好砍掉重練。 所以若我們能透過某些方式,例如重新組織或部分改寫程式碼,好讓程式容易維護,那麼我們就可以為自己省下許多時間,以從容迎接新的挑戰。
我們回過頭來看看前面所寫的 Android 程式。Android 平台的開發者已經先依照 MVC 模式,為我們將顯示介面所用的 XML 描述檔、顯示資源所用的 XML 描述檔從程式碼中區隔開來。將與程式流程無關的部份分開來組織,讓程式流程更清楚,相對易於維護。
而在主要程式碼(Bmi.java)方面,雖然程式碼量很少,還算好讀,但整體上並不那麼令人滿意。例如,假使我們要在這段程式碼中再多加上按鍵、適用於多種螢幕顯示模式、或是再加入選單等等內容,很快地程式碼就開始變得複雜,變得不容易閱讀,也開始越來越不容易維護。
因此,在繼續新的主題之前,我們先來重構這個 BMI 應用程式。在重構的過程中,也許我們能學到的東西,比學任何新主題還重要哩。
MVC
我們打算重構 BMI 程式的部份 java 程式碼。既然我們已經照著 Android 平台的作法,套用 MVC 模式在我們的程式組織上,那麼,我們不妨也試著套用同樣的 MVC 模式在 Bmi.java 程式碼上。
如何套用 MVC 模式到Bmi.java 程式碼上哩?
原來的程式片段是這樣的
1 @Override
2 public void onCreate(Bundle savedInstanceState) {
3 super.onCreate(savedInstanceState);
4 setContentView(R.layout.main);
5
6 //Listen for button clicks
7 Button button = (Button) findViewById(R.id.submit);
8 button.setOnClickListener(calcBMI);
9 }
上面的程式片段中,包含了所有 Android 程式共用的標準內容, 整個程式的大致架構在前面章節中已經講解過,現在我們從中取出我們感興趣的部分來討論:
Button button = (Button) findViewById(R.id.submit);
button.setOnClickListener(calcBMI);
在第7行我們看到一段程式碼來宣告按鈕物件,與針對該按鈕物件作動作的程式碼。 button.setOnClickListener 程式碼的意義是指定一個函式,來負責處理"按下"這個"按鈕"後的動作。
我們可以想像,在同一個畫面中,多加入一些按鈕與欄位後,"onCreate" 這段程式將變得壅腫,我們來試著先對此稍作修改:
首先,我們可以套用 MVC 模式,將宣告介面元件(按鈕、數字欄位)、指定負責函式等動作抽取出來,將 onCreate 函式改寫如下
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViews();
setListensers();
}
接著我們將宣告介面元件的部份寫成一個獨立的「findViews」函式:
private Button calcbutton;
private EditText fieldheight;
private EditText fieldweight;
private void findViews()
{
calcbutton = (Button) findViewById(R.id.submit);
fieldheight = (EditText) findViewById(R.id.height);
fieldweight = (EditText) findViewById(R.id.weight);
}
順便將原本很沒個性的按鈕識別參數「button」改名成「calcbutton」,以後在程式中一看到「calcbutton」,就知道是一個按下後將開始處理計算工作的按鈕。
同樣地,我們也將指定特定動作(按按鈕)的負責函式獨立出來:
//Listen for button clicks
private void setListensers() {
calcbutton.setOnClickListener(calcBMI);
}
如此一來,我們就將程式邏輯與介面元件的宣告分離開來,達成我們重構的目的。
完整程式如下:
package com.demo.android.bmi;
import java.text.DecimalFormat;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class Bmi extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViews();
setListensers();
}
private Button button_calc;
private EditText field_height;
private EditText field_weight;
private TextView view_result;
private TextView view_suggest;
private void findViews()
{
button_calc = (Button) findViewById(R.id.submit);
field_height = (EditText) findViewById(R.id.height);
field_weight = (EditText) findViewById(R.id.weight);
view_result = (TextView) findViewById(R.id.result);
view_suggest = (TextView) findViewById(R.id.suggest);
}
//Listen for button clicks
private void setListensers() {
button_calc.setOnClickListener(calcBMI);
}
private Button.OnClickListener calcBMI = new Button.OnClickListener()
{
public void onClick(View v)
{
DecimalFormat nf = new DecimalFormat("0.0");
double height = Double.parseDouble(field_height.getText().toString())/100;
double weight = Double.parseDouble(field_weight.getText().toString());
double BMI = weight / (height * height);
//Present result
view_result.setText(getText(R.string.bmi_result) + nf.format(BMI));
//Give health advice
if(BMI>25){
view_suggest.setText(R.string.advice_heavy);
}else if(BMI<20){
view_suggest.setText(R.string.advice_light);
}else{
view_suggest.setText(R.string.advice_average);
}
}
};
}
同樣是「calcBMI」 函式,在完整程式中,改將「calcBMI」 函式從原本的「OnClickListener」宣告成 「Button.OnClickListener」。這個改變有什麼差別呢?
閱讀原本的程式碼,在匯入(import)的部分可以看到,「OnClickListener」是來自於「android.view.View.OnClickListener」函式:
import android.view.View.OnClickListener;
改成 「Button.OnClickListener」後,「Button.OnClickListener」就變成來自於「android.widget.Button」中的「OnClickListener」函式,在查閱程式時,整個「Button」與「OnClickListener」之間的關係變得更清晰。
另外,我們偷偷將「OnClickListener」中其他會存取到的介面元件識別參數,也補進 findViews 宣告中:
private void findViews()
{
button_calc = (Button) findViewById(R.id.submit);
field_height = (EditText) findViewById(R.id.height);
field_weight = (EditText) findViewById(R.id.weight);
view_result = (TextView) findViewById(R.id.result);
view_suggest = (TextView) findViewById(R.id.suggest);
}
同時,我們也把識別參數的命名方法做了統一:按鈕的識別參數前加上 「button_」前綴,可輸入欄位的識別參數前加上 「field_」前綴,用作顯示的識別參數前則加上「view_」前綴。將變數名稱的命名方法統一有什麼好處呢? 好處在於以後不管是在命名新變數,或是閱讀程式碼時,都能以更快速度命名或理解變數的意義,讓程式變得更好讀。
我們也把原本在程式中直接寫進的字串
TextView result = (TextView) findViewById(R.id.result);
result.setText("Your BMI is "+nf.format(BMI));
改寫成
//Present result
view_result.setText(getText(R.string.bmi_result) + nf.format(BMI));
並將「TextView view_result」宣告改放到 findViews 中一次處理好。
現在,整個程式流程是不是清爽了許多呢?
AndroidDialog
加入對話框
我們的程式主功能已經完成了,現在我們要試著讓它看起來更像一個完整的應用程式。
接下來的幾章,我們要為「BMI」應用程式加上一個選單。選單裡面有一個「關於..」選項。按下「關於...」選項後,會彈出一個對話框,裡面會顯示「BMI」程式的相關訊息。
本章中將先學習如何處理對話框。
在本章中,我們要產生一個應用程式中常見的「關於」頁面。 應用程式的「關於」頁面中,通常要包含版本訊息、作者、聯絡方式、首頁等資訊。
我們的「關於」頁面將以彈出對話框的方式表現。所需要做的,是撰寫負責處理對話框的「openOptionsDialog」函式,並將之附加在原本應用程式中「calcBMI」這個按鈕元件的「OnClickListener」方法上。當我們按下「計算 BMI 值」按鈕時,即彈出對話框。
等我們學會了對話框的寫法,在接下來學習 Android 選單的章節中,我們會改將對話框函式放入選單中。
對話框中所能顯示的內容千變萬化。對 Android 來說,對話框也是一種顯示內容(View)。與一般全頁面顯示的不同之處,在於對話框會重疊顯示到原本的呼叫頁面上,而且在對話框的主要顯示內容下方,可能還會再附加上幾個按鈕,用以回到原頁面,或是用來執行其他的動作。
要在 Android 程式中呼叫一個對話框,有二個主要步驟:
# 定義呼叫點 # 實作對話框
定義呼叫點
修改「Bmi.java」
Bmi.java
1 private OnClickListener calcBMI = new OnClickListener()
2 {
3 public void onClick(View v)
4 {
. ....
6 }else{
7 view_suggest.setText(R.string.advice_average);
8 }
9 openOptionsDialog();
我們在「calcBMI」函式的尾端加入一行「openOptionsDialog();」,用以在每次計算完BMI值並顯示建議後,順便呼叫「關於」對話框。
實作對話框
緊接著「calcBMI」這個「OnClickListener」函式之後,我們實際開始撰寫對話框函式。
private void openOptionsDialog() {
new AlertDialog.Builder(Bmi.this)
.setTitle("關於 Android BMI")
.setMessage("Android BMI Calc")
.show();
我們來分析這個對話框程式。
首先,顯示一個最基本的對話框所需的程式碼如下。
new AlertDialog.Builder(Bmi.this).show()
我們建立了一個 AlertDialog 對話框類別實體,AlertDialog 呼叫 Builder 方法來預備對應的介面元件。最後使用 show() 方法來將對話框顯示在螢幕上。
透過
.setTitle("關於 Android BMI")
我們設定了對話框的標題。
透過
.setMessage("Android BMI Calc")
我們設定了對話框的主要內容。
重構
我們把其中用到的字串抽取出來,整理到「res/values/strings.xml」中。
res/values/strings.xml
....
關於 Android BMI
Android BMI Calc\n
作者 gasolin\n\n
gasolin+android [at] gmail.com
....
於是 openOptionsDialog 函式變成這樣:
private void openOptionsDialog() {
new AlertDialog.Builder(Bmi.this)
.setTitle(R.string.about_title)
.setMessage(R.string.about_msg)
.show();
打開模擬器,在按下按鈕後,我們看到計算出 BMI 值的同時,螢幕上也彈出了一個有標題的對話框。
加入按鈕
目前的對話框中,並沒有提供離開對話框的方法。所以我們得按下 「Undo」按鈕來離開對話框,有點不便,所以我們來為這個對話框加入一個「確認」按鈕。
.setPositiveButton("確認",
new DialogInterface.OnClickListener(){
public void onClick(
DialogInterface dialoginterface, int i){
}
})
「setPositiveButton」、「setNegativeButton」 或 「setNeutralButton」 函式都可以用來定義按鈕,各按鈕分別預設代表正面/中立/負面的結果。
上方程式碼中定義的「setPositiveButton」裡,包含了一個沒有作用的對話框介面(DialogInterface)。 表示當我們按下按鈕時,不做任何事就直接退出對話框。
完整對話框函式的程式碼如下
private void openOptionsDialog() {
new AlertDialog.Builder(Bmi.this)
.setTitle(R.string.about_title)
.setMessage(R.string.about_msg)
.setPositiveButton(R.string.ok_label,
new DialogInterface.OnClickListener(){
public void onClick(
DialogInterface dialoginterface, int i){
}
})
.show();
}
}
更詳細的對話框使用可參考官方文件 http://code.google.com/android/reference/android/app/AlertDialog.Builder.html
順道一提 -「Toast 」介面元件
對話框的使用模式,限制了使用者得按下某些按鍵以跳出對話框,才能繼續使用原本程式。如果我們只是要顯示一小段提示訊息,而不想打擾使用者的注意力,有沒有更適合的方法哩? 有的,我們可以把顯示方式比較有彈性的對話框拿掉,改為使用簡單的 「Toast 」介面元件。「Toast 」介面元件的作用是彈出一個訊息框,快速在螢幕上顯示一小段訊息。
程式碼如下:
import android.widget.Toast;
...
private void openOptionsDialog() {
Toast.makeText(Bmi.this, "BMI 計算器", Toast.LENGTH_SHORT).show();
/*new AlertDialog.Builder(this) //註解掉原本的對話框
...
*/
}
打開模擬器。我們按下「計算 BMI 值」按鈕後,螢幕上不再出現一個對話框,而改成彈出一段「BMI 計算器」文字訊息,過幾秒之後即自動隱去。
整段程式值得注意的一行是
Toast.makeText(Bmi.this, "BMI 計算器", Toast.LENGTH_SHORT).show();
我們對 Toast 元件指定了欲顯示文字,與Toast 元件的顯示時間長短(LENGTH_SHORT,即短訊息),最後與處理對話框一樣,呼叫 「show() 」方法來將 Toast 元件顯示在螢幕上。
錯誤處理
解決出錯最好的方式就是阻止它們的發生。
雖然在當前的程式中,我們似乎用不到 Toast 元件。不過,其實我們還是可以巧妙地運用它。
使用者在輸入資料時,難免會出錯。而現在我們寫好的 BMI 程式中,並沒有對使用者可能的輸入錯誤做處理。
因此在下面的程式改進中,我們使用了 try...catch 語句,與 Toast 元件來做錯誤處理。
DecimalFormat nf = new DecimalFormat("0.00");
try{
double height = Double.parseDouble(field_height.getText().toString())/100;
double weight = Double.parseDouble(field_weight.getText().toString());
double BMI = weight / (height * height);
//Present result
view_result.setText(getText(R.string.bmi_result) + nf.format(BMI));
//Give health advice
if(BMI>25){
view_suggest.setText(R.string.advice_heavy);
}else if(BMI<20){
view_suggest.setText(R.string.advice_light);
}else{
view_suggest.setText(R.string.advice_average);
}
}
catch(Exception err)
{
Toast.makeText(Bmi.this, "打錯了嗎?只能輸入數字喔", Toast.LENGTH_SHORT).show();
}
講解
try
{
主要程式流程
}
catch(Exception err)
{
錯誤處理流程
}
try...catch 語句是 Java 語言的錯誤處理語句。首先將「主要的程式流程」包在 try 語句的兩個大括號中執行,如果執行正常,就跳過 catch 語句,繼續執行後續的語句。旦是如果在 try 語句中執行的流程出現錯誤了,那麼程式流程就會從 try 語句跳到對應的 catch 語句,並開始執行對應的 catch 語句大括號中的「錯誤處理流程」。
在我們的 BMI 程式中,catch 語句大括號中的「錯誤處理流程」,是簡單地在螢幕上顯示一串提示使用者輸入錯了,應該輸入數字的訊息。
Toast.makeText(Bmi.this, "打錯了嗎?只能輸入數字喔", Toast.LENGTH_SHORT).show();
需要在螢幕上顯示一串提示時,就是 Toast 元件派上用場的地方。除了所顯示的字串不同之外,整段程式與上一節相同。
重構
為了更好地重用,我們繼續把字串提取到「res/values/strings.xml」中
....
打錯了嗎?只能輸入數字喔
然後在程式中使用「R.string.input_error」來取得字串
Toast.makeText(Bmi.this, R.string.input_error, Toast.LENGTH_SHORT).show();
AndroidUrl
初見 Intent
開啟網頁
我們已經對 Android 應用程式的寫法有了概觀的認識。可是我們還沒有觸及 Android 平台的特別之處:整合網路的應用。
在上一章中,我們學到如何使用對話框,與如何在對話框下添加按鈕,以回到原畫面。在這一章裡,我們來為我們的應用程式添加一個簡單的網路功能:在上一章實做的「openOptionsDialog」對話框函式中,新添一個「連線到首頁」的按鈕。
我們先把「openOptionsDialog」函式中使用到的字串增加到「res/values/string.xml」裡。因此完整的「res/values/string.xml」檔案如下:
1
2
3 BMI
4 身高 (cm)
5 體重 (kg)
6 計算 BMI 值
7 你的 BMI 值是
8
9 關於 Android BMI
10 Android BMI Calc 0.6\n
11 作者 gasolin\n
12 gasolin+android [at] gmail.com
13 確認
14 首頁
15
增加了「連線到首頁」按鈕,完整「openOptionsDialog」函式的新版程式碼如下:
1 private void openOptionsDialog() {
2 new AlertDialog.Builder(this)
3 .setTitle(R.string.about_title)
4 .setMessage(R.string.about_msg)
5 .setPositiveButton(R.string.ok_label,
6 new DialogInterface.OnClickListener(){
7 public void onClick(
8 DialogInterface dialoginterface, int i){
9 }
10 })
11 .setNegativeButton(R.string.homepage_label,
12 new DialogInterface.OnClickListener(){
13 public void onClick(
14 DialogInterface dialoginterface, int i){
15 //go to url
16 Uri uri = Uri.parse("http://androidbmi.googlecode.com/");
17 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
18 startActivity(intent);
19 }
20 })
21 .show();
22 }
講解
在上一章「openOptionsDialog」函式的基礎上,我們在函式中添加了一個「setNegativeButton」方法,以提供另一個「NegativeButton」按鈕。
.setNegativeButton(R.string.homepage_label,
new DialogInterface.OnClickListener(){
public void onClick(
DialogInterface dialoginterface, int i){
.....
}
})
與上一章我們將 DialogInterface 中的內容空白不同的是,我們為這個按鈕添加了連線到特定網址(首頁)的「動作」,當使用者按下「首頁」按鈕後,程式會開啟瀏覽器,並連線到本專案的首頁「http://androidbmi.googlecode.com/」。
要完成整個連線的「動作」只需要三行程式碼:
//go to url 這是註解
Uri uri = Uri.parse("http://androidbmi.googlecode.com/");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
以下是分行詳細的講解:
Uri uri = Uri.parse("http://androidbmi.googlecode.com/");
建立一個 Uri 實體,裡面包含我們要連到的網址「http://androidbmi.googlecode.com/」。
在我們第一次在程式碼中加入「Uri」時敘述時,「Uri」下方會出現紅色的線,表示「Uri」可能是個需要由外部導入(import)的函式或類別。在「Eclispe」開發環境中,我們可以使用「ctrl-shift-O」(Windows)或「cmd-shift-O」(Mac)來自動在程式開頭的地方導入「android.net.Uri」函式庫。
startActivity(intent);
透過「startActivity」 函式,Android 系統即根據收到不同 「意圖」(Intent) 的動作和內容,開啟對應的新頁面或新程式。
在 Android 平台上,各個 Activity 之間的呼叫與交流都要透過「startActivity」 一類的函式來互動。「startActivity」 一類的函式中,最重要需傳入的內容就是「意圖」(Intent) 。因此我們在後面會進一步闡述「Intent」與「Activity」之間的關係。
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
在這行中,我們建立一個「意圖」(Intent) 實體,並傳入這個意圖的「動作」與「內容」。
Intent 的格式如下:
Intent intent = new Intent(動作, 內容);
我們所建立「意圖」(Intent) 中,所傳入的「動作」是「Intent.ACTION_VIEW」。「Intent.ACTION_VIEW」是 Android 內建的「動作」之一。在 Eclipse 編輯畫面中輸入「Intent.」時,Eclipse 編輯器會彈出可輸入的建議動作選單,我們可以透過這個選單,了解可使用的各種 Intent 內建動作。
「Intent.ACTION_VIEW」這個動作的意義為:依所提供內容的不同,開啟對應的程式以檢視內容資料。我們可以把這個動作想成在一般桌面系統上,開啟一個已知的檔案類型檔案(比如說一張照片),作業系統會用你預設的應用程式(比如說某看圖軟體)開啟這個檔案。本例中提供了「Uri」網址類型的內容給「Intent.ACTION_VIEW」這個動作,所得到的結果,就是開啟瀏覽器並前往「http://androidbmi.googlecode.com/」 頁面。
再做好一點
前面我們看到 Uri 實體的定義方法:
Uri uri = Uri.parse("http://androidbmi.googlecode.com/");
看到 Uri.parse() 中,有一個固定的網址,你應該也會想把它抽取出來,丟到 Resource 中統一管理。那麼我們把程式改寫,首先把網址抽取出來,放到「res/values/string.xml」檔案中。 「res/values/string.xml」檔案更新如下:
...
首頁
http://androidbmi.googlecode.com/
我們把 Uri.parse() 函式修改,傳入資源識別符號.
Uri uri = Uri.parse(R.string.homepage_uri);
糟了,Eclipse 上出現了紅線,表示在我們的程式碼裡,有什麼地方弄錯了。 因為我們只修改了 Uri.parse 傳入的內容,我們可以很確定是我們傳入的內容錯了。 我們重新輸入「Uri.」,利用 Eclipse 的自動提示功能,看看 parse 函式到底接受那些類別的參數。 原來,Uri.parse() 函式並不接受資源識別符號型態的輸入。 這麼一來,我們就得自行根據資源識別符號,來取得資源識別符號所代表的文字敘述內容。 真正能執行的程式碼如下:
Uri uri = Uri.parse(getString(R.string.homepage_uri));
在程式中,我們可以使用「android.content.Context」類別中的「getString」 函式(或是getText),來取得資源識別符號對應的文字。
AndroidMenu
加入選單(Menu)
在進一步學習 Intent 與 Activity 之前,我們先來完善我們的應用程式。 在前幾章中,我們把 「openOptionsDialog」 這個用來彈出對話框的函式,放進「calcBMI」這個按鈕元件的「OnClickListener」方法中。現在,我們要把「openOptionsDialog」 移出「OnClickListener」方法,改成按下「Menu」鍵後,跳出一個選單列(Menu Bar)。當我們點擊選單列中的選項後,才彈出 「openOptionsDialog」 的對話框。
完整的程式碼如下:
1 protected static final int MENU_ABOUT = Menu.FIRST;
2 protected static final int MENU_Quit = Menu.FIRST+1;
3
4 @Override
5 public boolean onCreateOptionsMenu(Menu menu) {
6 super.onCreateOptionsMenu(menu);
7 menu.add(0, MENU_ABOUT, 0, "關於...");
8 menu.add(0, MENU_Quit, 0, "結束");
9 return true;
10 }
11
12 @Override
13 public boolean onOptionsItemSelected(MenuItem item)
14 {
15 super.onOptionsItemSelected(item);
16 switch(item.getItemId()){
17 case MENU_ABOUT:
18 openOptionsDialog();
19 break;
20 case MENU_Quit:
21 finish();
22 break;
23 }
24 return true;
25 }
每個選單都包含兩個部分:
1. 建立選單
2. 處理選項動作
「onCreateOptionsMenu」函式即選單列的主體。在 Android 機器或模擬器上按下硬體的「Menu」(選單)鍵,所彈出的選單列即是靠「onCreateOptionsMenu」函式來定義。當我們在 Activity 中定義了 「onCreateOptionsMenu」 之後,按下「Menu」(選單)鍵時,就會彈出相對應的選單列。
當我們在 Android 應用程式的選單列上選擇了相應的選項後,則是依賴「onOptionsItemSelected」函式,來負責處理選單列中各選項所個別對應的動作。
在上面的程式裡,我們定義了「關於...」與「結束」兩個選單列中的選項。我們分部分講解如下:
建立選單
在 「onCreateOptionsMenu」函式中,我們定義了兩個選單列中的選項。 分行講解如下:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
「onCreateOptionsMenu」這個函式是選單列的主體,它是一個「public」(公開)的函式。函式傳入一個「Menu」(選單)型別的「menu」參數。「boolean」則表示函式的返回值必須為「boolean」型別的值。因此在函式最後,我們提供函式一個返回值「true」。「@Override」表示我們要完全重寫掉已定義在「Activity」類別中的這個函式。
基於與 onCreate 函式一樣的原因,因為我們把選單列原本的動作覆載 (Override) 掉了,因此在撰寫我們自己的內容前,加上一句「super.onCreateOptionsMenu(menu)」敘述,用來呼叫「onCreateOptionsMenu」函式執行預設的動作。
menu.add(0, MENU_ABOUT, 0, "關於...");
menu.add(0, MENU_Quit, 0, "結束");
Android 每個頁面對應到一個 Activity,每個 Activity 都有一個獨立的選單列。對傳入的「menu」參數作處理就能改變選單列的內容。
我們看到,增加一個選單列中選項的格式如下:
menu.add(0, 識別符號(identifer), 0, 字串或資源識別符號);
最後一欄「字串或資源識別符號」就是顯示在螢幕上的敘述。 而「識別符號」的目的則是作為這個選項的標籤,以供後續處理選項動作時,更容易辨認出所對應的選項。
protected static final int MENU_ABOUT = Menu.FIRST;
protected static final int MENU_Quit = Menu.FIRST+1;
我們看到 MENU_ABOUT 識別符號的定義,是一個固定的常數型別(static final int)。「Menu.FIRST」則代表識別選單開頭的數字,當然我們也可以把這「Menu.FIRST」代號直接用任意數字替換,看看程式會發生什麼事。
處理選項動作
在「OptionsItemSelected」函式中,我們分行講解如下:
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
super.onOptionsItemSelected(item);
return true;
}
「onOptionsItemSelected」這個函式是處理所有選項的主體,和「onCreateOptionsMenu」函式相同,也是一個「public」(公開)的函式。「onOptionsItemSelected」函式傳入了一個「MenuItem」(選項)型別的「item」參數。「boolean」表示函式的返回值必須為「boolean」型別的值。因此在函式最後,我們提供函式一個返回值「true」。 「super.onOptionsItemSelected(item);」表示我們要先執行已定義在「Activity」類別中原本的「onOptionsItemSelected」函式內容,後面再接著執行我們為此函式新定義的動作。
switch(item.getItemId()){
我們可以用「item.getItemId()」函式來取得在螢幕上選取的選項所對應的識別符號代碼(identifer)。
switch(識別符號代碼){
....
}
在 swith 敘述中,我們根據從「item.getItemId()」函式取得的識別符號代碼判斷, 根據選到的識別符號代碼,作相應處理。
case MENU_ABOUT:
openOptionsDialog();
break;
case
....
break;
在「onOptionsItemSelected」函式中收到 「MENU_ABOUT」 識別符號時,我們呼叫 「openOptionsDialog」 函式來彈出對話框。
case MENU_Quit:
finish();
break;
在「onOptionsItemSelected」函式中收到 「MENU_Quit」 識別符號時,我們呼叫 Android 內建的 「finish」函式來關閉這個 Activity。因為我們的「BMI」應用程式只由一個「Bmi」Activity 組成,所以當我們呼叫「finish」函式來關閉「Bmi」這個 Activity,就等於直接關閉了這個「BMI」應用程式。
而事實上, 在 Android 平台上,無論是開發者或是使用者,都不需要自己來關閉 Activity。 因為 Android 虛擬機(Dalvik) 接手了什麼時候 Activity 該啟動或關閉的工作。整個 Android Activity 的運作流程,將在後續章節中作講解。
AndroidManifest
定義 Android 清單
在初見 Intent一章中,我們已嘗試過使用 「startActivity」函式,傳入適當的「Intent」,來呼叫瀏覽器的 Activity。
到目前為止,我們可以由學習 Android 應用程式的經驗中歸納得出:所有 Android 程式的運作流程,都定義在 Activity 中。
Android 系統與其他系統很不一樣的地方是:它的應用程式並不直接與底層系統緊密結合,而是跑在 Android 框架中。這意思是設計 Android 應用程式時,我們並不需要關心實際上運作的機器是哪一牌的手機或是哪一種嵌入式系統,或使用哪一種架構(ARM、x86、MIPS)。我們要關心的只有Android 框架提供了那些功能,好讓我們能操作這台設備。具體來說就是我們只要知道這台機器的螢幕大小、有沒有鍵盤,有沒有支援 GPS 等等訊息,就知道我們寫的應用程式是否能在這台機器上順暢地運作。Android 框架與底層系統的整合的問題完全可以留給軔體工程師來操心。
在執行「startActivity」函式時,應用程式並不是直接呼叫另一個 Activity,而是將「Intent」(意圖)傳進 Android 框架中。 Android 框架會查看 「startActivity」 呼叫所傳入的動作與 Intent 內容是否在註冊表中,如果符合,就啟動對應的服務或 Activity。
Android 系統中的每一個應用程式,在安裝的過程裡,都得事先在 Android 框架中註冊、登記這個應用程式所建立的 Activity,並事先註明會使用到的服務。譬如當我們在 Android 上安裝我們撰寫的 BMI 應用程式時,BMI 應用程式就會向 Android 框架登記相關資訊:BMI 應用程式將會用到 「Bmi」這個 Activity。
這份訊息存在於每個 Android 應用程式專案根目錄下的「AndroidManifest.xml」檔案中。如果我們在程式裡,要用到其他應用程式或服務所提供的功能,也需一併在此列出。
在安裝應用程式的時候,Android 框架會根據應用程式提供的這份清單,將資訊註冊於 Android 框架的註冊表中。
備註:
這麼說其實是不太精確的。Android 應用程式的運作流程,存在於四種載體中:
1. Activity (活動)
2. Broadcast Intent Receiver
3. Service
4. Content Provider
各種載體的相關內容會在後續章節提到時作解說。
預設的 Activity 清單
我們使用 eclipse Android 開發工具打開「BMI/AndroidManifest.xml」檔案。切換到「AndroidManifest.xml」分頁標籤,查看預設的 「BMI/AndroidManifest.xml」檔案原始碼:
1
2
6
7
8
9
10
11
12
13
14
15
我們分行講解如下:
....
「AndroidManifest.xml」這個檔案也是以 XML 格式描述,每個 Android 應用程式都需要一個「AndroidManifest.xml」檔案,每份「AndroidManifest.xml」檔案的開頭都會出現這段敘述。而整個「AndroidManifest.xml」檔案的敘述,都包含在「manifest」(清單)這個主要標籤中。
package="com.demo.android.bmi"
「package」 是「manifest」(清單)標籤的一個特別屬性,範例中的內容可用來標明,這個應用程式的進入點存在於「com.demo.android.bmi」這個名稱空間/路徑中。
android:versionCode="1"
android:versionName="1.0"
「android:versionCode」和「android:versionName」是應用程式版本號。 這兩個屬性是可選的(非必要)。 「android:versionName」是給使用者看的版本號,如「1.0」、「2.0」。「android:versionCode」則是開發者用的內部版本號,一般使用流水號。
...
「manifest」標籤中主要包含一個「application」標籤(備註1)。「application」標籤裡面,定義了所有這個應用程式用到的 Activity、服務等資訊。「application」標籤中的「android:icon」屬性,定義了這個應用程式將顯示在 Android 主畫面中的應用程式圖示。「android:icon="@drawable/icon"」表示應用程式圖示的資源檔存在於 「res/drawable/icon」 中。圖示的大小必須超過 64x64 像素(Pixel)。「application」標籤中的「android:label」屬性可用來指定應用程式將顯示在 Home 主畫面上的名稱。也就是預設剛開好機時,可以從桌面下方拉出的應用程式列表。
...
「application」標籤中所有用到的 Activity ,都要包含在一個個「activity」標籤中(備註2)。 Activity 是 Android 應用程式與使用者互動的主要元素,當使用者開啟一個應用程式,第一個看到的畫面就是一個 Activity。若是一個應用程式中包含多個畫面時,會定義多個不同的 Activity,我們也必須在「application」標籤中,使用多個「activity」標籤,為不同的 Activity 添加描述。如果我們已經在程式碼中定義好了 Activity ,卻忘了在「AndroidManifest.xml」檔案中加入對應的「activity」標籤,那麼在執行中呼叫到這個 Activity 的時候,將無法開啟這個 Activity。
「activity」標籤的「android:name」屬性,指出了這個 Activity 所對應的類別(class)。「activity」標籤中的「android:label」屬性可用來指定應用程式將顯示在 Activity 畫面上方的名稱。也可以在程式碼中透過「setTitle(“名稱”)」來動態修改。
因為在上一層「Manifest」標籤屬性中已經定義了「package="com.demo.android.bmi"」,因此在「activity」標籤的 「android:name」屬性中,「.Bmi」代表著「com.demo.android.bmi.Bmi」的簡寫。也可以寫成「Bmi」,一樣是代表「com.demo.android.bmi.Bmi」這個類別。
「intent-filter」標籤定義了這個「activity」的性質。 「intent-filter」中主要包含了兩個標籤:「action」跟「category」標籤。 「action」標籤中的「android:name」屬性,其內容「android.intent.action.MAIN」表示:這個 Activity 是此應用程式的進入點(就像程式中常見的 main 主程式),開啟這個應用程式時,應先執行這個 Activity。 。常見的還有「android.intent.action.EDIT」等標籤,會在之後章節用上的時候講解。「category」標籤中的「android:name」屬性,其內容「android.intent.category.LAUNCHER」表示:這個 Activity 將顯示在 Launcher 的應用程式列表中。
我們把整個檔案合到一起看,可以總結出這個檔案所傳達的訊息:在「com.demo.android.bmi」路徑下的「Bmi.java」這個檔案中,已定義了一個主要的 Activity; 當我們打開 Android 的時候,顯示的是位於「res/drawable/icon」的圖示。一旦我們按下圖示來啟動這個應用程式,Android 應用程式框架會去尋找到定義了「android.intent.action.MAIN」內容的 「.Bmi」activity,並呼叫執行。
Android SDK 1.1 版之後引入了這條敘述。透過指定這個參數,系統可以依此辨別應用程式是否使用相容的 SDK 版本,好決定能否在這台機器上安裝執行。「1」代表 Android SDK 1.0,「2」代表 SDK 1.1,「3」代表 SDK 1.5。這也是一個可選填的選項。但如果我們的應用程式要發佈出去,一些強勢的通路如 Google Android Market 已規定所有新發佈的應用程式必須指定「android:minSdkVersion」這個參數。
備註1
除了「application」標籤外,還有「uses-permission」(例如允不允許存取SMS、能否存取聯絡簿、相機功能)、「permission」、「instrumentation」等主要標籤。相關的內容在後續章節用到時再一併解說。
備註2
除了「activity」標籤外,對應於 Android 應用程式的運作流程,還有「service」、「receiver」、「provider」等主要元件。相關內容會在後續章節提到時作解說。
參考資料
· Android manifest http://developer.android.com/reference/android/R.styleable.html#AndroidManifest
· Intent Action http://developer.android.com/reference/android/content/Intent.html
AndroidActivity
加入新 Activity
直觀來看,每個 Activity 通常會負責處理一個螢幕的內容(包含介面、選單、彈出對話框、程式動作等)。當我們需要從一個螢幕畫面切換到另一個螢幕畫面的時候,就涉及到了 Activity 切換的動作。 我們可以將 Activity 看成 MVC 模式中的 Control。Activity 負責管理 UI(詳細的UI細節可以由資源檔讀入),並接受事件觸發。
以是否需要與其他 Activity 交換資料來區分,Activity 可以粗分為兩種類型:「獨立的 Activity」與「相依的 Activity」。不同類型的 Activity,其動作也不盡相同:
獨立的 Activity
獨立的 Activity 是不需要從其他地方取得資料的 Activity。只是單純的從一個螢幕跳到下個螢幕,不涉及資料的交換。 從一個獨立的 Activity 呼叫另一個獨立的 Activity 時,我們只要填好 Intent 的內容和動作,使用 startActivity 函式呼叫,即可喚起獨立的 Activity。例如前幾章中,用作開啟特定網頁的 Activity。
相依的 Activity
相依的 Activity 是需要與其他 Activity 交換資料的一種 Activity。相依的 Activity 又可再分為單向與雙向。從一個螢幕跳到下個螢幕時,攜帶資料供下一個螢幕(Activity)使用,就是單向相依的 Activity; 要在兩個螢幕之間切換,螢幕上的資料會因另一個螢幕的操作而改變的,就是雙向相依的 Activity。 與獨立的 Activity 比起來,相依的 Activity 變化更加精采。
我們會在後續章節中,對相依的 Activity 做進一步的說明。
獨立的 Activity
本章將繼續透過改進 BMI 應用程式來講解 Android 應用程式設計。在這個過程中,我們將使用到獨立的 Activity。
這章中所做的改動都是為了介紹獨立的 Activity,而不是為了讓 BMI 程式變得更完整。因此你不妨先將寫好的 BMI 程式先壓縮備份到其他目錄中,再隨著後面的教學繼續探索 Android。
本章的目的是介紹獨立的 Activity,會用到兩個螢幕,因此除了原本的一個 XML 描述檔與一個程式碼檔案之外,我們還會額外再定義一個 XML 描述檔與一個程式碼檔案,以支援第二個螢幕的動作。
要完成獨立的 Activity 的動作,我們要做幾件事:
1. 在程式碼中建立新 Activity 類別檔案
2. 在清單中新增 Activity 描述
3. 在原 Activity 類別中加入 startActivity 函式
程式碼中建立新的 Activity 類別檔案
首先,使用 Navigator 檔案瀏覽視窗,切換到「src/com/demo/android/bmi」資料夾。在「bmi」資料夾中,現存有 「Bmi.java」與「R.java」兩個檔案。我們準備在此建立一個新的 Activity 類別檔案。
在「bmi」資料夾圖示上按右鍵,選擇「New->Class」選項。 Eclipse 會跳出一個「New Java Class」對話框。
在對話框中的「Name」一欄上填入「Report」。「Report」的字頭需大寫,這是 Java 程式語言的默認規則。
在「Superclass」一欄右方,按下「Browse...」,Eclipse 會跳出「Superclass Selection」對話框。在對話框中的「Choose a type」欄位中輸入「activity」,輸入框下方的「Matching items」欄位中,會顯示出所有可能的類別。我們選擇「Activity - android.app - ...」這個選項,點擊右下方的「ok」按鈕,回到上一個對話框。
此時,「Superclass」欄位中將填入「android.app.Activity」訊息。按下對話框右下角的「Finish」鍵,Eclipse 會在「bmi」資料夾中,產生一個對應的「Report.java」檔案。
剛產生(尚未修改過的)的「Report.java」檔案如下:
1 package com.demo.android.bmi;
2
3 import android.app.Activity;
4
5 public class Report extends Activity {
6
7 }
在解讀程式流程一章中,我們已講解過 Android 程式碼的基本架構,即 XML 描述檔與程式碼兩個主要組成部分。稍後我們要處理建立新程式碼的相關工作,包含定義對應的 XML 描述檔,與在程式碼中,填入這個 class 的內涵。
相關工作
在「res/layout」中新增一個「report.xml」檔案,並把描述使用者介面 一章中講解過的 xml 檔案複製一份過來:
1
2
7
12
打開「src/com/demo/android/bmi/Report.java」,把解讀程式流程一章中講解過的預設的程式碼複製進來:
1 package com.demo.android.bmi;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5
6 public class Report extends Activity {
7 /** Called when the activity is first created. */
8 @Override
9 public void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.report);
12 }
13 }
上面的程式碼中,我們將
setContentView(R.layout.main);
換成了:
setContentView(R.layout.report);
以對應我們新定義的 XML 描述檔產生的資源識別符號。
清單中新增 Activity 描述
我們再打開「AndroidManifest.xml」檔案,並切換到「Application」分頁。在「Application」分頁的左下角,我們可以看到「Application Nodes」欄位中,列出目前已在「AndroidManifest.xml」檔案中定義的所有「Activity」。現在我們就來將 「Report」這個新的 Activity 加入到「AndroidManifest.xml」檔案中。
點擊「Application Nodes」欄位右側的「Add...」按鈕,彈出一個小對話框。 選擇「Activity」後,按下「ok」回到「Application」分頁。「Application Nodes」欄位中會增加一個「Activity」項目。選擇這個「Activity」項目後,在「Application Nodes」欄位右方會出現新的「Attributes for Activity」相關欄位。
我們點選 「Name*」欄位右側的 「Browse...」按鈕,開啟另一個對話框。新的對話框中我們可以選擇在程式中現有定義的 Activity。我們選擇「Report - com.demo.android.bmi」後,按 「ok」 鍵回到「Application」分頁。此時「Name*」欄位的內容變成了「Report」,「Application Nodes」欄位中的名稱也更新成「Report(Activity)」了。
activity 標籤的內容
我們從「Application」分頁切換到「AndroidManifest.xml」分頁,查看剛剛的動作實際上作了些什麼事。
我們發現,在原本的 activity 敘述下方,新增了一行名為「Report」的 activity 標籤,完整的「AndroidManifest.xml」清單內容如下:
1
2
4
5
6
7
8
9
10
11
12
13
手動新增 activity 標籤
Android 提供了多種方式來協助我們定義「AndroidManifest.xml」清單檔案,除了使用對話框選擇的方式之外,你也可以在原本的「AndroidManifest.xml」檔案中直接修改原始碼,加入如下敘述:
來得到相同的效果。
修改頁面標題文字
如果希望在打開 Report Activity 頁面時,標題欄上的文字將會是「BMI 報告」,而不是預設跟 Activity 名稱相同的「Report」,我們可以在清單裡的「report activity」標籤中,加入標籤(label)屬性的描述。步驟如下。
1. 在「res/values/」目錄下,新建一個 report.xml 描述檔,存放 Report 活動頁面用到的字串。檔案內容如下:
BMI 報告
2. 有了描述檔後,我們可以再修改「AndroidManifest.xml」清單,為 Report activity 添加標籤(label)屬性:
原 Activity 類別中加入 startActivity 函式
在準備好相關資源、並在「AndroidManifest.xml」清單添加新 activity 描述後,我們來到實際控制整個程式流程的程式碼部份。
在程式碼中,我們要把原本按下按鈕即開始計算,並顯示 Bmi 值到同一螢幕的按鈕動作,改成按下按鈕後跳轉到 「Report」 這個 Activity。
首先修改「BMI」這個 Activity。打開「src/com/demo/android/bmi/Bmi.java」檔案,修改「OnClickListener」函式(定義按下按鈕時做的動作)的內容。按下按鈕後從 Bmi Activity 切換(跳轉)到 Report Activity 的程式片段如下:
1 private Button.OnClickListener calcBMI = new Button.OnClickListener()
2 {
3 public void onClick(View v)
4 {
5 //Switch to report page
6 Intent intent = new Intent();
7 intent.setClass(Bmi.this, Report.class);
8 startActivity(intent);
9 }
10 };
修改好後,開啟 Android 模擬器。當我們按下「計算 BMi 值」按鈕後,螢幕會切換到「Hello World, Bmi」頁面(已經進入了 Report Activity)。在這個頁面上,我們並沒有辦法可以直接回到前一個頁面,因為這兩個頁面都是獨立的 Activity。但我們還是可以透過按下硬體的「Undo」鍵,使螢幕切換回原本輸入身高體重的頁面 (即回到 Bmi Activity)。
講解
Intent intent = new Intent();
我們建立一個新的「意圖」(Intent) 實體。
intent.setClass(Bmi.this, Report.class);
為這個意圖指定來源的 Activity 所在 class,與要前往的 Activity 所在的 class。
startActivity(intent);
將定義好的 intent 傳入「startActivity」函式中。「startActivity」函式會將 intent 傳入 Android 框架,Android 框架會根據各應用程式在系統中註冊的資料(有沒有聯想到我們剛剛為 Report Activity 增加的 activity 清單描述?),找出 Report 這個 Activity,並呼叫它。
搞懂之後,原來呼叫一個獨立的 Activity,所需的功夫其實很單純呀。
AndroidIntent
傳送資料到新 Activity
Intent 是一個動作與內容的集合。Intent 像是一串網址,傳送到系統並意圖靠其他 Activity 來處理網址中所指定的動作跟內容。
前一章中,我們已學過獨立的 Activity。Android 使用 Intent 來完成在螢幕間切換的動作。Intent 包含 Activity 間切換所需的動作、分類、傳送資料等訊息,就像是 Activity 之間的宅急便一樣。
因此當我們得在 Activity 之間交換資料時,需要先了解 Intent 的用法。
Intent 可以分為兩種類型:「現成的 Intent」與「自訂的 Intent」。使用現成的 Intent 的例子,可以參考「初見 Intent」一章。在 Android 清單中作設定時,我們還可以使用 IntentFilter,來過濾和找尋對應的 Intent。而一般開發者在程式中所自行撰寫的 Intent,則是透過自訂 Intent 來做很多事情。比如切換 Activity、在其間傳遞各式的資料。
要完成在 Activity 之間透過 Intent 傳送資訊的動作,可以分成「傳遞資訊」與「接收資訊」兩部分。
使用 Intent 傳遞資訊
上一章的範例中,我們新增了一個 Report Activity 頁面,但是還沒有為新頁面填入實值內容。在本章中我們會完成將 BMI 應用程式從一個頁面改寫成為兩個頁面:「輸入頁面」(原本的 Bmi Activity),與「結果頁面」(Report Activity)的應用程式。「輸入頁面」從介面上取得身高、體重值,透過傳送 Intent,將值攜帶到「結果頁面」。「結果頁面」從 Intent 中取出其攜帶的身高、體重值,用這兩個參數來產生 BMI 報告結果。
打開 「src/com/demo/android/bmi/Bmi.java」,修改「Button.OnClickListener」 函式:
1 private Button.OnClickListener calcBMI = new Button.OnClickListener()
2 {
3 public void onClick(View v)
4 {
5 //Switch to report page
6 Intent intent = new Intent();
7 intent.setClass(Bmi.this, Report.class);
8 Bundle bundle = new Bundle();
9 bundle.putString("KEY_HEIGHT", field_height.getText().toString());
10 bundle.putString("KEY_WEIGHT", field_weight.getText().toString());
11 intent.putExtras(bundle);
12 startActivity(intent);
13 }
14 };
講解
Intent intent = new Intent();
intent.setClass(Bmi.this, Report.class);
...
startActivity(intent);
是的,如果你真的有學懂上一章的內容,那麼你可能會發現:我們準備講解的這段程式碼主體,與上一章中所提到的程式碼其實一模一樣。這段程式碼的作用是透過 Intent 通知系統(Android 框架):我們將要從 Bmi Activity 這個頁面(輸入頁面)前往 Report Activity 頁面(結果頁面)。如果把我們多加的用來附加資料的程式碼拿掉,這段程式碼即原來獨立的 Activity 的程式碼。
Bundle bundle = new Bundle();
...
intent.putExtras(bundle);
相依的 Activity 與獨立的 Activity 不同之處,就在於相依的 Activity 會附帶傳送額外資訊到新的 Activity。這些額外資訊都是靠著 Intent 物件來攜帶的。
傳送 intent 時,我們可以在其上附加一些訊息,比如說本例中我們從輸入介面中取出了的身高、體重值,要將身高、體重值傳送給 Report Activity 後作計算。這些附加在 Intent 上的訊息都儲存在 Bundle 物件中。透過「intent.putExtras(bundle)」敘述,我們將「bundle」 物件附加在 Intent 上,隨著 Intent 送出而送出。
bundle.putString("KEY_HEIGHT", field_height.getText().toString());
bundle.putString("KEY_WEIGHT", field_weight.getText().toString());
這段程式是實際用來附加資料的程式碼。將使用者輸入的身高、體重值,儲存到 bundle 物件中。 Bundle 其實是一種特別定義的映射(map)型別。「KEY_HEIGHT」、「KEY_WEIGHT」是我們為儲存在 bundle 物件中的身高、體重值,所指定的「識別符號」。在這邊,我們直接把身高、體重值都儲存成字串。因為整個程式都是我們控制,到時候在接收的 Activity 一端,再透過「KEY_HEIGHT」、「KEY_WEIGHT」這兩個「識別符號」來取得實際的身高、體重值。讀出的值也是字串,等值讀出來以後,再去做型別轉換就好了。當然你也可以直接把身高、體重值存成數字。
Bundle 型別額外提供了很多 API。在傳送 Intent 時,使用 Bundle 型別的物件來攜帶資料,相當方便。
使用 Intent 接收資訊
在使用 Intent 接收資訊前,我們先來加上「Report」這個 Activity 的介面。
相關工作
打開「res/values/report.xml」檔案,修改如下:
BMI 報告
前一頁
打開「res/layout/report.xml」檔案,修改如下:
這時打開 Eclipse 開發環境的 layout 檢視,或是執行模擬器,我們可以看到一個「前一頁」按鈕,按鈕前其實還有兩個沒有內容的 TextView 介面元件,下一節中我們將在「Report」這個 Activity 中取得從「Bmi」Activity 傳過來的身高體重資料,根據這些資料產生報告資訊。
在 Activity 中解開資訊
用作接收 Intent,透過 Intent 攜帶的資訊來計算出 BMI 值的「src/com/demo/android/bmi/Report.java」完整程式碼如下:
1 package com.demo.android.bmi;
2
3 import java.text.DecimalFormat;
4 import android.app.Activity;
5 import android.os.Bundle;
6 import android.view.View;
7 import android.widget.Button;
8 import android.widget.TextView;
9
10 public class Report extends Activity {
11 /** Called when the activity is first created. */
12 @Override
13 public void onCreate(Bundle savedInstanceState) {
14 super.onCreate(savedInstanceState);
15 setContentView(R.layout.report);
16 findViews();
17 showResults();
18 setListensers();
19 }
20
21 private Button button_back;
22 private TextView view_result;
23 private TextView view_suggest;
24
25 private void findViews()
26 {
27 button_back = (Button) findViewById(R.id.report_back);
28 view_result = (TextView) findViewById(R.id.result);
29 view_suggest = (TextView) findViewById(R.id.suggest);
30 }
31
32 //Listen for button clicks
33 private void setListensers() {
34 button_back.setOnClickListener(backMain);
35 }
36
37 private Button.OnClickListener backMain = new Button.OnClickListener()
38 {
39 public void onClick(View v)
40 {
41 // Close this Activity
42 Report.this.finish();
43 }
44 };
45
46 private void showResults() {
47 DecimalFormat nf = new DecimalFormat("0.00");
48
49 Bundle bunde = this.getIntent().getExtras();
50 double height = Double.parseDouble(bunde.getString("KEY_HEIGHT"))/100;
51 double weight = Double.parseDouble(bunde.getString("KEY_WEIGHT"));
52 double BMI = weight / (height * height);
53 view_result.setText(getString(R.string.bmi_result) +nf.format(BMI));
54
55 //Give health advice
56 if(BMI>25){
57 view_suggest.setText(R.string.advice_heavy);
58 }else if(BMI<20){
59 view_suggest.setText(R.string.advice_light);
60 }else{
61 view_suggest.setText(R.string.advice_average);
62 }
63
64 }
65 }
講解
整個程式的架構我們在「完成 BMI 程式」與「重構程式」兩章中已經詳細說明過了。
Bundle bunde = this.getIntent().getExtras();
當我們透過 Intent 傳到新的 Activity 後,只要使用 Activity.getIntent() 函數,就可以得到傳來的 Intent 物件。然後使用「getExtras」函式,就能取得附加在 Intent 上的 bunde 物件。
double height = Double.parseDouble(bunde.getString("KEY_HEIGHT"))/100;
double weight = Double.parseDouble(bunde.getString("KEY_WEIGHT"));
在當前的 Activity 取得了 bundle 物件後,我們可以透過指定儲存在 bundle 物件中的身高、體重值的識別符號「KEY_HEIGHT」、「KEY_WEIGHT」來取出身高、體重值的資料。由於我們傳參數值來時,所使用的是是字串格式,所以我們在此得做個型別轉換,將參數從字串轉換成雙倍精度浮點數(Double)型別。
private Button.OnClickListener backMain = new Button.OnClickListener()
{
public void onClick(View v)
{
// Close this Activity
Report.this.finish();
}
};
當按下 backMain 按鈕元件後,結束 Report Activity,顯示出原本的 Bmi Activity。
不透過 Bundle 交換資訊
使用 Intent 傳遞資訊時,我們看到可以用「setClass」方法來指定要傳送到的 Activity。我們也可以使用類似的「setString」、「setInt」方法來指定要透過 Intent 附帶傳送的參數。
在使用 Intent 接收資訊時,我們再使用「this.getIntent().getData()」方法就能取到參數了。「getData」方法取到的參數一般是字串型態的。 若事先已經知道傳來參數的型別,還可以用比「getData」方法更精確的「getString」、「getInt」等方法來取得參數值。
不過既然有好用的 Bundle 類別可用,為什麼要自己把事情弄得複雜呢?
AndroidDebug
記錄與偵錯
記錄與偵錯可以分成「在程式中加上除錯訊息」,與「在偵錯環境中查看除錯訊息」兩部分。
在程式中加上除錯訊息
程式幾乎行行都可以出錯。要看程式中的哪一部分可能會出錯,實在是門很深的學問。要是沒有線索,光靠我們的腦袋來追蹤判斷,或是靠直覺東試試、西改改,這種作法就跟使用巫毒術扎娃娃一樣,直到被扎的人哪天身體疼了,就算巫毒作法有效。這樣實在不是一種好的除錯方式。
如果是程式碼語法格式上的問題,我們可以在編譯前,就透過開發工具提供的預先編譯警示,得到提醒並及早改正。在我們改正好這些語法格式上的問題後,開發工具才允許我們實際編譯應用程式。接著,才能將編譯好的應用程式上傳至模擬器,再開始進一步的測試。
除了程式碼語法格式上的問題,絕大部分會造成大麻煩的,是隱藏在程式邏輯中的問題。這些問題只有在模擬器甚至在實際機器上運行時才會出現。為了解決這些問題,我們需要一些協助工具。在 Android 平台上,我們可以透過「Log」函式,來達到自行在程式碼中加入一個個自訂的「記錄點」或「檢查點」。並可以透過開發環境中的「LogCat」工具來查看記錄。當程式流程每次運作到「記錄點」時,相應的「記錄點」就會在開發工具記錄中輸出一筆偵錯用的訊息。開發者透過這份記錄,來檢查程式執行的過程、使用到的參數,是否與我們期望的結果符合。並依此來辨別程式碼中可能出錯的區域,好能對症根治造成問題的程式碼。
導入 Log 函式
打開「src/com/demo/android/bmi/Bmi.java」檔案,我們在程式中加入一些除錯訊息。一段含有記錄點(Log)的程式碼片段如下
import android.util.Log
....
public class Bmi extends Activity {
private static final String TAG = "Bmi";
....
Log.d(TAG, "find Views");
Log.d(TAG, "set Listensers");
講解
就像許多人在學生時代k書時,會在課本上使用不同顏色作記號。用不同顏色的色筆,來代表各段課文不同的重要性或是意義。「Log」函式的作用,就像是色筆一樣,協助我們在程式碼中「作記號」,這些數位記號,會在稍後就介紹到的「LogCat」工具中顯示。
Log 的使用格式如下
Log.代號(標籤, 訊息);
代號
依據訊息的類型,我們有五種 Log 訊息形式可以用作記錄。
1. Log.v (VERBOSE) 詳細訊息
2. Log.d (DEBUG) 除錯訊息
3. Log.i (INFO) 通知訊息
4. Log.w (WARN) 警告訊息
5. Log.e (ERROR) 錯誤訊息
一般較常用的是 Log.d(除錯訊息) 、Log.w (警告訊息),和 Log.e (錯誤訊息)。範例中多使用 Log.d(除錯訊息) 。
標籤
private static final String TAG = "Bmi";
....
Log.d(TAG, "find Views");
Log.(v,d,i,w,e) 的第一個參數,是一個自定的記錄標籤。在目前的 BMI 應用程式範例中,我們還看不太出來自定記錄標籤的意義。但是當程式的功能一擴張的時候(例如像在 AppDemos 範例那樣,包含各種不同功能),我們可以為不同的功能,給予不同的紀錄標籤。
訊息
Log.d(TAG, "find Views");
在 Log.(v,d,i,w,e) 的第二個參數中,加入我們想要記錄的資訊。
實際應用
在 BMI 應用程式中,我們可以在用來處理輸入錯誤的「try...catch」語句中加入「Log」訊息,好讓我們得以從記錄資料中,追蹤到輸入錯誤的情況。
public class Bmi extends Activity {
private static final String TAG = "Bmi";
....
catch(Exception err)
{
Log.e(TAG, "error: " + err.toString());
Toast.makeText(Bmi.this, getString(R.string.input_error), Toast.LENGTH_SHORT).show();
}
講解
catch(Exception err)
Log.e(TAG, "error: " + err.toString());
....
}
「Log.e..」敘述的意思是:根據「catch」到的例外型別的資訊(Exception err),將資料印出到記錄中。
其他的記錄標籤方式
我們也不是一定得為每個 TAG 事先定義好一個記錄標籤,我們可以用當前的 Activity 名稱來做為記錄標籤:
Log.e(this.toString(), "error: " + err.toString());
延伸運用
在實作錯誤訊息提示前,我們其實可以使用 Log.e 函式,來先將錯誤訊息記錄起來,等到整個程式大致底定了,再來用 Toast 或 AlertDialog 元件,來實作輸入錯誤提示的功能。
在偵錯環境中查看除錯訊息
在程式中加上除錯訊息後,我們可以使用除錯模式 (Debug Mode) 運行模擬器,並透過開發工具來查看除錯訊息。
偵錯工具的正式名稱為 Dalvik Debug Monitor Service (DDMS)。
啟動模擬器
使用除錯模式 (Debug Mode) 運行模擬器(選單列->Run->Debug History->BMI)。
切換到偵錯環境配置
點選開發環境右上角的 "Open Perspective"按鈕,選擇 "Other..."選項。選擇後會彈出一個「Open Perspective」(開啟環境配置)對話框。對話框中列出了所有可用的環境配置列表,選擇 "Debug"。此時,右上角的環境配置圖示列中,會多出一個「Debug」環境配置圖示。整個開發工具的介面配置也為之一變。在右上角的環境配置圖示列中,點選「Java」環境配置圖示,就會回到我們原來的介面配置。
現在先切換到「Debug」環境配置,可以看到右下角的「LogCat」視窗。其上有五個醒目的 V、D、I、W、E 圖示,分別代表著五種 Log 形式(Verbose, Debug, Info, Warn, Error),還有一個綠色的「+」號,與一個紅色的「-」號。
模擬器運行時會產生很多的訊息記錄(Log),一不注意就看到眼花了。這時候,我們自訂的記錄標籤(範例中自訂的標籤是「Bmi」)就派上了用場,正好可以為 LogCat 加上一個過濾器(Log Filter),只顯示與「Bmi」標籤相關的訊息記錄。
加入訊息記錄過濾器(Log Filter)
在「LogCat」視窗右側,按下綠色的「+」號,會彈出一個「Log Filter」視窗。在「Log Filter」視窗的「by Log Tag」欄位中填入「Bmi」,並填入任意的「Filter Name」後,按下「ok」按鈕。「LogCat」視窗上會多出一個與我們填入的「Filter Name」相同的標籤。裡面的內容,即所有標示為「Bmi」的自訂訊息記錄。
參考資料
· ddms http://code.google.com/android/reference/ddms.html
· debug http://code.google.com/android/intro/tutorial-extra-credit.html
· trace view http://code.google.com/android/reference/traceview.html
LifeCycle
活動的生命週期
維護一個 Activity 的生命週期非常重要,因為 Activity 隨時會被系統回收掉。
生命週期
作者在初級章節中一直努力地傳達給讀者:編寫 Android 平台的基本應用程式,跟編寫桌面應用程式的難度,兩者並沒什麼不同。甚至因為 Android 平台擁有免費、跨平台的開發工具,使得 Android 平台應用程式的開發更為單純。
但是請別忘了,Android 平台也是個手機作業系統。撇掉其他功能不談,手機的特性,就是應該能隨時在未完成目前動作的時候,離開正在使用的功能,切換到接電話、接收簡訊模式...而且在接完電話回來應用程式時,還希望能看到一樣的內容。
現在使用者使用智慧型手機,大多已習慣使用多工(Multi-Task)的作業系統(如 Windows Mobile),可以在用手機聽音樂的同時,也執行其他多個程式。同時執行多個程式有它的明顯好處,但是也有它的嚴重的缺點。每多執行一個應用程式,就會多耗費一些系統記憶體。而手機裡的記憶體是相當有限的。當同時執行的程式過多,或是關閉的程式沒有正確釋放掉記憶體,執行系統時就會覺得越來越慢,甚至不穩定。
為了解決這個問題, Android 引入了一個新的機制 -- 生命週期 (Life Cycle)。
行程
應用程式(一個個 Activity)執行的狀態稱為行程(process)。在 Android 作業系統中,每個應用程式都是一個行程。Android 系統平台(準確的說是 Dalvik 虛擬機)會維護一個唯一的 Activity 歷史記錄堆疊,並從旁觀察每個應用程式行程。系統平台會依照系統的記憶體狀況,與 Activity 的使用狀態,來管理記憶體的使用。
Activity 類別除了負責運行程式流程,與操作介面元件之外,最重要的,就是它提供了開發者控制行程生命週期的函式。我們已經相當習慣在 OnCreate (建立行程時的行為)函式中,加入我們對這個 Activity 執行流程的控制。在前面遇到的範例中,我們並不需要對除了 OnCreate 之外的行為做出改變。不過理解行程的生命週期,將為我們繼續深入 Android 開發打下基礎。
為什麼要了解生命週期
Android 應用程式的生命週期是由 Android 框架進行管理,而不是由應用程式直接控制。
通常,每一個應用程式(入口一般會是一個 Activity 的 onCreate 方法),都會佔據一個行程(Process)。當系統記憶體即將不足的時候,會依照優先級自動進行行程(process)的回收。不管是使用者或開發者,都無法確定的應用程式何時會被回收。
一個 Activity 類別除了 OnCreate 函式之外,還預先定義了 OnPause(暫停行程時的行為)、OnResume(繼續行程時的行為)等等的基本行為,當從一個 Activity 切換到另一個 Activity 的時候,原本的 Activity 將經過一連串的狀態改變。開發者可以在程式中添加一些各狀態相對應的流程,每次 Activity 改變狀態時,就會執行相對應的流程。
要讓使用者有好的使用經驗,Activity 需要在各個週期點上負責保管狀態、恢復狀態、傳送資料等工作。
Activity 的狀態
Android 的虛擬機(VM)是使用堆疊 (Stack based) 管理。主要有四種狀態:
· Active (活動)
· Paused (暫停)
· Stopped (停止)
· Dead (已回收或未啟動)
Active (活動)
「Active」狀態是使用者啟動應用程式或 Activity 後,Activity 運行中的狀態。
在 Android 平台上,同一個時刻只會有一個 Activity 處於活動(Active)或運行(Running)狀態。其他的 Activity 都處於未啟動(Dead)、停止(Stopped)、或是暫停(Pause)的狀態。
Paused (暫停)
「Paused」狀態是當 Activity 暫時暗下來,退到背景畫面的狀態。
當我們使用Toast、AlertDialog、或是電話來了時,都會讓原本運行的 Activity 退到背景畫面。新出現的Toast、AlertDialog等介面元件蓋住了原來的 Activity 畫面。Activity 處在「Paused」狀態時,使用者無法與原 Activity 互動。
Stopped (停止)
「Stopped」狀態是有其他 Activity 正在執行,而這個 Activity 已經離開螢幕,不再動作的狀態。
透過長按「Home」鈕,可以叫出所有處於「Stopped」狀態的應用程式列表。
在「Stopped」狀態的 Activity,還可以透過「Notification」來喚醒。「Notification」會在後面章節中解說。
Dead (已回收或未啟動)
「Dead」狀態是 Activity 尚未被啟動、已經被手動終止,或已經被系統回收的狀態。
要手動終止 Activity,可以在程式中呼叫「finish」函式。我們在加入選單一章中已經提到過了。
如果是被系統回收,可能是因為記憶體不足了,所以系統根據記憶體不足時的回收規則,將處於「Stopped」狀態的 Activity 所佔用的記憶體回收。
記憶體不足時的行為
記憶體不足時,Dalvik 虛擬機會根據其記憶體回收規則來回收記憶體:
1. 先回收與其他 Activity 或 Service/Intent Receiver 無關的行程(即優先回收獨立的Activity)
2. 再回收處於「Stopped」狀態的其他類型 Activity(在背景等待的Activity)。最久沒有使用的 Activity 優先回收(比較官方的說法是 "根據 LRU 演算法...")
3. 還不夠?回收 Service 行程
4. 快不行啦,關掉可見的 Activity/行程
5. 關閉當前的 Activity
當系統缺記憶體缺到開始「4. 關掉可見的 Activity/行程」時,大概我們換機子的時機也早該到啦!
觀察 Activity 運作流程
講了這麼多虛的,我們可以寫一些程式來直觀查看 Activity 的運作流程嗎?
當然可以。在上一章記錄與偵錯 (Log)中,我們學到的「Log」工具,正好可以在查看 Activity 的運作流程時派上用場。
打開「src/com/demo/android/bmi/Bmi.java」,在程式中加入一些「Log」記錄點:
public class Bmi extends Activity {
private static final String TAG = "Bmi";
public void onCreate()
{
super.onCreate(...);
Log.v(TAG,"onCreate");
}
public void onStart()
{
super.onStart();
Log.v(TAG,"onStart");
}
public void onResume()
{
super.onResume();
Log.v(TAG,"onResume");
}
public void onPause()
{
super.onPause();
Log.v(TAG,"onPause");
}
public void onStop()
{
super.onStop();
Log.v(TAG,"onStop");
}
public void onRestart()
{
super.onRestart();
Log.v(TAG,"onReStart");
}
public void onDestroy()
{
super.onDestroy();
Log.v(TAG,"onDestroy");
}
}
講解
我們為 Activity 的各個狀態加入了「Log」記錄訊息。當模擬器運行時,我們可以透過 「LogCat」工具來查看 Activity 所處在的狀態。
上面的七個狀態又可以歸納成三組:
1. 資源分配 (Create/Destroy)
完整的 Activity 生命週期由「Create」狀態開始,由「Destroy」狀態結束。 建立(Create)時分配資源,銷毀(Destroy)時釋放資源。
2. 可見與不可見(Start/ReStart/Stop)
當 Activity 運行到「Start」狀態時,就可以在螢幕上看到這個 Activity。相反地,當Activity 運行到「Stop」狀態時,這個 Activity 就會從螢幕上消失。
當使用者按下 Back 按鈕回到上一個 Activity 時,會先到 Restart 狀態,再到一般的 Start 狀態。
3. 使用者能否直接存取螢幕(Resume/Pause)
當有個 Toast、AlertDialog、簡訊、電話等訊息亂入時,原來的 Activity 會進入「Pause」狀態,暫時放棄直接存取螢幕的能力,被中斷到背景去,將前景交給優先級高的事件。當這些優先級高的事件處理完後,Activity 就改進入「Resume」狀態,此時又直接存取螢幕。
Activity 運作流程
由實際運行的記錄來看,我們可以歸納出所有 Android 應用程式都遵循的動作流程:
一般啟動
onCreate -> onStart -> onResume
啟動一個 Activity 的基本流程是:分配資源給這個 Activity(Create 狀態),然後將 Activity 內容顯示到螢幕上(Start 狀態)。在一切就緒後,取得螢幕的控制權(Resume 狀態),使用者可以開始使用這個程式。
呼叫另一個 Activity
onPause(1) -> onCreate(2) -> onStart(2) - onResume(2) -> onStop(1)
這是個先凍結原本的 Activity,再交出直接存取螢幕能力(Pause 狀態)的過程。 直到 Activity 2 完成一般啟動流程後,Activity 1 才會被停止。
回原 Activity
onPause(2) -> onRestart(1) -> onStart(1) -> onResume(1) -> onStop(2) -> onDestroy(2)
點 Back 按鈕可以回到原本的 Activity。
退出結束
onPause -> onStop -> onDestroy
如果程式中有直接呼叫「finish」函式來關閉 Activity的話,系統假設我們很確定我們在做什麼,因此會直接跳過先凍結(Freeze)的階段,暫停(Pause),停止(Stop),然後銷毀(Destroy)。
回收後再啟動
onCreate -> onStart -> onResume
被回收掉的 Activity 一旦又重新被呼叫時,會像一般啟動一樣再次呼叫 Activity 的 onCreate 函式。
當我們使用「Android」手機一陣子,在手機上已經執行過多個應用程式。只要按下「Back」(返回)鍵,「Android」就會開啟最近一次開啟過的 Activity。
這時我們要是按下多次「Back」(返回)鍵,理論上遲早會返回到某個已經銷毀(Destroy)的 Activity。這時會發生什麼事呢?
如果應該開啟的 Activity 已經被回收了,那麼這個 Activity 會再次被建立(Create)出來。再次被建立出來的 Activity,當然會跟原本我們開啟過的 Activity 不一樣啦。
所以如果要讓再次被建立出來的 Activity 看起來跟原本開啟過的一樣,那麼在 Activity 之間切換時,我們就要留意保留資料:最好在每次 Activity 運行到「onPause」或「onStop」狀態時先保存資料,然後在「onCreate」時將資料讀出來。
我們可以使用下章介紹到的「Preference」(偏好設定)等方法來記錄之前運作時的資料或設定。
參考資料
· Activity Cycle
· Activity 的生命週期 http://blog.csdn.net/sharetop/archive/2007/12/16/1941935.aspx
AndroidPreference
儲存資訊
我們都知道,一般人身高的變化程度,比起體重的變化程度小的多。
因此就設計一款 BMI 計算程式來說,如果能在使用者第一次輸入身高體重值後,程式能幫我們預先記住上次輸入過的身高,那麼等到下次啟動程式時,便只需要輸入體重。這麼一來,減少了使用者重複輸入的麻煩,在使用上就更方便了。使用者應該會喜歡這個便利的功能吧。
使用偏好設定
打開「src/com/demo/android/bmi/Bmi.java」,在「onCreate」和「onPause」中加入「Preference」(偏好設定)相關的程式碼。完整的程式碼如下:
public class Bmi extends Activity {
private static final String TAG = "Bmi";
public static final String PREF = "BMI_PREF";
public static final String PREF_HEIGHT = "BMI_Height";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
findViews();
restorePrefs();
setListensers();
}
// Restore preferences
private void restorePrefs()
{
SharedPreferences settings = getSharedPreferences(PREF, 0);
String pref_height = settings.getString(PREF_HEIGHT, "");
if(! "".equals(pref_height))
{
field_height.setText(pref_height);
field_weight.requestFocus();
}
}
......
@Override
protected void onPause(){
super.onPause();
// Save user preferences. use Editor object to make changes.
SharedPreferences settings = getSharedPreferences(PREF, 0);
settings.edit()
.putString(PREF_HEIGHT, field_height.getText().toString())
.commit();
}
講解
取得偏好設定
// Restore preferences
private void restorePrefs()
{
SharedPreferences settings = getSharedPreferences(PREF, 0);
String pref_height = settings.getString(PREF_HEIGHT, "");
if(! "".equals(pref_height))
{
field_height.setText(pref_height);
field_weight.requestFocus();
}
}
我們在「onCreate」函式中,加入一行 「restorePrefs」呼叫。並在「onCreate」函式外,再定義一個「restorePrefs」函式如上。
SharedPreferences settings = getSharedPreferences(PREF, 0);
我們宣告了一個偏好設定(SharedPreferences)型別「settings」,並使用「getSharedPreferences」函式,來尋找系統中有無符合以「BMI_PREF」字串(PREF 參數)作為檔名的偏好設定檔。如果有符合條件的偏好設定檔存在的話,就將這個偏好設定指定使用「settings」作為代號來操作。如果沒有的話,「getSharedPreferences」函式會回傳 0 給「settings」。
String pref_height = settings.getString(PREF_HEIGHT, "");
我們可以透過「getXXX」函式,來從偏好設定(SharedPreferences)讀取不同型別的內容。例如本例中使用「getString」來讀取文字類型的資訊。當「PREF_HEIGHT」偏好設定參數存在時,字串「pref_height」就會得到偏好設定參數的內容。如果不存在「PREF_HEIGHT」這個偏好設定參數時,字串「pref_height」則會得到一個空字串。
if(! "".equals(pref_height))
{
pref_height.setText(pref_height);
...
}
當「pref_height」字串存在時,我們將 field_height 欄位內容設定成偏好設定參數中取出的值。
field_weight.requestFocus();
同時,因為身高欄位已經預先填好了,使用者只需要再填入體重值即可開始計算自己的 BMI 值。但是當程式一執行,預設的焦點欄位(游標)還是停在「身高」欄位上。因此我們可以在「field_weight」欄位識別符號上,使用「requestFocus」函式,來手動將焦點欄位改到「體重」欄位上。這樣當使用者要輸入時,如果之前已經輸入過「身高」,那麼程式就會自動幫忙填好上次輸入的身高,並把焦點欄位設置到「體重」欄位上,使用者只需直接輸入體重數字就可以了。
如果只加入了「取得偏好設定」這段的程式碼,就運行模擬器來看看結果,會發現我們寫在「restorePrefs」函式中的程式碼,目前都還沒有發生作用。這是因為我們尚未在程式中儲存任何偏好設定。接著,我們將在程式中加入儲存偏好設定的程式碼,好能在開啟 Activity 時讀到偏好設定。
儲存偏好設定
@Override
protected void onPause(){
super.onPause();
// Save user preferences. use Editor object to make changes.
SharedPreferences settings = getSharedPreferences(PREF, 0);
settings.edit()
.putString(PREF_HEIGHT, field_height.getText().toString())
.commit();
}
當我們使用「Home」、「Back」按鈕或其他方式離開當前的 Activity 時,我們才把身高的值儲存到偏好設定中。根據上一章活動的生命週期,我們知道停止當前螢幕動作的狀態是「onPause」狀態。因此我們覆載(Override)了「onPause」函式,在其中加入儲存身高偏好設定的程式碼。「super.onPause」的作用是先將原本的「onPause」函式執行一遍。
SharedPreferences settings = getSharedPreferences(PREF, 0);
我們宣告了一個偏好設定(SharedPreferences)型別「settings」,並使用「getSharedPreferences」函式,來尋找系統中有無符合以「BMI_PREF」字串(PREF 參數)作為檔名的偏好設定檔。如果有符合條件的偏好設定檔存在的話,就將這個偏好設定指定使用「settings」作為代號來操作。如果沒有的話,「getSharedPreferences」函式會回傳 0 給「settings」。
settings.edit()
.putString(PREF_HEIGHT, field_height.getText().toString())
.commit();
在此我們串接了三個 settings 擁有的函式:「edit」、「putString」,和「commit」。 要改變偏好設定(SharedPreferences)型別的內容,需要透過「edit」函式來編輯。編輯結束後,要透過「commit」函式來將改變寫到系統中。我們可以透過「putXXX」函式來為偏好設定(SharedPreferences)填入不同型別的內容。例如本例中使用「putString」來寫入文字類型的資訊(讀者也可以試試用 putInt 或 putFloat 函式來直接將身高值儲存成整數或浮點數)。
本例中「putString」函式所執行的動作,是透過「field_height」介面元件識別符號來取得身高的字串後,將字串儲存到「PREF_HEIGHT」所代表的偏好設定參數中。
參考資料
http://code.google.com/android/devel/data/preferences.html
AndroidMarket
發佈到市集(Market)
Android Market
要釋出程式讓所有使用者使用有三種方式:
1. 發佈到 Android Market
2. 自己提供程式線上下載
3. 發佈到第三方 Android 應用程式下載網站
「Android Market (市集)」是一個「Android」官方(Google)提供的「Android」應用程式下載網站,同時也內建於所有的「Android」手機中。透過手機上的「Market」程式,使用者可以直接在「Android」手機上瀏覽「Android Market」網站,查看各種可供使用的應用程式。看到喜歡的程式可以直接下載安裝。也可以透過「Android Market」為這些軟體打分、或是交換對這些軟體的意見。
我們也可以將寫好的應用程式放在自己的網站上提供下載,或是透過其他的「Android」應用程式下載網站發佈。但是,還有哪個地方會比官方的「Android Market」更容易吸引使用者造訪呢?所以我們將主要介紹如何將應用程式發佈到官方「Android Market」上。
Android Market 的運作方式
「Android Market」的運作方式如下
· 開發者可以將自己寫好的軟體上傳到 Android Market 中。
· 開發者透過 Android Market 販賣軟體的 30% 收入,得分給電信商跟電子收費商(如手機月費帳單或 Google Checkout 等),所以開發者可以拿到應用程式定價的 70%。
· 註冊為「Android Market Developer」要收美金 25元的「入場費」。推測可能是種為了保證「Android Market」上應用程式的質量,也為了促使開發者寫一點收費軟體,好讓電信商有得分成的策略。
註冊 Android Market
前往 http://www.android.com/market/ ,畫面右上角有一段「Interested in having your application in Android Market?」敘述,按下其下方的「learn more」按鈕,即可開始註冊成為「Android」開發者。
開發者用的網址是 http://market.android.com/publish
開發者可以透過「Android Market」發佈「Android」應用程式。首先,開發者得註冊一個 Google 帳號。然後使用(Google Checkout)以信用卡付出 $25 美元的註冊費用。 最後得同意「Android Market」的使用授權協議。
註冊一個 Google 帳號不難,相信大部分讀者都已經擁有一個 Google 帳號。
在申請「Android Market」時要填入加上國碼的手機號碼。台灣加上國碼的手機號碼為「+8869xxxxxxxx」。「886」是國碼,加上一個「0」之後,「09xxxxxxxx」是你的手機號碼。「+」則是「加上國碼的手機號碼」表示方式。 接著按下「Google Checkout」圖示,如果沒有「Google Checkout」的話,也需作先設定。一切完成後在「Google Checkout」中勾選「I agree and I am willing to associate my credit card and account registration above with the Android Market Developer Distribution Agreement.」。畫面會出現「等待信用卡認證的訊息」,並有 「Google Checkout」的確認函寄到我們設定的電子信箱中。接著想要繼續登錄開發者網頁時,會發現這個網頁似乎壞掉了。其實是等待信用卡認證完成,需要一點時間(一兩個鐘頭),等認證好,完成付款程序後,網頁就能再次開啟。
開啟後會出現「Your Registration to the Android Market is approved!You can now upload and publish software to the Android Market.」(已經註冊完成)訊息。以後點擊「Android Market」網頁右上角的按鈕時,就會進入開發者面板(Developer Console)頁面。
在開發者面板畫面的左上角是開發者的暱稱。暱稱旁邊可以選擇「Edit profile » 」(編輯個人資料)來編輯之前填入的「Android Market Developer」資訊。
上傳應用程式到 Android Market
選擇右下角的「Upload Application」(上傳應用程式)按鈕,出現應用程式上傳畫面。各個欄位的作用都寫的很明白,也可以為應用程式自行定價。
「Android Market」上所有的程式可分為「應用程式」與「遊戲」兩大類。選擇好大分類後,其下會出現各自可選的子分類。 在「Upload assets」區塊中,點選「Application .apk file」旁的「瀏覽...」按鈕,就可以上傳已經簽署好金鑰的「.apk」程式。(本書還未提及怎麼釋出簽署金鑰的應用程式)
直接選擇「BMI/bin/」目錄中的「BMI.apk」的話,會出現
Market does not accept apks signed with the debug certificate. Create a new certificate that is valid for at least 50 years.
Market requires that the certificate used to sign the apk be valid until at least October 22, 2033. Create a new certificate.
這段警告訊息。意思是說我們要上傳的「apk」檔用的是「debug」的授權金鑰,這樣是不能用做發佈的,我們得要自行簽署金鑰才成。
如果改選擇透過「AndroidManifest.xml」的「Overview」頁籤中「Exporting the unsigned .apk」連結,會出現訊息
The apk is not properly signed.
如果驗證成功,該欄位上會直接出現該應用程式圖標(icon),與所需的存取權限(permissions)數目。
最後按下左下方的「Publish」按鈕,即可將應用程式發佈到「Android Market」上。
檢視成果 - 查看管理介面
「Android Market」的開發者面板(Developer Console)頁面上,列出了開發者當前已發佈與未發佈的應用程式名稱與圖標。應用程式名稱右側有明顯的星號,表示目前的使用者評價。星號旁邊的括號表示當前已給予評價的人數。星號的右方是該程式的定價。最右側則是應用程式狀態,已發佈的應用程式狀態是「Published」。還未發佈的應用程式狀態是「Saved Draft」。
目前只有透過「Android」手機,才能查看關於應用程式的評論。
線上查看評論可以透過 cyket查看。
程式熱門程度排名可以透過 aTrackDog 和 AndroidStats 等網站查看。
自行提供程式線上下載
要自行提供程式線上下載的話,需要指定下載檔案的 MIME 類型。可以在「Apache」網頁伺服器的「.htaccess」設定中加入:
AddType application/vnd.android.package-archive apk
一行,如此一來使用者在瀏覽器中點選到「.apk」檔的連結時,瀏覽器能自動辨識該檔案為「Android」應用程式類型。
發佈到第三方 Android 應用程式下載網站
請自行參考「參考資料」中的「其他的 Android 應用程式下載網站」。
針對使用者作設計
針對使用者作設計,有沒有意義呢?每個人都有自己的一套道理,不如就用數據來說話吧。
在「Android Market」開放給開發者上傳應用程式的第一天(美國時間10/27),作者即將本書中的兩個範例程式「aBMI」(英制)(本章的範例)、「gBMI」(公制)(基礎、中階的範例)上傳到「Android Market」上。考慮到當時使用者(美國)主要集中在使用英制的國家,因此預期「aBMI」應用程式會得到比較好的評價。
果然,在第一天結束之後,「aBMI」(英制)得到 732 次下載,目前「active installs」(仍安裝在機器上)的人數為 452 人(比率 61%)。共有 25 個人平均給予 3 顆星的評價。就一個運作相當簡單的應用程式而言,比起其他書籍範例的完成度,3 顆星的評價還是算相當可接受的。
至於「gBMI」(公制)則因為不是針對目標使用者設計,得到 602 次下載,「active installs」的人數為 193 人(比率 32%)。只有 11 個人平均給 2 顆星的評價。
因此可以明顯看到,「gBMI」不論是下載的人數、安裝後繼續使用的比率,或是整體評價都要比「aBMI」差一個檔次。當 Android 手機在使用「公制」的國家開賣後,相信比例或評價會再次變化。
我們在設計兩個應用程式時,同樣需花上差不多的時間,但是卻得到有相當明顯差別的結果。由此可以看出,手機應用程式需針對使用者特性來設計的重要性。
參考資料
· Android Market http://www.android.com/market/
· Signing and Publishing Your Applications http://code.google.com/android/devel/sign-publish.html
· http://docs.sun.com/app/docs/doc/820-4607/ablqz?l=zh_TW&a=view
· http://www.anddev.org/viewtopic.php?p=12252
· http://keytool.sourceforge.net/
其他的 Android 應用程式集散地
· AndAppStore http://andappstore.com/
· MobiHand OnlyAndroid http://onlyandroid.mobihand.com/
· SlideMe http://www.slideme.org/
· 各種 Android 市集一覽表
NeverEnd
開發不息
一個學習階段的結束,就是另一個學習階段的開始。
我們來回顧前面所學,並重新檢視我們對 BMI 應用程式所做的設計。
在「設計使用者介面」一章,我們這樣描述BMI應用程式所需的介面元件:「為了輸入 BMI 程式所需的身高體重值,大致上我們需要兩個 TextView 元件用來提示填入身高體重數字,另外也需要兩個文字輸入欄位用來填入身高體重數字。我們還需要一個按鈕來開始計算,而計算完也需要一個 TextView 元件來顯示計算結果。」
我們根據上述的設計,撰寫了 BMI 應用程式,並打算發佈到 Android Market 上。
BMI 應用程式的功能算是很完整了。但是仔細想想,可能你也會意識到,現在的BMI 應用程式其實存在一些不對勁的地方。
嗯,請讀者們花點時間想一想。
什麼地方不對勁呢?
BMI 應用程式設計的缺陷
首先,「應用程式的目的應該是寫給使用者使用的。」應該沒有人有異議。 那麼本書寫作的階段, Android Market 才準備開放,第一批的 Android 手機(T-Mobile G1)才剛寄送到第一批使用者手裡,這些使用者期望的應用程式,應該是會是什麼樣子呢?
......
喔!我們的第一批使用者是一堆美國人!
讀者:美國人又怎樣?
作者:噫,再仔細想想,並回來檢視看看我們的應用程式介面吧。
讀者:哇哇哇,BMI 應用程式的使用者介面,用的是「中文」!
作者:不錯,讀者您已經具備獨立思考的能力了。
讀者:知道問題後,這其實很好辦,只要把「res/value/string.xml」中的字串換成英文,重新編譯一下就好了,不是什麼大問題啦(挺胸)。
作者:嘿嘿,我鋪這麼久的梗,BMI 應用程式怎麼可能只存在這個問題哩(魔王貌),再從「美國人」這個關鍵點再想想。
讀者:...... 我知道了,在BMI應用程式中,我們使用的身高體重單位是公分、公斤,但是在看美國 NBA 時,那些人的身高體重好像是用不一樣的度量單位算的!作者,你你你...好毒呀(捧心)!
作者:還是被你看出來啦。
下一階段
下一階段的學習,就是試著將 BMI 應用程式改成目標使用者「美國人」所適用的 BMI 應用程式(英制)。在下一階段中,除了複習前面章節用到的技巧外,還會講到如何支援多國語言,並用到 spinner、 List、ArrayAdapter 等等元件來改善操作可用性。以此來鞏固我們所學到的 Android 應用程式設計知識。
結語
本書的入門、初階、中階章節講解了如何撰寫 Android 介面、程式碼、如何新增選單,與應用程式如何在多個 Activity 之間切換等內容。了解了這些內容,已足夠我們寫出一般的 Android 應用程式。
Android 是個完整的平台,還有諸多內容值得研究。例如應用程式間共享內容、整合網路服務、瀏覽器離線應用程式、相機與條碼、動作感應、手勢感應、3D、繪圖、遊戲、影音、通話與簡訊等等進階的主題。
希望讀者能以本書出發,開發出自己的 Android 程式。
在電子書完稿後,筆者出版的實體書接續著本電子書的主題。在後續的主題中,透過二次開發英制版本的 BMI 程式,再次帶領讀者進入稍深一層的 Android 開發,並實際帶讀者走了一遍釋出程式時應作的動作。最後附上資料庫應用、地圖與定位應用兩大主題,協助讀者了解「Android」平台上完整的儲存資料方式、與如何應用地圖與定位服務(請參看後續章節)。
請別吝於留言反應,寫出自己的讀後感言、提出覺得不解的地方、分享自己的經驗。這些都可以協助你更好的學習 Android。如果覺得有那些小地方漏講,也請您直接在該章後加上您的建議。協助其他人,就是協助當初的自己。
版權宣告
誠摯地提醒您,本書的文字、圖片、程式碼皆不歡迎轉載,也不可使用於商業用途。 但歡迎您將本書網址http://code.google.com/p/androidbmi/wiki/IntroAndroid 分享到各網站。
填寫讀後問卷
如果您花時間讀了本書,請幫忙填寫只有4道的讀後問卷,以協助改善本書。
因為 Android 在一年多的時間內出了多個版本,電子版有些不及更新之處,請您理解。 遇到這種情況,請您直接在文章下方回文指出,筆者會盡量修改。
實體書的讀者請填寫讀者問卷,可留言或回報錯誤。
附件
AndroidResource
Android 相關資源
· [http://www.android.com/ Android 官方網站
· Android Market
· Android 線上文件
· RoadMap,最新的 Android 平台開發進度
· Planet Android 部落格文章收集地
· Google Android 開發者部落格
· Android 已上市機種一覽表
· Anddev 論壇,有許多教程
· 30 Days of Android Apps
· Android 開發工具功用介紹
· Android 參考文件列表
· Android 介面元件列表
· Android 系統與工具相關原始碼
· Android 原始碼網站
· 官方範例
書籍或文章
· Android 書籍列表
· java 語言教學 (英文)
· IBM Android 教學(簡體)
· SQL 語法
· 2D Game 教學(英文)
· 3D Game 教學(英文)
工具
· DroidDraw GUI 拖拉設計工具
· positron 自動測試工具
· SD Card Image 使用方法 http://www.android123.com.cn/moniqi/48.html
· Android 應用程式下載 http://andappstore.com/
· Eclipse 開發環境中文化 http://download.eclipse.org/technology/babel/update-site/ganymede
函式庫
· 掃條碼 ZXing - Barcode reader http://code.google.com/p/zxing/
· 文字轉語音 Eyes Free - TTS http://code.google.com/p/eyes-free/
· 雷達圖顯示 Radar http://code.google.com/p/apps-for-android/
· XML-RPC - http://code.google.com/p/android-xmlrpc/
· PhoneGap http://phonegap.com/
· 擴增實境 AR http://www.noritsuna.com/archives/2008/11/nyartoolkitandroid.html
· 動態語言環境 http://code.google.com/p/android-scripting/
· 線上 OCR http://www.bitquill.net/trac/wiki/Android/OCR
· OCR http://code.google.com/p/mezzofanti/
· Remote Control PC http://sourceforge.net/apps/trac/android-tesla/
· BlueTooth http://code.google.com/p/android-bluetooth/
· Chart http://code.google.com/p/chartdroid/
· 第三方 intent 列表 http://www.openintents.org/en/intentstable
· DRM http://andappstore.com/AndroidApplications/licensing.jsp
· Google Analytics http://code.google.com/intl/zh-TW/apis/analytics/docs/tracking/mobileAppsTracking.html
其他開發
· Native C development http://blog.sina.com.cn/s/articlelist_1242184131_6_1.html
Gears
· Gears Best Practice
遊戲
· http://sites.google.com/site/drpaulthomasandroidstuff/Home/voxel-fun/how-it-works
圖示
· Android 圖示設計規範
· Android 內建圖示清單
· 另一份內建圖示清單
· Tango
· Nuvola
· http://openclipart.org/
OPhone
· http://www.oms-sdn.com/
AfterWard
後續章節
後續章節
書籍介紹網頁在連結中。
我很喜歡讀書,但已經無法滿足於市面上出書的質量與速度,所以乾脆自己寫。
隨著本書電子書的入門、基礎、中階部分寫作告一段落,花了將近一年時間。可以說已經完成我對本書預期達到的目標。當然我自己也會以本書的觀念為基礎,繼續學習 Android 平台的各種應用。
本書的擴展版本以「Google! Android 應用程式設計入門」為名出版,作者為蓋索林(gasolin),可至博客來、誠品或金石堂線上購買,或至各大書局選購*。
除了更精鍊原有的內容之外,也為各章附上圖片說明、基本概念(如 MVC 模式)介紹、並加入資料庫應用、地圖應用等後續章節內容。協助讀者以最快捷的方式入門 Android 平台,並應用其中最常見的功能。
出書後筆者仍會繼續在本站上補充更進階的章節,要是 Android 出了比書上更新的版本,在這邊也找的到更新版的入門教學。
您期望在實體書中看到什麼樣的內容或圖例呢?請透過回覆本文,或是提交表單的方式提出您的建議!
目錄
本書(第一版)的目錄如下
入門
安裝並熟悉「Android」開發環境,學會如何新建/開啟「Android」專案並操作「Android」模擬器。使用範例:AppDemos。
1. 初探 Android
2. 安裝 Android 開發工具
3. 開啟現有專案
4. 操作 Android 模擬器(Emulator)
5. 建立一個 Android 程式
基礎
熟悉「Android」應用程式專案的基本架構。能讀懂「Android」程式碼與 XML 介面描述檔,並學會使用基本介面元件來撰寫「Android」應用程式。使用範例:BMI。
1. 描述使用者介面
2. 設計使用者介面
3. 存取識別符號
4. 解讀程式流程
5. 完成 BMI 程式
中階
進一步熟悉「Android」應用程式設計的主要技術內容。使用範例:BMI。
1. 重構程式
2. 加入對話框(Dialog)
3. 查看線上內容 (Uri)
4. 加入選單(Menu)
5. 定義 Android 清單
6. 加入新 Activity
7. 傳送資料到新 Activity
8. 記錄與偵錯 (Log)
9. 活動的生命週期
10. 儲存資訊(Preference)
11. 開發不息
在增強了電子書原有章節的基礎下,已額外規劃了以下這些章節內容,以補足讀者入門的需求:
融會貫通
應用前面章節中使用到的觀念與技術,加深印象的同時也學習一些實用的新技巧。使用範例:aBMI。
1. 顯性設計
2. 支援多國語言
3. 使用接口 (Adapter)
4. 加入下拉選單元件 (Spinner)
5. 簽發應用程式金鑰 (keytools)
6. 發佈到 Android 市場(Market)
資料庫應用
學習 SQLite 資料庫與「Android」平台相關實用技能,並能使用 Android 上的資料庫完成增刪改查操作。使用範例:DummyNote。
1. 加入列表活動 (ListActivity)
2. 使用資料庫 (SQLite)
3. 存取資料表 (SQLiteOpenHelper)
4. 加入增刪改查操作(CRUD)
5. 加入相依的活動 (ActivityForResult)
地圖與定位應用
使用 Android 極富特色的地圖與定位功能,學習控制地圖元件、地圖定位、在地圖中設置地標等等與地圖/定位應用程式設計相關的技術。使用範例:twTrainStation、MyLocation。
1. 申請 Google 地圖服務(API Key)
2. 使用地圖(MapView)
3. 加入按鍵控制 (KeyEvent)
4. 取得現在位置(GPS/基地台三角定位)
5. 結合地圖與定位功能(MyLocationOverlay)
6. 為地圖標上地標(ItemizedOverlay)
附錄
1. 後記
2. 取得原始碼
資源
1. Android 相關資源 (相關資源)
歷程記錄
· 2009/8 月第二版正式出版(基於 Android 1.5) 書籍網頁
· 2009/3/20 正式出版(基於 Android 1.1),可至各大書局選購
GetSource
取得範例原始碼
如何取得範例原始碼
線上瀏覽
我們可以直接前往網站 http://code.google.com/p/androidbmi/source/browse/ 查看範例最新的原始碼。
使用 svn 工具下載
我們也可以使用 svn 工具將原始碼下載下來。電腦裡需具備 svn 工具。網站上也有提供如何安裝各平台 svn 工具的方法。
BMI
「基礎」到「中階」主題所使用的範例
BMI 身高體重指數計算(公制)
svn checkout http://androidbmi.googlecode.com/svn/trunk/2nd/BMI BMI
aBMI
「融會貫通」主題所使用的範例
BMI 身高體重指數計算(英制)
svn checkout http://androidbmi.googlecode.com/svn/trunk/2nd/aBMI aBMI
DummyNote
「資料庫應用」主題所使用的範例
筆記本程式
svn checkout http://androidbmi.googlecode.com/svn/trunk/2nd/DummyNote DummyNote
twTrainStation
「地圖與定位」主題所使用的範例
svn checkout http://androidbmi.googlecode.com/svn/trunk/2nd/twTrainStation twTrainStation
台灣車站周邊地圖
MyLocation
「地圖與定位」主題所使用的範例
自動定位範例
svn checkout http://androidbmi.googlecode.com/svn/trunk/2nd/MyLocation MyLocation