首页资源分类嵌入式系统安卓 > Android应用程序开发36技

Android应用程序开发36技

已有 450100个资源

下载专区

文档信息举报收藏

标    签:Android

分    享:

文档简介

Android应用程序开发36技

文档预览

1 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 應用框架原理與程式設計 36 技 適用於 Android 1.0 版 本書完整範例程式碼請到網站下載: www.misoo1.com 或 tom-kao.blogspot.com @更多资源@http://cleopard.download.csdn.net/ @更多资源集合@http://cleopard.download.csdn.net/album 高 煥 堂 著 (2008 年 10 月第三版) misoo.tw@gmail.com @更多资源@http://cleopard.download.csdn.net/ @更多资源集合@http://cleopard.download.csdn.net/album 17份软件测试文档 http://download.csdn.net/album/detail/1425 13份WPF经典开发教程  http://download.csdn.net/album/detail/1115 C#资料合辑二[C#桌面编程入门篇]  http://download.csdn.net/album/detail/957 C#资料合辑一[C#入门篇]  http://download.csdn.net/album/detail/669 [Csharp高级编程(第6版)](共8压缩卷)  http://download.csdn.net/album/detail/667 10个[精品资源]Java学习资料合辑[一]  http://download.csdn.net/album/detail/663 10个C#Socket编程代码示例  http://download.csdn.net/album/detail/631 6份GDI+程序设计资源整合[全零分]  http://download.csdn.net/album/detail/625 2 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 著作權聲明: z 本書已於 2008 年 4 月出版發行。 z 著作權屬於 高煥堂 所擁有。 z 本 e-book 可整份免費自由複製流傳。 z 但非經作者書面同意,不可以加以切割、剪輯及部分流傳。 z 任何商業用途皆需得到作者的書面同意。 書內範例原始程式碼,請到 tom-kao.blogspot.com 或www.misoo1.com下载。 3 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第三版序言 由於 Android 正式(1.0)版和 HTC/Android 實體手機皆已經上市了,因之本書 也針對 Android 1.0 版的出爐而即時修訂,成為本書的第三版。 大家幾乎都聽過愚公移山的故事,但是大家常把焦點擺在愚公和移山,而忽 略了畚「箕」的角色。禮記.學記篇上有言:良弓之子,必學為箕。其意思是,欲 做出優良的弓,必先好好研究其模子(即箕)。最近許多人知道 Google 推出轟動武 林、驚動萬教的 Android 手機平台。但是幾乎都只關心如何在該新平台上開發應 用程式,卻忽略了 Android 是個框架(Framework),而框架裡含有成百上千個「箕」 類(註:基類是大陸對 Super Class 的譯詞)。基於「良弓之子,必學為箕」的精神, 本書先教您正確認識框架(箕)之原理,然後才介紹如何善用畚箕來開發出優良的 Android 應用程式(良弓)。本書共分為 4 篇: ※ 第一篇:介紹應用框架概念、原理和特性。 ※ 第二篇:闡述應用框架之設計技巧。亦即,如何打造應用框架。 (註:如果你的職務是「使用」Android 框架來開發應用程式的 話,可以跳過本篇,直接進入第三篇。) ※ 第三篇:說明及演練 Android 應用程式設計的 36 技。 ※ 第四篇:介紹 Android 框架與硬體之間 C 組件的開發流程及工具。 筆者並不是說 Android 的應用程式師是愚公,而旨在說明手機軟體領域的三個主 要分工角色: z 做畚箕者:如 Andriod 開發團隊。 z 畚箕買主:如 Google 公司。 z 挑畚箕者:如 Android 應用程式師。 本書也不把您設定為應用程式師單一角色,而是盼望能協助您開拓更寬廣的未 來,無論在上述的任何角色,都能如魚得水,輝煌騰達。於此誠摯地祝福您! 高煥堂 謹識於 2008.10.3 tom-kao.blogspot.com 4 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 目錄 第一篇 良弓之子,必學為箕(框架) 第 1 章 認識應用框架, 14 1.1 何謂應用框架 1.2 框架的起源 1.3 框架的分層 1.4 框架的「無用之用」效果 1.5 框架與 OS 之關係:常見的迷思 第 2 章 應用框架魅力的泉源:反向溝通, 31 2.1 前言 2.2 認識反向溝通 2.3 主控者是框架,而不是應用程式 2.4 現代應用框架:採取廣義 IoC 觀念 2.5 框架的重要功能:提供預設行為 ~禮記.學記~ 第二篇 無之(抽象)以為用 ~老子:無之以為用~ 第 3 章 如何打造應用框架, 54 3.1 基礎手藝:抽象(無之)與衍生(有之) 3.2 打造框架:細膩的抽象步驟 3.2.1 基本步驟 3.2.2 細膩的手藝(一):比較資料成員 3.2.3 細膩的手藝(二):比較函數成員 3.2.4 細膩的手藝(三):將抽象類別轉為介面 5 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第三篇 有之(繼承)以為利 ~老子:有之以為利~ 第 4 章 應用程式設計的基礎手藝 12 技, 82 4.1 #1:如何建立 Menu 選單 4.2 #2:如何呈現按鈕(Button)之 1 4.3 #3:如何呈現按鈕(Button)之 2 4.4 #4:如何進行畫面佈局(Layout) 4.5 #5:如何呈現 List 選單之 1 4.6 #6:如何呈現 List 選單之 2 4.7 #7:如何運用相對佈局(Relative Layout) 4.8 #8:如何運用表格佈局(Table Layout) 4.9 #9:如何動態變換佈局 4.10 #10:如何定義自己的 View 4.11 #11:如何定義一組 RadioButton 4.12 #12:一個 Activity 啟動另一個 Activity 第 5 章 Use Case 分析與畫面佈局之規劃, 141 5.1 善用 Use Case 分析 5.2 以 Android 實踐 Use Case 分析之策略 第 6 章 Use Case 分析的實踐(策略-A):6 技, 149 6.1 #13:使用 Menu 和 starActivity()實踐之 6.2 #14:使用 starActivityForResult()替代 startActivity() 6.3 #15:使用 ListView 替代 Menu 6.4 #16:以 ListActivity 替代 Activity 父類別 6.5 #17:改由.xml 檔案定義畫面佈局 6.6 #18:使用 onResume()函數 6 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第 7 章 Use Case 分析的實踐(策略-B):2 技, 179 7.1 #19:一個 Activity 支持兩個畫面佈局 7.2 #20:將兩個畫面佈局合併為一 第 8 章 介紹關聯式資料庫與 SQLite , 193 8.1 何謂關聯式資料庫 8.2 建立一個表格(Table) 8.3 從表格中查詢資料 8.4 關聯資料模型 8.5 關聯的種類 8.6 兩個表格之互相聯結 8.7 SQL 子句:加總及平均 8.8 SQL 子句:分組 第 9 章 資料庫手藝:5 技, 201 9.1 #21:SQLite 基本操作 9.2 #22:讓 SQLite 披上 ContentProvider 的外衣 9.3 #23:細說 SQLite 與 ContentProvider 9.4 #24:讓 SQLite 配合 onCreate()、onResume()而來去自如 9.5 #25:如何實現商業交易(Transaction) 第 10 章 進階手藝 10 技, 237 10.1 #26:如何定義 BroadcastReceiver 子類別 10.2 #27:如何撰寫 Service 子類別 10.3 #28:如何使用 ProgressDialog 物件 10.4 #29:如何捕捉按鍵的 KeyEvent 10.5 #30:善用 UML Statechart 嚴格控制系統的狀態 10.6 #31:如何使用 MapView 7 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 10.7 #32:如何使用 WebView 10.8 #33:如何自動化操作畫面輸入 10.9 #34:如何活用 COR 設計樣式 10.10 #35:如何活用 State 設計樣式 第四篇 第三十六技:為箕是上策 第 11 章 如何撰寫框架與硬體間之 C 組件, 307 11.1 #36:如何撰寫框架與硬體間之 C 組件 11.2 發展 Android C 組件的經濟意義 附錄 A:327 ◆ A-1 如何安裝 Windows 平台的 Android SDK 1.0 版及 Eclipse ◆ A-2 如何離線安裝 Android SDK 1.0 版及 Eclipse ◆ A-3 如何著手撰寫 Android 應用程式 ◆ A-4 如何執行 Android 應用程式 ◆ A-5 如何安裝 Linux/Ubuntu 平台的 Android SDK 1.0 版及 Eclipse ◆ A-6 如何安裝 C/C++ Cross Compiler 附錄 B:336 ◆ B-1 高煥堂於 Omia 行動應用服務聯盟會議上演講的講義 ◆ B-2 歡迎一起推動「百萬個小 Google 計畫」 ◆ B-3 迎接 IT 第三波:移(行)動時代 ◆ B-4 高煥堂教你最先進的「現代軟體分析與設計」 ◆ B-5 認識 Android 模擬器的操作 Eclipse 8 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 本書由 Misoo 團隊創作與出版 Misoo 技術團隊介紹 由高煥堂領軍的 Misoo 團隊與大陸、俄羅斯、日本專家所組成的跨國嵌入式聯合 設計(Co-design)團隊。Misoo 的開放合作態度,贏得國際的好感和商機。 ƒ 位於風景秀麗的 Voronezh, Russia Russia 例如,跨國團隊成功地將俄羅斯研發 20 多年的頂級 Linter 嵌入式資料庫系統納入 Android 手機裡執行,成為 Android 的嫡系成員之一。此外,Misoo 團隊開發的 Android 遊戲應用程式也順利外銷歐美諸國,如下圖: 9 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 還有,手機線上遊戲等等,如下圖: 10 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 跨國團隊經驗豐富、技術精湛,嵌入式開發成功經驗,包括: ƒ 客製化影音播放器(video player)開發 ƒ 嵌入式資料庫管理引擎(DBMS)開發 ƒ 行動平台 GPS 系統開發 (Blackberry, WinCE, J2ME) ƒ 電信業的專屬無線協定(wireless protocol)的開發 ƒ 學習內容播放系統開發(Flash-based) 11 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 基於 Misoo 的開放精神,高煥堂將本書製作成 e-book 供大家免費閱讀,希望 本書在這千載難逢的大好機會裡,能陪伴諸位的成長與茁壯。此外,高煥堂又把 一些跨國團隊的實務經驗和技術加以編輯,並出版成書,或成為企業培訓課程的 講義,將進一步與大家分享。 如何與 Misoo 跨國團隊技術合作呢? ◎ 開發專案(項目)合作: z 歡迎直接與 Misoo 團隊聯絡: TEL: (02) 2739-8367 E-mail: misoo.tw@gmail.com ◎ 公開教育訓練課程,或企業團隊內訓: z 台北地區 歡迎與 Misoo 團隊聯絡: TEL: (02) 2739-8367 E-mail: misoo.tw@gmail.com z 上海地區 歡迎與 祝成科技洽詢: TEL: 400-886-0806 E-mail: sv@softcompass.com 歡迎多多指教 Misoo 網頁: tom-kao.blogspot.com 或 www.misoo1.com 12 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 歡迎報名參加 高煥堂 主講的 Google Android 技術教育訓練課程 詳細課綱與日期,請上網 www.misoo1.com 服務電話:(02)2739-8367 E-mail: misoo.tw@gmail.com 高煥堂的第 2 本 Android 暢銷書籍(天瓏網路書局熱賣中) *** 詳細目錄 請看第 308 頁 或上網www.android1.net *** 第 1 章 認識應用框架 13 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第一篇 良弓之子,必學為箕(框架) ~~禮記.學記~~ 良弓來自好的框架(箕)。 優良的應用程式來自美好的應用框架。 優秀的 Android 程式師,必先學習應用框架的原理。 14 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1 第 章 認識應用框架 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 1.1 何謂應用框架 1.2 框架的起源 1.3 框架的分層 1.4 框架的「無用之用」效果 1.5 框架與 OS 之關係:常見的迷思 第 1 章 認識應用框架 15 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.1 何謂應用框架 顧名思義,應用框架是﹕某特定應用領域(Domain)中,程式間的共同結構。 讓該領域中的程式師們,依共同結構來發展程式,使程式間具有一致性,增加了 程式的清晰度,以降低程式的設計與維護費用。 所謂「共同結構」,包括了通用的類別、物件、函數,及其間的穩定關係。由於框 架 是 通 用 的 , 大 家 能 共 享 (Share) 之 , 增 加 了 工 作 效 率 , 提 升 了 軟 體 師 的 生 產 力 ( Productivity)。茲拿個簡單例子來說吧﹗兩個長方形,分別為直角及圓角,如下﹕ 首先分辨它們的異同點,然後將其共同部分抽離出來,如下﹕ 我們稱這過程為「抽象」(Abstraction) 。並稱此圖形為「抽象圖」,其只含 共同部分,而相異部分從缺。原有的直角及圓角方形,為完整圖形,稱為「具體 圖」。一旦有了抽象圖,就可重複使用(Reuse) 它來衍生出各種具體圖,且事半功 倍﹗例如﹕ ●用途 1 ── 衍生直角方形。 拷貝一份抽象圖,在圖之四角分別加上┌、┘、└及┐,就成為直角 方形了,如下﹕ 16 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ●用途 2 ── 衍生圓角方形。 拷貝一份抽象圖,在圖之四角分別加上╭、╰、╯及╮,就成為圓 角方形了,如下﹕ ●用途 3 ── 衍生球角方形。 拷貝一份抽象圖,在圖之四角各加上● 就成為﹕ 上述簡單例子中,說明了兩個重要動作﹕ ☆抽象──從相似的事物中,抽離出其共同點,得到了抽象結構。 ☆衍生──以抽象結構為基礎,添加些功能,成為具體事物或系統。 同樣地,在軟體方面,也常做上述動作﹕ ★抽象── 在同領域的程式中,常含有許多類別,這些類別有其共同點。程式 師將類別之共同結構抽離出來,稱為抽象類別(Abstract Class)。 ★衍生── 基於通用結構裡的抽象類別,加添些特殊功能,成為具體類別,再 誕生物件。 第 1 章 認識應用框架 17 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 所以「抽象類別」存在之目的,是要衍生子類別,而不是由它本身來誕生物 件。由於抽象類別本身不誕生物件,所以有些函數並不完整。反之,如果類別內 之 函 數 , 皆 是 完 整 的 , 而 且 要 用 來 誕 生 物 件 , 就 稱 它 為 具 體 類 別 (Concrete Class)。上述簡單例子中,說明了兩個重要動作﹕ ☆抽象──從相似的事物中,抽離出其共同點,得到了抽象框架。 ☆衍生──以抽象框架為基礎,添加些功能,成為具體事物。 其中,「抽象」結果的好壞,決定於程式師的領域知識,及其敏銳觀察力。 這是個複雜的動作,其過程愈精細,愈能得到穩定而通用的框架。一旦有了穩定 且彈性的框架,衍生具體類別的動作,就輕而易舉了。 框架中除了抽象類別外,還有類別間之關係。未來衍生出子類別,並誕生物 件,其物件就會依循既定的關係來溝通、協調與合作。因之,框架說明了物件的 溝通與組織方式,就如同「食譜」敘述著食物料理方法。 不過,食譜並不能完全比喻框架,只比喻一部分而已。食譜只敘述料理方 法,並無真正的蔥、牛肉等。然而框架含有類別、函數、以及物件等真正的程 式。因之,有人拿「未插完的花盆」來比喻框架,似乎更傳神﹗插花老師先插上 背景花,並留下空間,任學生發揮,繼續插完成。框架設計師提供了基本類別, 也預留空間讓您發揮,繼續衍生出子類別。 從上所述,可知框架包括了﹕ ☆一群抽象類別,類別內有函數,函數內有指令,但有些函數內的指令從缺, 預留給應用程式師補充之。 ☆抽象類別間之穩定關係。 然而,現在市面上的框架,不只含抽象類別,且含有具體類別、函數、及物 件。實際上,框架已涵括了傳統類別庫(Class Library) 之功能,使得大家不易區 分框架與類別庫之差別了。只能在理論上,區分兩者如下﹕ 18 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 應用框架 ────────────────────── ◎目的﹕讓應用程式師衍生出具 體類別,衍生時可修正 類別,才誕生物件。 ◎應用框架中的類別的函數,常 呼叫應用程式中的函數。 ◎含有類別間之關係,其預設了 物件間的互助合作關係。 ◎物件常含預設計行為(Default Behavior) ,預設行為可讓應 用程式師修正之。 類別庫 ─────────────────────── ●目的﹕讓程式師拿現成類別來誕 生物件,類別並未預留空 間給程式師來修正。 ●應用程式的函數只能呼叫類別庫 中的函數,反之不可。 ●類別是獨立的,並未設定物件間 的溝通方式。 ●物件的行為皆是固定的,無法修 正之。 在實用上,許多人已將它們混為一談了。 1.2 框架的起源 框架(Framework)的歷史已經有 20 多年了,可追溯到 1980 代 Smalltalk 語言的 MVC,到了 Apple Macintosh 時代的 MacApp 框架開始大放異彩。逐步演進到今 天的.Net Framework,其應用範圍愈來愈大,已經成為資訊服務系統的共通核心 框架了。20 多年來,框架的基本原理一直都沒有改變,其基本結構也沒有太多變 化,其基本用法也是老樣子,只是應用的場合及範圍不斷地擴大,經驗不斷累積 中。在框架的發展過程中,最具有代表性的是﹕ ● 1980 年代初期 ----- Smalltalk-80 的 MVC Framework ● 1980 年代中期 ----- Macintosh 電腦的 MacApp Framework ● 1990 年代初期 ----- Visual C++ 的 MFC Framework ● 1990 年代中期 ----- IBM 的 San Francisco Framework ● 2000 年 -------------- Microsoft 的.Net Framework 第 1 章 認識應用框架 19 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ● 2007 年 -------------- Google 的 Android 框架 茲簡介如下: 1.2.1 Smalltalk-80 的 MVC 框架 應 用 框 架 的 起 源 中 , 大 家 最 熟 悉 的 是 Smalltalk-80 語 言 中 的 MVC(Model-View-Controller)框架。其讓 Samlltalk 程式師迅速建立程式的使用者 介面(User Interface)。從 1980 年代的 Smalltalk-80 到 1990 年代 Smalltalk-V ,其 使用者介面皆依循這個著名的框架。典型的 MVC 框架包括三個抽象類別── Model、View 及 Controller。應用程式從這些抽象類別衍生出具體類別,並誕生物 件。其物件間的關係如下﹕ 圖 1-1 著名的 MVC 框架 model 物件負責管理資料或文件,它可對應到數個 view 物件,每個 view 物 件顯示出 model 物件的某一方面﹔每個 view 物件有 1 個相對應的 controller 物 件,其負責解釋使用者輸入的訊息,如移動滑鼠等等。使用者輸入訊息時, controller 依訊息去要求 model 處理文件資料,也會要求 view 物件更新畫面。一 旦 model 物件中的資料更動了,model 物件會通知各 controller 及 view 物件,各 view 物件會向 model 取得新資料,然後更新畫面。因之典型 MVC 框架是由一群 model、view 及 controller 物件互助合作,做為使用者與應用程式的溝通介面。 20 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.2.2 MacApp 框架 1980 年代中期,已有多種商業性的應用框架上市,其中最流行的是 Apple 公司的 MacApp 框架,其能協助發展 Macintosh 電腦上的應用程式。這應用程式 包括 3 個部分﹕ ◎ application ──負責啟動程式、解釋使用者的訊息與命令。 ◎ document ──管理與儲存應用程式的文件資料。 ◎ view ────顯示與輸出文件資料。一個程式常含有數個 view,裨從不同 角度來瀏覽文件資料。 Macintosh 電腦具有視窗畫面。在螢幕畫面上,view 依偎在 window 中,且 view 的外圍有個 frame。當使用者選取視窗選擇表中的項目時,會產生 command 來要求更新 document 或 view 之內容。因之,由 MacApp 框架所產生的介面, 含有下述物件﹕ ● application 物件 ──負責啟動程式、誕生 document 物件,顯示視窗選擇表,並傳遞訊息與命令 等。 ● document 物件 ──負責誕生有關的 view、window 及 frame 等物件。當 document 中的資料異動 時,document 物件會通知 view 物件來取得新資料,並更正視窗中的內容。 window 物件負責視窗的開關、移動、及通知 frame 物件來協助改變視窗大 小及捲動等。 ● frame 物件 ──負責將視窗分割為小區域,每區域可擺入一個 view,也負責捲動及改變窗 之大小。 ● view 物件 ──負責顯示資料、記錄滑鼠的位置、以及改變游標的形狀。 ● command 物件 ──當使用者藉滑鼠、選擇表及鍵盤來發出命令時,由 command 物件來轉送給 document 或 view 物件,要求它們採取進一步的行動。 第 1 章 認識應用框架 21 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 雖然 MacApp 框架中,定義了許多類別來誕生上述的物件,然而應用程式中 通 常 可 直 接 使 用 現 成 的 window 、 frame 及 command 等 類 別 和 物 件 。 至 於 application 、document 及 view 物件,常須加以修正,才能合乎特定的應用場 合。因之,應用程式必須分別由 TDocument 、TView 及 TApplication 抽象類別 衍生出具體子類別,然後才誕生 document、view 及 application 物件。 MacApp 框架成功地結合了螢幕視窗功能,並提供親切的軟體發展環境,其 應用程式呈現可塑性,能隨需求而不斷修正。這成功經驗,對後來的應用框架的 發展,產生了極大的影響。 1.2.3 Visual C++ 的 MFC 框架 在 1990 年~1993 年之間,Borland C++ 上市並提供了 OWL 應用框架,隨後 Microsoft C/C++ 及 Visual C++上市,提供了 MFC 應用框架。OWL 及 MFC 的 目的和功能大致相同──皆為了將 Windows 的 API 介面函數包裝起來,使得 C++ 應用程式師能依循一致的框架,迅速發展 Windows 應用程式。初期的 MFC 包含 兩部分: ◎ 與 Windows 有關的類別 ──用來包裝 Windows 的介面函數。 ◎ 通用性的類別 ──例如 List、Array 、Date 等與常用資料結構有關的類 別。 後來逐漸增加了更多組件,例如: ● OLE 類別 ──協助應用程式經電腦網路而連結到分散各地的物件。 ● ODBC 類別 ──協助應用程式以統一的 SQL 敘述來存取各式資料庫(如 Oracle、Sybase 等)之內容。 MFC 的物件組織與合作方式,類似於 MacApp 的物件群組關係。MFC 含有 CWinApp 、CMainFrame、CView 及 CDocument 等基本類別。應用程式必須從 這些類別衍生出具體子類別,並誕生物件,互相溝通與合作。其物件間的關係如 下: 22 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 圖 1-2 MFC 的 Document/View 框架 Windows 將使用者所輸入的訊息傳給 mainfrm 物件,由 mainfrm 再轉達給 view、app 或 document 物件。當 document 物件內的資料有所異動時,document 會通知 view(程式可含許多 view)物件來取得新資料,以便更新視窗內容。 1.2.4 IBM 的 San Francisco 框架 上述的 MVC、MacApp 及 MFC 皆是擔任系統層次的核心任務,如電腦網 路、分散式資料庫的管理工作。到了 1990 年代中期,框架開始擴展到商業資訊 服務的層面,就是俗稱的應用框架(即 Application Framework),又稱為商業的領 域 框 架 ( 即 Business Domain Framework) 。 其 中 最 著 名 的 是 IBM 公 司 的 San Francisco 框架。 IBM 的 San Francisco 含有商業服務的企業核心流程,如 ERP 的訂單循環、 會計的應收應付循環等,如下圖 1-3 所示。此外也含有像「客戶」及「帳戶」等 核心的企業物件及物件之間的關係。 第 1 章 認識應用框架 23 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 應用程式 核心企業流程 Business Financials Order Management 共同企業物件 基礎服務 Java VM Unix、NT 等平台 圖 1-3 IBM 的 San Francisco 元件框架 IBM 的 San Francisco 框架所提供的是 非客製化 的商業核心服務,讓其它 資訊服務廠商進行開發客製化的商業應用服務。 1.2.5 微軟的 .Net Framework 到了 2001 年,微軟所推出的.Net Framework,其格局更擴大到整個企業 (Enterprise)的分散式框架,甚至包括以 Web service 為核心的靠企業大型分散式框 架。例如它提供 XML Web Service、MSMQ 非同步的訊息服務、Security 服務 等。在 .Net Framework 裡含有上千個既有的類別,能透過繼承或介面委託方式 使用這些類別的功能或服務。 1.2.6 Google 的 Android 框架 於 2007 年 11 月,Google 推出的 Android 應用框架,其適用於「手機+網路」 的新市場上。除了它是一個新的應用之外,更珍貴的是其程式碼採開放策略,讓 大家能一窺其全貌,給予軟體開發者很好的學習機會。 24 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.3 框架的分層 由於框架介於應用程式與系統程式之間,能大量地重複使用(Reuse) ,並可 不斷修正之,因而提升了應用程式之彈性,也能提升系統程式之彈性。它本身也 可分為兩個層級,如下圖: 圖 1-4 應用框架之層次 例如,在多平台(Multi-platform) 系統上,彈性是極重要的。在這個層次裡, 框架提供了支援性的服務,通稱為支援性框架,讓人們不但能使用作業系統的 API 函數,也可以修正之,裨更符合企業的需要。這種支援性的框架,其觀念與 一般應用框架相同,只是它負責系統層次的任務,如電腦網路、分散式資料庫的 管理工作。一般,應用程式師並不直接修正支援性框架,而是由系統維護人員來 修正之。 在應用層次上,許多企業已著手創造自己的專業框架,整合公司裡的軟體系 統。如果您把應用框架比喻為「食譜」,則不難想像到各領域(Domain)的產業都 可能發展出應用框架了。例如,歐洲汽車廠就聯合發展出 AUTOSAR 應用框架, 它們就如同食譜配方,是餐廳賺錢的祕方。因之,在支援性框架的協助下,許多 專精於某領域的公司,設計出各式各樣的應用框架,像貿易、運輸、醫療、手機 第 1 章 認識應用框架 25 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 等,例如 Android 就是手機+網路的應用框架,讓各手機廠商,可經由修正及補充 來創造出「獨家」的應用系統,成為自己公司中的重要資源。 例如,Android 就包含了支援性框架和手機專業應用框架。 1.4 框架的「無用之用」效果 小樹的用途少,人們不理睬它、不砍伐它、才有機會長成有用之巨木,此為 「無用」之用﹗老子說過:「人皆知有用之用,而莫知無用之用」,這與框架觀 念是一致的。 數千年前,老子提出了這「有、無」哲理,從無為狀態中創造出有為的積極 效果。像房子的中間、門、窗皆是空的,才能供人們進出、居住與透透空氣。其 積極效果是﹕日後依新環境的條件而加以調整、充實,創造出多樣化的用途。例 如畚箕的中間是空、虛的,才能裝泥土、垃圾等各式各樣的東西。此外,畚箕的 空無,創造了畚箕的重複使用性(Reusability),裝完了泥土,倒掉之後,還可拿 來裝垃圾等,不斷重複使用之,一直到壞掉為止。 不僅上述的樹木、房子、畚箕等東西深含虛無之用哲理,在人們的精神修養 上也常見同樣哲理。例如古之賢者常教導年輕人應該「虛」懷若谷,才能不斷虛 心求教,不斷吸收新知識,不斷充實與成長,成為有用之人。反之,志得意滿的 年輕人,常不願虛心吸收新知識,常在不知不覺中變為新環境中的古典人物,為 不斷變化的潮流所淘汰。 應用框架中的主角──抽象類別,並非具體的類別,不能用來誕生物件,看 似無用的東西。可是它可衍生出無數個具體子類別,可誕生出無數種物件來﹗抽 象類別中的「抽象(abstract)」函數常是空虛的讓抽象類別能虛懷若谷,讓應用程 式師不斷充實它,其子孫類別就個個精明能幹﹗抽象類別發揮無用之用的效果, 應用框架則更進一步地發揮這種效果。人們易於得意驕傲,不易虛懷若谷。同樣 地,易於創造具體類別,而不易創造出抽象類別。不過,當您懂得藉由眼前的 「無用」來換取長遠的「有用」時,創造與使用抽象類別就易如反掌了。 26 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 1.5 框架與 OS 之關係:常見的迷思 1.5.1 迷思 許多人從空間角度去想像 OS 與應用框架之間的關係。的確,OS(如 Linux 或 Windows)像木板床,應用框架像彈簧床墊,其擺在木版床上。而應用程式則像睡 在床墊上的人。這個觀點是對的(如圖 1-4 所示)。 然而,許多人順勢推論他們之 間的互動關係如下圖: 圖 1-5 常見的迷思 乍看之下,似乎蠻合理的,其實是個迷思。請你換個角度,採取另一個觀 點,如下圖,更容易體會框架的角色和涵意,此新觀點如下圖 1-6 所示。 回想一下,您寫傳統程式時,主控權掌握在程式手中,其決定如何呼叫庫存 函數﹔就像棒球比賽的「投手」一樣。反之,使用框架時,您的程式則擔任「捕 手」之角色。盼您在使用框架時,能有這種心理準備(Mindset) 。 第 1 章 認識應用框架 27 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 圖 1-6 較合理的觀點 1.5.2 藉生活實例來闡述 在 Linux / Windows 的事件驅動觀念中,OS 會不斷與應用程式溝通,不斷修 正其慣例,裨對外界的事件提供迅速反應與服務。所以 OS 頻繁地主動跟應用程 式溝通。如下圖: 圖 1-7 較合理的觀點(相當於上圖 1-6) 在日常生活中,也常見這種溝通情形。例如,大飯店(Hotel) 的溝通如下﹕ 28 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 圖 1-8 OS 相當於服務生 再如一般商店的溝通﹕ 圖 1-9 OS 也相當於店員 當客人問道﹕今天打幾折﹖這是個小問題,店員按慣例(即按公司規定)來 回答﹕8 折。當客人討價還價而問道﹕可否打 7 折﹖店員請教經理,但經理並未 給予特別指示,店員就依照慣例回答﹕滿 1000 元打 7 折。 圖 1-10 店員與經理溝通 第 1 章 認識應用框架 29 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 假如,經理有所特別指示,則店員修正慣例如下﹕ 圖 1-11 經理的回覆 從上述生活實例中,可體會出應用框架之角色了。與顧客互動的細節幾乎都 由店員處理掉了,有必要才會打擾(呼叫)經理,所以經理較輕鬆了。以此類推, OS 與框架之關係,也相當於框架與應用程式之關係。如下圖: 圖 1-12 輕鬆的總裁 與店員的互動細節幾乎都由經理人員處理掉了,有必要才會打擾(呼叫)總 裁,所以總裁就非常輕鬆了。因此,有了像 Android 的框架(即經理),手機應用 程式(即總裁)就簡單許多了。◆ 30 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 平台(Platform)的迷思 一談到平台,許多人就聯想到地基,當然又聯想到漂亮的房子囉!持著這個 觀點是沒錯,但是堅持這單一觀點,常導致多項迷思: z 引導我們的眼光聚焦於漂亮的房子。 z 既然聚焦於房子,就只會以『用』態度去對待平台;就如同一位女生以『用』 的態度去對待男生,男生就不會愛她了。 z 既然聚焦於房子,就希望買來好地基,縮短建房子的工期;就如同依賴雀 巢的咖啡包、奶精,逐漸地競相開咖啡廳,造咖啡包的工業就式微了。 為了提供給您另一個觀點,筆者把 Android 平台比喻為漢堡: 芝麻:Android 應用程式(房子) 上層麵包:Android 框架(平台) 牛肉和 Cheese:框架與硬體之間 的 C 組件 底層麵包:硬體組件 每一個觀點都沒有錯,但是多了一些觀點,讓我們的判斷更精準而已。如果 您想進一步了解漢堡觀點,可先閱讀本書第 11 章。 第 2 章 應用框架魅力的泉源:反向溝通 31 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2 第 章 應用框架魅力的泉源: 反向溝通(IoC: Inversion Control) ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 2.1 前言 2.2 認識反向溝通 2.3 主控者是框架,而不是應用程式 2.4 現代應用框架:採取廣義 IoC 觀念 2.5 框架的重要功能:提供預設行為 32 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.1 前言 上章裡,您已知道應用框架之目的,也瞭解它在軟體設計上之角色。本章 裡,將專注於框架之主角──抽象類別上,說明抽象類別之特性,分析「抽象」與 「具體」類別之間的雙向溝通方法。應用框架中最令人著迷之處是: 框架裡的函數,能呼叫應用程式的函數。 ﹌﹌﹌﹌﹋﹌﹌﹋﹌﹌﹌﹌﹌﹋﹌﹌﹋﹋ 這是框架與一般類別庫(或程式庫)的極重要區別。使用一般程式庫時,程 式中的函數呼叫了現成的庫存函數,但庫存函數不能反過來,呼叫您所寫的函 數。由於庫存函數設計在先,而您寫程式在後﹔所以,您的函數呼叫庫存函數, 這種晚輩呼叫前輩的傳統溝通情形,是您已非常熟悉的了。 應用框架除了能進行傳統溝通外,還提供新潮方法:前輩呼叫晚輩。雖然前 輩(應用框架)誕生時,晚輩(應用程式)尚未誕生﹔但是前輩有時候可預知晚 輩中的函數,就可呼叫它。這種功能,具有下述效果: ☆ 框架能事先定義許多「預設」(Default)函數。預設(default) 函數就是依 慣例而設定之函數。慣例是自動化科技的基本觀念,也是應用框架的 重要機制。例如,搭計程車時,您只要告訴計程車司機:「到士林夜 市」,司機會依照其經驗習慣而選取路線,讓您舒適抵達夜市。更重要 的是,您可特別指示司機,他會按照您(即應用程式)的意思而「修正」 其慣例。 ☆ 應用程式師的主要工作是:設計函數供框架來呼叫。這些函數可 修正或取代框架中的函數。 ☆ 如果程式中的函數已修正或取代預設函數,框架就呼叫程式中的 函數﹔反之則呼叫預設函數。 這些效果正滿足當令流行的「事件驅動」(Event-Driven)軟體的需要,如下圖 2-1 所示。 第 2 章 應用框架魅力的泉源:反向溝通 33 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 使用者 事件 1 事件 2 事件 3 OS(Linux/Windows) 訊息 應用框架 訊息 預設 f1() abstract f2() 預設 f3() 訊息(呼叫) 預設 f4() 訊息 f1() 訊息 訊息 應用程式 f2() f4() 訊息 圖 2-1 應用框架與事件驅動軟體 這是在 Linux 或 Windows 等作業系統下,應用框架的典型雙向溝通情形,茲 將上述 4 種呼叫情形說明如下: 1. 框架中預設了 f1(),程式中也定義了 f1()。此時優先呼叫晚輩的 f1() 函數。 2. 框架「虛」設了 f2(),亦即 f2()是個抽象(abstract)函數。此時您務必 34 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 定義 f2()來充實之,並供 Linux/Windows 或其它函數呼叫。例如 f3() 呼叫 f2()。 3. 框架預設了 f3(),程式並未定義 f3()。此時呼叫預設的 f3()函數。 4. 框架預設了 f4(),您也定義了 f4()。此時優先呼叫 f4()函數,而 f4()可呼 叫前輩(預設)的 f4()函數。 從上所述,您可看出重要現象: 框架與程式之間,主控權是在框架手上,您寫的函數皆供框架呼叫 ﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹋﹌﹌﹌﹌﹌ 回想一下,您寫傳統程式時,主控權掌握在程式手中,其決定如何呼叫庫存 函數﹔就像棒球比賽的「投手」一樣。反之,使用框架時,您的程式則擔任「捕 手」之角色。盼您在使用框架時,能有這種心理準備(Mindset) 。 2.2 認識反向溝通 ---- 又稱為反向控制(IoC) 通常框架都是設計在先,而應用程式則設計在後,這種前輩擁有主導權, 進而「控制」後輩之情形,就通稱為「反向控制」。顧名思義,IoC(Inversion of Control)就是「反向控制」之意思。而它是相對於「正向控制」一詞,所以在本節 裡,將先介紹「正向控制(溝通)」之涵意,就能迅速理解「反向溝通」之意義了。 IoC 觀念和機制源自於 OO 語言(如 C++、Java 等)的類別繼承體系,例如 Java 語言中,父類別(Superclass)的函數可以主動呼叫子類別(Subclass)之函數,這就是 最傳統的 IoC 機制,稱為「繼承體系 IoC」。後來,人們常將許多相關的父類別 聚集起來成為框架,逐漸地,延伸為:應用框架主動呼叫應用程式之情形,就稱 為 IoC 。 或 者 說 : 會 主 動 呼 叫 應 用 程 式 之 框 架 , 就 稱 為 IoC 框 架 , 例 如 Android、Spring 等等。 第 2 章 應用框架魅力的泉源:反向溝通 35 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.2.1 正向溝通 傳統的程式庫(Function Library)已含有許多現成的函數(前輩),您的程式(晚 輩)可呼叫之。例如, public myFunction() { int x = abs( y ); …… } abs()是您已很熟悉的庫存函數,它誕生在先,是前輩;您的程式(晚輩)誕 生在後,是晚輩。這是傳統呼叫法:晚輩呼叫前輩。一般類別庫(Class Library)含 有現成的類別,這些類別含有函數,可供新類別的函數來呼叫之。例如,先有個 Person 父類別如下: public class Person { private String name; public void SetName(String na) { name = na; } public void Display() { System.out.println("Name: " + name ); } } 接著,您可以寫個子類別 Customer 去繼承它,並且呼叫它的函數,如下: public class Customer extends Person { public void Init() { super.SetName(“Tom”); } public void Show() { super.Display(); } } 上述的 Init()呼叫了晚輩 SetName()函數。或者,寫個 JMain 類別: public class JMain { private p; public void Init() { p = new Customer(); p.SetName(“Tom”); } public void Show() { p.Display(); } } 這也是晚輩呼叫前輩的情形。由於大家已習慣了這種晚輩呼叫前輩的用法, 就通稱為「正向」(Forward) 呼叫法。由於晚輩擁有主控權,所以這種機制又稱為 36 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 「正向控制」。再來看看本書的主角:Android 框架,以下就是 Android 的應用程 式碼: // Android 程式 public class MyActivity extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); } 上述晚輩(即 MyActivity)的 onCreate()呼叫了晚輩(即 Activity)的 onCreate()和 setContentView()函數。而 Activity 就是 Android 框架裡的重要抽象類別。 2.2.2 反向溝通 當子類別繼承父類別時,父類別之函數可以呼叫子類別之函數。雖然父類別 (前輩)誕生時,子類別(晚輩)常常尚未誕生﹔但是前輩有時候可預知晚輩中的函 數,就可呼叫它。框架裡的抽象類別就是扮演父類別的角色,只是含有一些陽春 型的類別,其提供很通用,但不完整的函數,是設計師刻意留給應用程式的子類 別來補充的。一旦補充完成,框架裡的父類別的函數就可以「反向呼叫」子類別 裡的函數了。 2.2.2.1 以一般 Java 程式為例 例如:有了一個繪圖的 Shape 父類別: // Shape.java package _framework; public class Shape { public void Paint() { this.Draw(); } public abstract void Draw(); } 設計者預期子類別將會定義一個 Draw()函數,於是讓 Paint()呼叫子類別的 Draw()函數。於是子類別(晚輩)提供 Draw()給父類別(前輩)來呼叫之,如下: 第 2 章 應用框架魅力的泉源:反向溝通 37 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ // Circle.java package _objects; import java.awt.Color; import java.awt.Graphics; import _framework.*; public class Circle extends Shape { private Graphics m_gr; private int x, y, radius; public Circle(Graphics gr) { m_gr = gr; } public void SetValue(int x0, int y0, int rad){ x = x0; y = y0; radius = rad; } public void Draw(){ //畫圓 m_gr.setColor(Color.BLACK); m_gr.drawOval(x-radius, y-radius, 2*radius, 2*radius); }} 接者,寫個 JMain 類別: // JMain.java import java.awt.*; import javax.swing.*; import _framework.Shape; import _objects.*; class JP extends JPanel { public void paintComponent(Graphics gr) { super.paintComponents(gr); Circle cir = new Circle(gr); cir.SetValue(160, 100, 45); Shape sp = cir; sp.Paint(); }} public class JMain extends JFrame { public JMain() { setTitle(""); setSize(400, 300); } public static void main(String[] args) { JMain frm = new JMain(); JP panel = new JP(); frm.add(panel); frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frm.setVisible(true); 38 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ }} 此程式執行到 JP 類別的 sp.Paint()指令時,就呼叫到 Shape 父類別的 Paint()函 數,接著就呼叫到 Circle 子類別裡的 Draw()函數了。這種前輩呼叫晚輩的用法, 就通稱為「反向」(Inversion) 呼叫法。由於前輩擁有主控權,所以這種機制又稱 為「反向控制」(Inversion of Control)。 2.2.2.2 以 Android 程式為例 例如想在 Android 上畫出一個長方形,可寫程式如下: // Android 程式 public class MyView extends View { private Paint paint; public MyView(Context context) { super(context); private Paint paint= new Paint(); } public void ReDraw() { this.invalidate(); } @Override protected void onDraw(Canvas canvas) { // 畫長方形 paint.setAntiAlias(true); paint.setColor(Color.YELLOW); canvas.clipRect(30, 30, 100, 100); }} 程式執行到 ReDraw()函數時,就正向呼叫到 Android 框架裡的 invalidate()函 數了。接著,Android 框架會反過來呼叫 MyView 子類別的 onDraw()函數。這就 是「反向溝通」了。如果你沒有定義 onDraw()函數的話,會執行 View 父類別預 設的 onDraw()函數,而依據框架預設之慣例而行了。 第 2 章 應用框架魅力的泉源:反向溝通 39 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.3 主控者是框架,而不是應用程式 前面說過,傳統的程式庫及類別庫只提供正向溝通,而應用框架則提供正向 和反向兼具的雙向溝通。本節針對應用框架的雙向溝通,做進一步的闡述雙向溝 通機制讓框架成為主控者,而應用程式只是配角而已。首先看個例子,假設已經 設計了 Person 及 Customer 兩類別並存於應用框架中,如下圖所示: Person Customer (框架) 圖 2-2 一個簡單的框架 茲以 Java 程式表達如下: // Person.java package _abstract_classes; public abstract class Person { protected String name; public void setName(String na) { name = na; } public abstract void display(); } // Customer.java package _abstract_classes; public class Customer extends Person { public void display() { System.out.println("Customer: " + super.name); } } 40 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 現在,基於這個框架而衍生出 VIP 子類別,如下圖: Person Customer (框架) (應用程式) VIP 圖 2-3 衍生出應用程式的類別 其 Java 程式碼如下: // VIP.java package _concrete_classes; import _abstract_classes.*; public class VIP extends Customer { private String tel; public VIP(String na, String t) { super.setName(na); tel = t; } public void display() { super.display(); System.out.println("TEL: " + tel); }} 第 2 章 應用框架魅力的泉源:反向溝通 41 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 建 構 式 VIP() 呼 叫 父 類 別 的 setName() 函 數 , 且 display() 呼 叫 Customer::display()函數,這兩者皆為正向溝通。亦即,程式中的函數呼叫框架中 的函數。現在繼續增添反向溝通機制。例如,於框架中增加了 Product 類別,此 時,框架共含三個類別。基於這框架,您可衍生出子類別如下圖所示: Product pc Person Customer (框架) (應用程式) TV VIP 圖 2-4 框架掌握更多主控權 其 Java 程式碼如下: // Product.java package _abstract_classes; public abstract class Product { protected int pno; protected Customer cust; public Product(int no) { pno = no; } public void soldTo(Customer cobj) { cust = cobj; } public void inquire() { this.print(); System.out.println("sold to ..."); cust.display(); } 42 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public abstract void print(); } // TV.java package _concrete_classes; import _abstract_classes.*; public class TV extends Product { private double price; public TV(int no, double pr) { super(no); price = pr; } public void print() { System.out.println("TV No: " + pno); System.out.println("Price: " + price); }} 其反向溝通機制如下圖: public class Product { .... public void inquire() { (反向溝通) .... this.print(); (反向溝通) cust.display(); } } public class TV extends Product { .... public void print() .... }} public class Person { .... } public class Customer extends Person { .... } public class VIP extends Customer { .... public void display() { .... 繼續看個主函數,會更清晰地理解框架的主控地位,如下程式碼: // JMain.java import _abstract_classes.*; 第 2 章 應用框架魅力的泉源:反向溝通 43 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ import _concrete_classes.*; public class JMain { public static void main(String[] args) { TV t = new TV(1100, 1800.5); VIP vp = new VIP("Peter", "666-8899"); t.soldTo(vp); t.inquire(); }} Product 父類別設計在先,然後才衍生 TV 子類別,而且常由不同人所設計。 那麼,何以 Product 類別的 inquire() 敢大膽地呼叫 TV 類別的 print()函數呢﹖萬 一 TV 類別並無 print()時,怎麼辦呢﹖答案很簡單: ☆ TV 類別必須定義 print()函數,才能成為具體類別。 因為 Product 裡的 print()是抽象函數,內容從缺: public abstract void print(); 其中的 abstract 字眼,指示子類別必須補充之,才能成為具體類別。 ☆ TV 類別成為具體類別,才能誕生物件。 ☆ 有了物件才能呼叫 inquire()函數, ☆ 既然 TV 類別已覆寫 print()函數,inquire() 可大膽地呼叫之。 於是,必須替 TV 類別添增 print()函數如下: public void print() { System.out.println("TV No: " + pno); System.out.println("Price: " + price); } 執行時,就產生反向呼叫的現象了。 此外,Product 類別的 inquire() 呼叫 VIP 類別的 display()函數。Product 類別 與 VIP 類別 並非同一個類別體系。此時, z VIP 類別必須是具體類別才能誕生物件。 z cust 變數必須參考到剛誕生的物件。 z 由 cust 所參考之物件來執行其 display()函數。 z inquire()就透過 cust 而成功地呼叫到 VIP 類別的 display()了。 44 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 這過程有個先決條件──VIP 類別必須覆寫 display()函數才行。否則將會呼叫 到 Customer 類別預設的 display(),而不是呼叫到 VIP 類別的 display()函數。也 許,您還會問一個問題:何不將 cust 變數改宣告為 VIP 型態之參考呢﹖答案是: 別忘了,抽象類別通常設計在先,而具體類別產生在後。因之,設計 Product 類 別時,VIP 類別尚未誕生呢﹗ 這程式展現了應用框架的重要現象: ☉程式執行時,主控權在框架手上。 雖然 main()函數仍為程式的啟動者,但主要的處理過程皆擺在 Product 類別 內。例如, ● soldTo()負責搭配產品與顧客之關係。 ● inquire() 負責呼叫 TV 的 print() 輸出產品資料,並呼叫 VIP 的 display() 輸出顧客資料。 ☉程式裡的類別,其成員函數,主要是供框架呼叫之。 例如,TV 類別的 print()供 inquire()呼叫之,而 VIP 類別的 display()供 inquire() 呼叫之。 ☉由於框架掌握主控權,複雜的指令皆擺在框架中,大幅簡化應用程式。 因之,優良的應用框架常讓程式師如虎添翼。 ☉框架裡的 inquire() 進行反向溝通,它呼叫子類別的 print() 函數。 這是同體系內的反向呼叫。 ☉框架裡的 inquire() 反向呼叫 VIP 的 display() 函數。 因 Product 與 VIP 分屬不同的類別體系。這是跨越體系的反向溝通。 @更多资源@http://cleopard.download.csdn.net/ @更多资源集合@http://cleopard.download.csdn.net/album 第 2 章 應用框架魅力的泉源:反向溝通 45 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 2.4 現代應用框架:採取廣義 IoC 觀念 上 一 節 所 提 的 反 向 溝 通 都 是 仰 賴 Java 的 類 別 繼 承 機 制 來 達 成 , 例 如 把 Product 類別納入框架中,應用程式員就能繼承 Product 而衍生出 TV 子類別。然 後在執行時(Run-time),整個程式的主控權就掌握在 Product 的 inquire()函數手 中,這是仰賴類別繼承的傳統「反向控制」(IoC)。由於數十年來,愈來愈多的框 架上市了,幾乎所有的框架都擅用 IoC;於是,IoC 一詞之涵義逐漸地擴大為: 框架擁有主控權,它主動呼叫應用程式的情形,就通稱為 IoC。 如此,IoC 就不限於上述的類別繼承情形了。只要框架主動來呼叫應用程式 裡的類別,就是一種 IoC 了。例如下圖: Factory (框架) Document Document Initialize() { doc = new Document(); doc.Setter( new Integer() ); ………… } (應用程式) IDisplay pd; void Setter(IDisplay d) { dp = d; } void Display() { dp.Display(); } IDisplay Integer 圖 2-5 不是透過類別繼承的框架主動呼叫 46 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 茲以 Java 程式來實現上圖結構,其步驟如下: Step-1: 建立一個 Java 專案,並定義介面 在此專案裡,建立三個套件如下: 然後在_framework 套件裡定義一個介面,其 Java 程式碼如下: // IDisplay.java package _framework; public interface IDisplay { public void Display(); } 並且在_framework 套件裡定義 Factory 類別,其 Java 程式碼如下: // Factory.java package _framework; import _objects.*; import _objects.Integer; public class Factory { private Document doc; public Document Initialize() { doc = new Document(); doc.Setter(new Integer()); return doc; }} Step-2: 定義應用物件 在_objects 套件裡定義兩個類別:Document 和 Integer,其 Java 程式碼如下: // Document.java package _objects; import _framework.*; 第 2 章 應用框架魅力的泉源:反向溝通 47 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public class Document { IDisplay dp; public void Setter(IDisplay d) { dp = d; } public void Display() { dp.Display(); } } // Integer.java package _objects; import _framework.*; public class Integer implements IDisplay { int value; public Integer() { value = 100; } public void Display() { System.out.println("Value = " + String.valueOf(value)); }} Step-3: 設計 JMain 主應用程式 在(default package)套件裡定義一個類別:JMain,其 Java 程式碼如下:Java 程式碼如下: // JMain.java import _objects.*; import _framework.*; public class JMain { public static void main(String[] args) { Factory fa = new Factory(); Document doc = fa.Initialize(); doc.Display(); }} Step-4: 執行上述程式 當您仔細觀察上述程式範例時,會發現框架也掌握了物件的生殺大權,負責 誕生應用程式之物件。請看 Factory 類別的內容: // 框架 public class Factory { private Document doc; public Document Initialize() { doc = new Document(); 48 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ doc.Setter( new Integer()); return doc; }} 其掌握了應用程式物件之生命週期(Lifecycle),其含有物件參考(Reference) 參考到應用程式之物件,也就是它「包含」(Contain)了應用程式之物件。所以, 這種框架又通稱為 Container。此外,在建立物件之刻,也呼叫應用物件的 Setter() 函數,建立出 Document 與 Integer 兩個物件之相依關係(Depencency)。換句話說, 應用物件之間的相依關係之建立是掌握在框架手中,由框架主動呼叫應用程式而 建立的,這讓應用程式不必誕生其它應用物件,也不必費心管理應用物件之間的 相依性。而是由框架替應用程式『注入』相依關係,免除了應用物件之間的相依 關係,如同:注射流感疫苗,而免除了流感。這通稱為相依性注射(Dependency Injection)。 2.5 框架的重要功能:提供預設行為 2.5.1 預設行為之意義 框架裡的函數內容,通常扮演「預設函數」的角色,表達了慣例之行為。慣 例是自動化科技的基本觀念,也是軟體設計的重要觀念。拿汽車自動排擋做例子 吧﹗自動排擋的優點是:汽車會「自動地」依照速度而換擋,亦即會依慣例來維 持汽車的平穩。這還不夠,若由司機駕駛更佳﹗例如您只要告訴計程車司機: 「到士林夜市」,司機會依照其經驗習慣而選取路線,讓您舒適抵達夜市。更重 要的是,您可特別指示司機,他會按照您的意思而「修正」其慣例。因之慣例的 重要特色為: ● 讓使用者更加輕鬆愉快。 例如上述汽車的三個層次是: 第 2 章 應用框架魅力的泉源:反向溝通 49 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 汽車 司機 乘客 因為汽車會自動依慣例換擋,司機就輕鬆多了。也因為司機會依慣例選 擇理想的路線,乘客不必操心。 ● 慣例是可修正的。 慣例只適合一般狀況,若遇特殊狀況發生,應立即修正之。例如波音 747 客機會依照慣例起降,但遭遇特殊狀況(如碰到一大群鴿子),飛行員會立 即修正之。這飛行員的判斷凌駕於慣例之上,達到修正之目的。在電腦軟體 上,也具有三個層次: 電腦硬體 作業系統 應用程式 作業系統包含了各式各樣的慣例函數,自動化地指揮硬體,其降低了應 用程式之負擔。Linux/Windows 等作業系統已有所改進了。在事件驅動觀念 中,作業系統會不斷與應用程式溝通,不斷修正其慣例,裨對外界的事件提 供迅速反應與服務。如下圖: 50 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 電腦硬體 作業系統 應用程式 抽象類別 具體類別 抽象類別的預設函數扮演「備胎」角色,當子類別並未覆寫(Override)該函數 時,就會使用備胎。一旦將抽象類別移入框架中,框架就提供預設行為了。 2.5.2 以 Java 程式闡述預設行為 在 Java 裡,預設行為通常表現達於父類別的函數裡,讓子類別自由決定要不 要覆寫(Override)它,如果覆寫它,就會執行子類別的函數;反之。如果不覆寫 它,就會執行父類別的預先所寫的函數。請看下述的預設函數之範例: // Employee.java package _objects; public abstract class Employee { public abstract void SetFee(float basic_fee, float disc); public abstract float GetTotal(); public abstract void display(); } // SalesPerson.java package _objects; public abstract class SalesPerson extends Employee{ 第 2 章 應用框架魅力的泉源:反向溝通 51 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ protected String name, sex; protected float BasicFee, Discount; public SalesPerson(String na, String sx) { name = na; sex = sx; } public void SetFee(float basic_fee, float disc){ BasicFee = basic_fee; Discount = disc; } public void display() { System.out.println(name + ", Fee: " + this.GetTotal()); }} // SalesSecretary.java package _objects; public class SalesSecretary extends SalesPerson{ public SalesSecretary(String na, String sx) { super(na, sx); } public float GetTotal() {return BasicFee * Discount - 100; } } // JMain.java import _objects.*; public class JMain { public static void main(String[] args) { Employee linda = new SalesSecretary("Linda Fan", "Female"); linda.SetFee(2500f, 0.7f); linda.display(); }} 請你仔細看看此程式的執行過程,是很微妙而有趣的。此程式執行時,先執 行 JMain 類別的 main()函數,執行到指令: linda.display(); 就轉而執行 SalesPerson 類別預設的 display()函數: public void display() { System.out.println(name + ", } Fee: " + this.GetTotal()); 執行到指令:this.GetTotal(); 就轉而執行 SalesSecretary 類別的 GetTotal()函數: public float GetTotal() { return BasicFee * Discount – 100; } 52 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 這程式顯示了「抽象類別 + 預設函數」的美妙組合,如下圖所示: JMain main() { linda.display() } SalesPerson display() { this.GetTotal() } (反向呼叫) SalesSecretary GetTotal() { ……. } 雖然 main()函數仍為程式的啟動者,但主要的處理過程是在 SalesPerson 的 display()函數內。是它決定呼叫 GetTotal()的。子類別 SalesSecretary 扮演配角,其 GetTotal()只是供 SalesPerson 的 display()函數來呼叫之。前面也提供,因為抽象類 別掌握主控權,複雜的指令皆擺在抽象類別中,因而大幅簡化了具體類別開發者 的負擔。◆ 第 3 章 如何打造應用框架 53 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第二篇 無之(抽象)以為用 ~~老子.道德經:有之以為利,無之以為用~~ 畚箕必須先挖空(無之)才能拿來裝東西(有之)。 所以先無之而後始能有之。 抽象(Abstraction)是達到無之的一種手段或技術。 抽象出父類別是軟體業者實踐無之的慣用手藝。 而衍生則是軟體業者實踐有之的慣用手藝。 54 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3 第 章 如何打造 應用框架 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 3.1 基礎手藝:抽象(無之)與衍生(有之) 3.2 打造框架:細膩的抽象步驟 3.2.1 基本步驟 3.2.2 細膩的手藝(一):比較資料成員 3.2.3 細膩的手藝(二):比較函數成員 3.2.4 細膩的手藝(三):將抽象類別轉為介面 第 3 章 如何打造應用框架 55 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 請注意: ※ 如果你只對如何「使用」Android 框架有興趣的話,可以跳過本 章,直接進入第 4 章。 ※ 基於前兩章的概念,你已經對應用框架有足夠的基礎可好好使用 Android 應用框架了。 ※ 如果你想探討如何構思及打造應用架構,本章可帶你入門,然而 應用框架的打造,藝術含量頗高,工法與心法兼俱,尤須經驗 的累積,始能精益求精。本章提供給你一個美好的起點。 3.1 基礎技藝:抽象(無之)與衍生(有之) 前面兩章介紹了如何以 Java 表示抽象類別,以及如何建立類別繼承體系 等;其都偏向技巧,著重於表達之形式及其正確性。基於前面兩章所建立的基 礎,本章將進入構思與設計層次,說明如何發揮人類天賦的抽象能力,除了上 述的正確性之外,更追求設計的美感,設計更優雅的介面、整合出更具整體和 諧之應用框架。 「抽象」(Abstract)一詞常令人覺得那是「難以體會」的事。在軟體設計上, 如果您把「抽象」定義為「抽出共同之現象」,就是件輕鬆愉快的事了。例如, 觀察兩個相似的類別,並分辨其相同與相異點,然後把相同點抽離出來,構成 父類別,就是抽象類別了。就廣義上而言,凡是經由下述過程: Step 1. 觀察幾個相似的類別。 Step 2. 分辨它們的異同點。 Step 3. 把它們的相同點抽離出來。 而導出的父類別皆稱為抽象類別。 ◎ 以正方形為例 茲拿個簡單例子來說吧﹗有三個方形,分別為直角、圓角及缺角,如下: 56 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 首先分辨它們的異同點,然後將其共同部分抽離出來,如下: 我們稱這過程為「抽象」(Abstraction) 。並稱此圖形為「抽象圖」,其只含 共同部分,而相異部分從缺。原有的直角及圓角方形,為完整圖形,稱為「具體 圖」。一旦有了抽象圖,就可重複使用(Reuse) 它來衍生出各種具體圖,且事半 功倍﹗例如: ●用途 1 ── 衍生直角方形。 拷貝一份抽象圖,在圖之四角分別加上┌、┘、└及┐,就成為直 角方形了,如下: ●用途 2 ── 衍生圓角方形。 拷貝一份抽象圖,在圖之四角分別加上╭、╰、╯及╮,就成為 圓角方形了,如下: ●用途 3 ── 衍生球角方形。 第 3 章 如何打造應用框架 57 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 拷貝一份抽象圖,在圖之四角各加上● 就成為: 這個簡單例子,說明了無之以為用,有之以為利的設計哲理。 ◎ 以火鍋店為例 例如,有一家火鍋店,它的客人有些要吃石頭火鍋,有些要吃沙鍋魚頭, 也有些要吃韓國碳烤等。所以客人的需求是多樣化的,如下圖所示: 客人 1 吃: 石頭火鍋 客人 2 吃: 沙鍋魚頭 客人 n 吃: 韓國碳烤 圖 3-1 火鍋店的桌子 火鍋店的基本設計程序是: Step 1 ---- 釐清客製化與非客製化之界線 因為這些桌子,除了鍋子部份不一樣之外,其餘是相同的,界線清楚了。 Step 2 ---- 分離客製化與非客製化部分 將鍋子與桌子分離開來,也就是把變異部份抽離出來(在桌面上挖一個 洞)之後,剩下的部份就相當一致了。如下圖 3-2 和圖 3-3 所示: 58 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 一致的介面 抽掉(分離) 石頭火鍋 沙鍋魚頭 韓國碳烤 圖 3-2 將客製化部份分離出來 把變異部份抽離出來(在桌面上挖一個洞)之後,剩下的部份就相當一致 了,設計師只需要設計一款桌子(即框架)就可以了,火鍋店可視空間及客人數 而決定需訂製幾張桌子(即大量訂購框架)。因為不需要為石頭火鍋、沙鍋魚 頭、韓國碳烤等各設計其專用的桌子,所以能大量生產桌子(即框架的無限量 產)。至於鍋子部份,因為石頭火鍋、沙鍋魚頭、韓國碳烤等各有所不同,所以 必須個別設計(即客製化),如下圖 3-3 所示: 框架(抽象類別) 多樣化的組件(子類別) 圖 3-3 無之,始能得框架 從上述簡單的生活例子中,您能體會出框架的基礎手藝:細心釐清客製化 第 3 章 如何打造應用框架 59 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 與非客製化的界線,將其「分」(即無之)離開來,配合客人多樣性的需求而量身 定做客製化部分,然後將兩者組「合」(即有之)成為客人所喜愛的產品或服務。 換句話說,一旦有了框架和介面之後,當客人上門了,桌子與鍋子就能一拍即 合,如下圖所示: 框架(抽象類別) 鍋子(子類別) 泡菜鍋餐桌 鍋子(子類別) 砂鍋魚頭餐桌 圖 3-4 有之,始能服務客人而獲利 火鍋桌子元件是「實」的,在桌面上挖一個洞之後,得出一個介面,此介面 塑造出一個「虛」的空間,此虛的空間可用來容納多樣性的小元件 ----- 石頭火 鍋、韓國碳烤等,而這些小元件也是「實」的。就像老子在數千年前已經說過, 像房子的中間、門、窗皆是虛的空間的,才能供人們進出、居住與透透空氣。 其積極效果是:日後依新環境的條件而加以調整、充實,創造出多樣化的用 途。例如畚箕的中間是空、虛的,才能裝泥土、垃圾等各式各樣的東西。此 外,畚箕的空無,創造了畚箕的重複使用性(Reusability) ,裝完了泥土,倒掉 之後,還可拿來裝垃圾等,不斷重複使用之,一直到壞掉為止。 60 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3.2 打造框架:細膩的抽象步驟 3.2.1 基本步驟 上一節的日常生活實例中,說明了兩個重要動作: ☆抽象── 從相似的事物中,抽離出其共同點,得到了抽象結構。 ☆衍生── 以抽象結構為基礎,添加些功能,成為具體事物或系統。 同樣地,在軟體方面,也常做上述動作: ★抽象── 在同領域的程式中,常含有許多類別,這些類別有其共同點。程 式師將類別之共同結構抽離出來,稱為抽象類別(Abstract Class)。 其抽象之步驟是: Step 1. 觀察幾個相似的類別。 Step 2. 分辨它們的異同點。 Step 3. 把它們的相同點抽離出來。 ★衍生── 基於通用結構裡的抽象類別,加添些特殊功能,成為具體類別, 再誕生物件。 所以「抽象類別」存在之目的,是要衍生子類別,而不是由它本身來誕生物件。 由於抽象類別本身不誕生物件,所以有些函數並不完整。反之,如果類別內之 函 數 , 皆 是 完 整 的 , 而 且 要 用 來 誕 生 物 件 , 就 稱 它 為 具 體 類 別 (Concrete Class)。所謂不完整,就是函數的內容從缺,例如: public abstract class Person { //....... public abstract void Display(); } 這 Display()函數內的指令從缺,等待子類別來補充,如下: 第 3 章 如何打造應用框架 61 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ // EX03_01 // Person.java package _objects; public abstract class Person { protected String name; public void SetName(String na) { name = na; } public abstract void Display(); } // Employee.java package _objects; public class Employee extends Person { public void SetName(String na) { super.SetName(na); } public void Display() { System.out.println("Employee: " + name ); } } // JMain.java import _objects.*; public class JMain { public static void main(String[] args) { Person p = new Employee(); p.SetName("Peter Chen"); p.Display(); } } 這 Employee 是個子類別,已經將 Display() 函數充實完整,可用來誕生物件 了,此時 Employee 為具體類別。 那麼,什麼時候會跑出像 Person::Display()這種抽象的(即內容從缺的)函 數呢﹖答案是:在上述第 3 步驟中,抽離出共同點時,因為 Display() 函數之內 容不相同,只抽離出函數名稱而已。例如,有兩個類別如下: 62 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public class Employee { private String name; 相同 private int salary; public void SetName(String na) { name = na; } public void SetSalary(int s) { salary = sa } public void Display() { System.out.println(“Emp: ” + name + “Salary: ” + salary); } } 相同 相同 public class Customer { private String name; public voidSetName(String na) { name = na } public void Display() { System.out.println( “Cust: ” + name) } } 首先把相同的資料成員抽離出來,如下: public class Person { private String name; } 接著,把相同的函數成員抽離出來,如下: public class Person { private String name; public void SetName( String na ) { } name = na; } 最後,將名稱相同,但內容不同之函數抽離出來,成為抽象函數如下: public abstract class Person { private String name; public void SetName(String na) { public abstract void Display(); } name = na; } 由於只抽出 Display() 的名稱,而內容從缺,這就是抽象函數。於是,Person 就 成為抽象類別了。從上述實例之中,可歸納出「抽象」的三個分節動作: 第 3 章 如何打造應用框架 63 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ☆ 分辨 ☆ 封藏 ☆ 抽象 ──明察秋毫,把穩定與善變部份區分出來。 ──把差異部份的指令封藏於子類別中。 ──把類別的共同點抽象出來,成為抽象類別。 在 Java 程式上,抽象類別必須與具體類別合作,才能誕生物件來提供服 務;抽象類別跟具體類別有密切的互動,因而必須熟悉如下兩項重要的手藝, 才能落實好的抽象過程: ● 產生抽象類別。 ● 加入預設(Default)指令,提高彈性,仍保持共通性。 現在,就準備產生抽象類別。請從一個簡單 Java 程式介紹起吧! // EX03_02 // Employee.java package _objects; public class Employee { private String name; private String sex; static class Fee { public static float BasicFee; public static float Discount; public static float GetTotal(){ return BasicFee * Discount; } } public Employee(String na, String sx) { name = na; sex = sx; } public void SetFee(float basic_fee, float disc) { Fee.BasicFee = basic_fee; Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal());} } // Customer.java package _objects; public class Customer { private String name; private String sex; static class Fee { public static float AnnualFee; 64 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public static float Discount; public static float GetTotal() { return AnnualFee * 32.25f * Discount; } } public Customer(String na, String sx) { name = na; sex = sx; } public void SetFee(float annual_fee, float disc) { Fee.AnnualFee = annual_fee / 32.25f; // Convert to US$ to NT$ Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); } } // JMain.java import _objects.*; public class JMain { public static void main(String[] args) { Employee emp = new Employee("Tom", "M"); Customer cust = new Customer("Lily", "F"); emp.SetFee(1000f, 0.9f); cust.SetFee(500f, 0.75f); emp.Display(); cust.Display(); }} 此程式輸出: Tom's fee: 900.0 Amy's fee: 375.0 當您看到這兩個類別---- Employee 與 Customer 時,就像看到火鍋店裡的兩 張餐桌,有些部份是一致的,也有些差異部份。類別裡包含兩項重要的成份: 資料成員和函數成員。於是先對資料成員比較一翻吧! 第 3 章 如何打造應用框架 65 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3.2.2 細膩的手藝(一):比較資料成員 茲比較兩類別的資料成員: public class Employee { private String name; private String sex; ststic class Fee { public static float BasicFee; public static float Discount; public static float GetTotal() { return BasicFee * Discount } 相同 } ....... } 相異 public class Customer { private String name; private String sex; static class Fee { public static float AnnualFee; public static float Discount; public static float GetTotal() { return BasicFee * 32.25 * Discount } } ....... } 接著,抽離出共同點,放入抽象類別中,如下: public class Person { private String name; private String sex; ...... } 其它部分,仍留在原類別中。於是上述程式相當於: 66 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ // EX03_03 // Person.java package _objects; public class Person { protected String name; protected String sex; } // Employee.java package _objects; public class Employee extends Person{ static class Fee { public static float BasicFee; public static float Discount; public static float GetTotal() { return BasicFee * Discount; } } public Employee(String na, String sx){ name = na; sex = sx; } public void SetFee(float basic_fee, float disc) { Fee.BasicFee = basic_fee; Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); } } // Customer.java package _objects; public class Customer extends Person { static class Fee { public static float AnnualFee; public static float Discount; public static float GetTotal() { return AnnualFee * 32.25f * Discount; } } public Customer(String na, String sx){ name = na; sex = sx; } public void SetFee(float annual_fee, float disc){ Fee.AnnualFee = annual_fee / 32.25f; // Convert to US$ to NT$ Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); } } // JMain.java import _objects.*; public class JMain { 第 3 章 如何打造應用框架 67 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public static void main(String[] args) { Employee emp = new Employee("Tom", "M"); Customer cust = new Customer("Lily", "F"); emp.SetFee(1000f, 0.9f); cust.SetFee(500f, 0.75f); emp.Display(); cust.Display(); }} 至此,抽象的結果是:得到 Person 抽象類別。 3.2.3 細膩的手藝(二):比較函數成員 其步驟如下: Step 1: 抽出名稱、參數及內容皆一致的函數。 比完了資料成員,接著比較函數成員。可看出 Employee 和 Customer 兩類別 的建構者函數的參數及內容是一致的,就將之抽象到 Person 父類別裡,如下的 Java 程式: // EX03_04 // Person.java package _objects; public class Person { protected String name; protected String sex; public Person(String na, String sx) { name = na; sex = sx; } } // Employee.java package _objects; public class Employee extends Person{ static class Fee { public static float BasicFee; public static float Discount; public static float GetTotal() 68 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ { return BasicFee * Discount; } } public Employee(String na, String sx) { super(na, sx); } public void SetFee(float basic_fee, float disc){ Fee.BasicFee = basic_fee; Fee.Discount = disc; } public void Display(){ System.out.println(name + "'s fee: " + Fee.GetTotal()); } } // Customer.java package _objects; public class Customer extends Person { static class Fee { public static float AnnualFee; public static float Discount; public static float GetTotal(){ return AnnualFee * 32.25f * Discount; } } public Customer(String na, String sx) { super(na, sx); } public void SetFee(float annual_fee, float disc) { Fee.AnnualFee = annual_fee / 32.25f; // Convert to US$ to NT$ Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); } } // JMain.java import _objects.*; public class JMain { public static void main(String[] args) { Employee emp = new Employee("Tom", "M"); Customer cust = new Customer("Lily", "F"); emp.SetFee(1000f, 0.9f); cust.SetFee(500f, 0.75f); emp.Display(); cust.Display(); }} 此程式輸出: Tom's fee: 900.0 Amy's fee: 375.0 第 3 章 如何打造應用框架 69 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ Step 2: 抽出名稱相同、參數及內容有些差異的函數。 這個步驟較為複雜,其細膩過程如下: ★ 找出相異點。 ★ 運用多形函數來盡量吸收相異點。 ★ 吸收之後,有些原不相同函數,會變成相同了。 ★ 將相同之函數提升到高層類別中。 其關鍵在於:如何運用多形函數﹖讓我們細心介紹這個重要手藝吧!首先 請看個更簡單的 Java 程式範例: AA 類別 BB 類別 private String x; public void Print() { System.out.println(x); } ..... private int x; public void Print() { System.out.println(x); } ..... 資料成員的型態並不相同,找到了相異點: String x;l 相異 int x; 這導致函數的內容也不一樣: void Print() { System.out.println( x ); } void Print() { System.out.println( x ); End Sub 相異 此時,就將相異點封藏起來,那麼多形函數就派上用場了﹗茲為兩類別各定義 一個 GetData()函數: 70 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ AA 類別 private String x; public final void Print() { System.out.println(GetData()); } public String GetData() { return x; } ..... BB 類別 private int x; public final Print() { System.out.println(GetData()); } public String GetData() { return String.valueOf(x); } ..... 於是,Print()函數變成為相同點了,可擺入抽象類別中;然後也把 GetData()的 定義擺入抽象類別裡,如下圖: SuperAB pubic final void Print() { System.out.println( GetData() ); } public String GetData() {} AA private String x; public String GetData() { return x; } BB private int x; public String GetData() { return String.valueOf(x); } 在上述例子中,SuperAB 類別的 GetData()裡沒有任何指令,它就相當於 abstract 抽象函數。所以上述指令: public String GetData(){} 就相當於: public abstract String GetData(); @更多资源@http://cleopard.download.csdn.net/ @更多资源集合@http://cleopard.download.csdn.net/album 第 3 章 如何打造應用框架 71 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 這例子已讓您了解:透過 GetData()卡榫函數將差異點包裝起來,然後將 Print()函數抽象出來,擺入抽象父類別裡。其中,Print()函數的參數是一致的(包 括沒有參數),只有函數裡的部分指令有差異而已。如果參數有些差異時,又該 如何呢?請您再看個例子吧!如下: // EX03_05 // JMain.java package _objects; public class JMain { private void print(double x, int y) { System.out.println(x+y); } private void print(int k, int y) { System.out.println(k*y); } public static void main(String[] args) { JMain mObj = new JMain(); mObj.print(3.6, 6); mObj.print(2, 60); }} 請您練習如何觀察這兩個 print()函數裡的異同,會發現其參數型態並不相 同。於是,找到了相異點: public void print( double x, int y ) { System.out.println( x + y ); 相異 } 相異 public void print( int k, int y ) { System.out.println( k * y ); } 一樣地,只要將差異點包裝起來,就行了。其步驟如下: 72 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ Step-2a 建立參數之抽象類別,例如: Number Double Integer 則兩個 print()函數之參數就一致了,如下: public void print( Number numb, int y ) { ……… } Step-2b 使用多形函數將內部差異指令包裝起來,例如,以 prStr()函數包裝之 後,print()函數成為: public void print( Number numb, int y) { System.out.println( numb.prStr(y) ); } 如下述 Java 程式: // EX03_06 // Number.java package _objects; public abstract class Number { public abstract String prStr(int y); } // jvInt.java package _objects; public class jvInt extends Number{ private int x; public jvInt(int i) { x = i; } public String prStr(int y) { return String.valueOf( x * y ); } } // jvFloat.java package _objects; public class jvFloat extends Number { 第 3 章 如何打造應用框架 73 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ private float x; public jvFloat(float a) { x = a; } public String prStr(int y) { return String.valueOf(x + y); } } // JMain.java import _objects.*; import _objects.Number; public class JMain { private void print(Number numb, int y) public static void main(String[] args) { JMain mObj = new JMain(); jvFloat a = new jvFloat(3.6f); jvInt b = new jvInt(2); mObj.print(a, 6); mObj.print(b, 60); }} { System.out.println(numb.prStr(y)); } 此程式輸出: 9.6 120 接著,繼續抽出 print()函數,擺入抽象父類別中,如下: // EX03_07 // Number.java package _objects; public abstract class Number { public abstract String prStr(int y); public void print(int y) { System.out.println( this.prStr(y) ); } } // jvInt.java package _objects; public class jvInt extends Number{ private int x; public jvInt(int i) { x = i; } public String prStr(int y) { return String.valueOf(x * y); } } // jvFloat.java package _objects; public class jvFloat extends Number { 74 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ private float x; public jvFloat(float a) { x = a; } public String prStr(int y) { return String.valueOf(x + y); } } // JMain.java import _objects.*; import _objects.Number; public class JMain { public static void main(String[] args) { JMain mObj = new JMain(); jvFloat a = new jvFloat(3.6f); jvInt b = new jvInt(2); mObj.print(a, 6); mObj.print(b, 60); }} 現在,請回到前面 EX03-04 的例子吧﹗請您練習觀察這兩個函數裡的異同點: public class Employee { ...... public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); } } 與 public class Customer { ...... public void Display(){ System.out.println(name + "'s fee: " + Fee.GetTotal()); } } 前面已認定 Employee.Fee 類別與 Customer.Fee 類別的內容並不相同,是相 異之處,這導致兩個 Fee.GetTotal()是相異之處。因之,Display()不能直接擺入 抽象類別中。現在設計一個 Overridable 函數,吸收其相異點,如下程式: // EX03_08 // Person.java package _objects; public class Person { 第 3 章 如何打造應用框架 75 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ protected String name; protected String sex; public Person(String na, String sx) { name = na; sex = sx; } } // Employee.java package _objects; public class Employee extends Person{ static class Fee { public static float BasicFee; public static float Discount; public static float GetTotal(){ return BasicFee * Discount; } } public Employee(String na, String sx){ super(na, sx); } public void SetFee(float basic_fee, float disc){ Fee.BasicFee = basic_fee; Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); }} // Customer.java package _objects; public class Customer extends Person { static class Fee { public static float AnnualFee; public static float Discount; public static float GetTotal(){ return AnnualFee * 32.25f * Discount; } } public Customer(String na, String sx) { super(na, sx); } public void SetFee(float annual_fee, float disc) { Fee.AnnualFee = annual_fee / 32.25f; // Convert to US$ to NT$ Fee.Discount = disc; } public void Display() { System.out.println(name + "'s fee: " + Fee.GetTotal()); }} // JMain.java import _objects.*; public class JMain { 76 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public static void main(String[] args) { Employee emp = new Employee("Tom", "M"); Customer cust = new Customer("Lily", "F"); emp.SetFee(1000f, 0.9f); cust.SetFee(500f, 0.75f); emp.Display(); cust.Display(); }} 透過 GetFee()的協助,Display()就能飛上枝頭變鳳凰了。依樣畫葫蘆,也能 輕易地讓另一個函數:SetFee()飛上枝頭變鳳凰。這就留給您自己練習了。 3.2.4 細膩的手藝(三):將抽象類別轉為介面 3.2.4.1 框架裡定義了許許多多的介面 前面說過,框架裡含有抽象類別,而抽象類別裡含有預設的函數,這些函 數的內容將表現出框架的預設行為。然而,框架設計師經常回發現有些抽象類 別 不 需 提 供 預 設 行 為 , 於 是 抽 象 類別裡的所有函數都成為抽象函數(abstract function)了。這種函數就是空的函數,只有定義而無實作指令。此時,這種純 粹的抽象類別就相當於 Java 的介面機制了。例如: public abstract class Graph { //純粹抽象類別 public abstract void draw(); public abstract void paint(); } 這個 Graph 抽象類別,表面上是一個父類別,但在意義上,它代表一個介 面。所以它也就相當於: // IGraph.java public interface IGraph { void draw(); void paint(); } 第 3 章 如何打造應用框架 77 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 那麼,為何 Java 既提供 Interface 機制,又提供純粹抽象類別呢? 這有一個 歷史因素,在 1997 年以前,主要 OOP 語言(如 C++)都沒有提供 Interface 機制, 那時是以純粹抽象類別來表達介面,也藉由多重繼承來表達出多重介面。 由於框架裡含有抽象類別,而有些抽象類別並不需要提供預設函數,於是 就以 Java 的介面機制表達之。所以框架你會發現框架(如 Android 或.Net)裡定義 了許許多多的介面。例如,Android 框架裡的 OnClickListener 介面,如下的 Android 應用程式碼: //Android 應用程式:MyActivity.java public class MyActivity extends Activity implements OnClickListener { /** Called when the activity is first created. */ private final int DB_Version = 1; private final int DB_Mode = Context.MODE_PRIVATE; private final int WC = ViewGroup.LayoutParams.WRAP_CONTENT; private SQLiteDatabase db=null; private Button btn; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); btn = new Button(this); btn.setText("Exit"); btn.setOnClickListener(this); setContentView(btn, new LinearLayout.LayoutParams(WC, WC)); try { db = createDatabase("MyDB", DB_Version, DB_Mode, null); } catch(FileNotFoundException e){ Log.e("ERROR", e.toString()); db = null; } if (db != null) setTitle("create DB OK!"); else setTitle("Error!"); } public void onClick(View v){ if(v.equals(btn)) this.finish(); }} 78 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 3.2.4.2 打造框架時,如何抽象出界面呢? 由於介面是純粹抽象類別,所以能運用本章前面各節所介紹的抽象步驟和 細膩手藝來抽象出介面。例如有兩個 Java 類別,各代表學生註冊領域裡的:「大 學生」與「研究生」概念,如下: // EX03_09 package _objects; public class 大學生 //如同石頭火鍋餐桌 { private String Name; public float ComputeTuition(int credit) { if (credit > 6) credit = 6; return (credit -1) * 500 + 5000; }} // BasicFee.java package _objects; public class 研究生 //如同砂鍋魚頭餐桌 { private String Name; public float ComputeTuition(int credit) { if (credit > 6) credit = 6; return credit * 700 + 5000; }} 其中的 ComputeTuition()函數可計算出大學生或研究生的學費。現在,就運 用本章前面各節所介紹的「抽象」步驟來打造介面。茲比較上述的兩個類別,看 出其不一樣之處: 即「大學生」類別裡的指令 ----- (credit-1)*500 與「研究生」類別裡的指令 ----- credit *700 其餘部份則是一樣的。這導致兩個 ComputeTuition()不能直接擺入抽象類別中。 現在設計一個 Overridable 函數:GetValue()抽象函數來封藏之,吸收其相異點, 就能讓 ComputeTuition()飛上枝頭變鳳凰了,如下程式: // EX03_10 package _student; import _interface.*; 第 3 章 如何打造應用框架 79 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public class 學生 { private String Name; public float ComputeTuition(int credit) { if (credit > 6) credit = 6; return tc.GetValue(credit) + 5000; } protected abstract float GetValue(int credit); } public class 大學生 extends 學生 { //如同鍋子 public float GetValue(int credit) { return (credit -1) * 500; } } public class 研究生 extends 學生 { //如同鍋子 public float GetValue(int credit) { return credit * 700; } } 接著,再將抽象函數 GetValue()獨立出來,單獨擺入一個抽象類別裡,就能為純 粹抽象類別了,也就能以介面表示出來,如下: // EX03_11 package _interface; public interface ITuition //學費介面 { public float GetValue(int credit); } package _student; import _interface.*; public class 學生 { private String Name; private ITuition tc; public void Setter(ITuition tuiObj) { tc = tuiObj; } public float ComputeTuition(int credit) { if (credit > 6) credit = 6; return tc.GetValue(credit) + 5000; }} package _tuition_plugin; import _interface.*; public class 大學生學費 implements ITuition { //如同鍋子 public float GetValue(int credit) { return (credit -1) * 500; } } public class 研究生學費 implements ITuition { //如同鍋子 public float GetValue(int credit) { return credit * 700; } 80 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ } 以上之類別支持 ITuition 介面,於是能將「學生」物件與「大學生學費」小 物件結合起來,此外也能將「學生」與「研究生學費」或其它小物件結合起來, 成為完整的資訊應用程式,如下之 JMain 主程式: import _student.*; import _tuition_plugin.*; public class JMain { public static void main(String[] args) { float t1, t2; 學生 Lily = new 學生(); 大學生學費 under_tui = new 大學生學費(); Lily.Setter(under_tui); t1 = Lily.ComputeTuition(5); 學生 Peter = new 學生(); 研究生學費 grad_tui = new 研究生學費(); Peter.Setter(grad_tui); t2 = Peter.ComputeTuition(7); System.out.println( "Lily: " + String.valueOf(t1) + ", Peter: " + String.valueOf(t2)); }} 此程式計算出大學生 Lily 和研究生 Peter 的學費: Lily: 7000, Peter: 9200 ◆ 高煥堂 教你最先進的「現代軟體分析與設計」,它是 OOP + UML + OOAD + Architecture Design 的一脈相傳、精雕細琢,漸臻於完美之境。請閱讀「附錄 B-4」。 第 4 章 Android 應用程式設計的基礎手藝:12 技 81 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 第三篇 有(繼承)之以為利 ~~老子.道德經~~ 無之而得 Android 框架, 有之而得 Android 應用程式。 應用程式裡的子類別繼承框架裡的父類別, 是實踐有之的慣用手藝。 82 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 4 第 章 Android 應用程式設計 的基礎手藝:12 技 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 4.1 #1:如何建立 Menu 選單 4.2 #2:如何呈現按鈕(Button)之 1 4.3 #3:如何呈現按鈕(Button)之 2 4.4 #4:如何進行畫面佈局(Layout) 4.5 #5:如何呈現 List 選單之 1 4.6 #6:如何呈現 List 選單之 2 4.7 #7:如何運用相對性佈局(Relative Layout) 4.8 #8:如何運用表格式佈局(Table Layout) 4.9 #9:如何動態變換佈局 4.10 #10:如何定義自己的 View 4.11 #11:如何定義一組 RadioButton 4.12 #12:一個 Activity 啟動另一個 Activity 第 4 章 Android 應用程式設計的基礎手藝:12 技 83 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 從本章開始,將開始介紹及演練 Android 應用程式開發的基本 36 技了。 其中,有 35 技是屬於 Android 框架之上的 Java 程式開發技巧。而第 36 技則屬 於 Android 框架之下的 C 程式開發技巧。茲拿麥當勞的漢堡來做比喻,如下圖 所示: 芝麻:Android 應用程式(共 35 技) 上層麵包:Android 框架 牛肉和 Cheese:框架與硬體之間 的 C 組件(第 36 技) 底層麵包:硬體組件 併不是 C 組件的技巧較少,而是本書範圍的緣故。本書的主角是「應用 程式開發」,所以偏重於芝麻部份的手藝。筆者的第二本書:「Android 應用 軟體架構設計」,也已經出版了,敬請閱讀之。 這裡以芝麻比喻應用程式,並不是說應用程式的渺小,而表示它的多與 香,若能與起司、牛肉互相搭配,更有特別風味。 在本章裡,將直接切入應用程式開發的技巧。如果你還沒有安裝過 Android SDK 及其 Eclipse 開發環境的話,請你先閱讀本書附錄-A,其內含: ◆ 如何安裝 Android SDK 及其開發環境 ◆ 如何著手撰寫 Android 應用程式 ◆ 如何執行 Android 應用程式 如 果 你 覺 得 附 錄 -A 還 不 夠詳細的話,請你上網tom-kao.blogspot.com或 www.misoo1.com 有更詳細的解說。現在就讓我們一起來演練 36 技吧!! 84 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 4.1 #1:如何建立 Menu 選單? 我想大家都很熟悉 Menu 選單的用途了,本節就來說明如何定義 Android 的 Menu 選單,也演練其操作。 4.1.1 操作情境: 1. 此程式開始執行後,按下就出現選單如下: 2. 如果選取選項,畫面標題(Title)區顯示出字串:”Insert…”。 3. 再按下顯示出選單,如果選取選項,提標(Title)區顯示 出:”Delete…”。 4. 再按下顯示出選單,如果選取選項,程式就結束了。 4.1.2 撰寫步驟: Step-1: 建立 Android 專案:ex01。 Step-2: 撰寫 Activity 的子類別:ex01,其程式碼如下: // ---- ex01.java 程式碼 ---package com.misoo.ex01; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class ex01 extends Activity { public static final int ADD_ID = Menu.FIRST; public static final int DELETE_ID = Menu.FIRST + 1; 第 4 章 Android 應用程式設計的基礎手藝:12 技 85 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ public static final int EXIT_ID = Menu.FIRST + 2; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, ADD_ID, 0, R.string.menu_add); menu.add(0, DELETE_ID, 1, R.string.menu_delete); menu.add(0, EXIT_ID, 2, R.string.menu_exit); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case ADD_ID: setTitle("Insert..."); break; case DELETE_ID: setTitle("Delete..."); break; case EXIT_ID: finish(); break; } return super.onOptionsItemSelected(item); }} Step-3: 修改/res/values/strings.xml 的內容,更改為: ex01 Add Item Del Item Exit 並儲存之。 Step-4: 執行之。 4.1.3 說明: 1. 一開始,框架就反向呼叫 onCreate()函數,也呼叫 onCreateOptionsMenu()。 2. 當你選取選項時,框架會反向呼叫 onOptionsItemSelected()函數。 3. 框架是主角,ex01 類別只是被呼叫的配角,複雜的控制邏輯都為框架所做掉 了,所以程式碼便得簡單清晰了。 4. 呼叫 onCreate()函數時,此函數首先正向呼叫父類別 Activity 的 onCreate()函 數,先執行父類別的預設行為,然後才執行 ex01::onCreate()函數的附加行 86 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 為。繼續執行到 setContentView(R.layout.main)指令時,就去讀取 main.xml 的 內容,依據它來進行螢幕畫面的佈局(Layout),並顯示出來。 5. 呼 叫 onCreateOptionsMenu() 函 數 時 , 執 行 到 指 令 : menu.add(0, ADD_ID, R.string.menu_add)就 去 讀 取 /res/values/strings.xml 檔 的 內 容 , 取 得 字 串 “Add Item”,顯示於畫面的選單上。 4.1.4 補充說明(一): ※ 為何子類別 ex01 的 onCreate()要正向呼叫父類別的 onCreate()函數呢? ※ 因為框架的某個函數(不是 Activity::onCreate())呼叫 ex01::onCreate()函數時, 此 onCreate()函數無法自己完成整個「create」的任務,而需要父類別的預設函 數來幫忙,才得以完成之。請看個簡單的 Java 程式,你就會明白了。如下範 例: <> IGraph <> Shape void onPaint() void paint() void onPaint() { // 畫天空背景 } void paint() { onPaint(); } Bird void onPaint() { super.onPaint(); // 畫海鷗 } 框架 由於框架的 paint()呼叫子類別的 onPaint()函數,但子類別需要父類別來幫忙 畫出背景,所以呼叫了父類別的 onPaint()。如果還不清楚的話,請仔細看下述程 第 4 章 Android 應用程式設計的基礎手藝:12 技 87 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 式碼就能明瞭了。 // IGraph.java ----介面 public interface IGraph { void onPaint(); void paint(); } //-------------------------------------------------------// Shape.java ---- 父類別 import java.awt.*; public abstract class Shape implements IGraph{ Graphics m_gr; public Shape(Graphics gr) { m_gr = gr; } public void onPaint(){ // 畫天空背景 m_gr.setColor(Color.black); m_gr.fillRect(10,30, 200,100); } public void paint() { onPaint(); } } //-----------------------------------------------// Bird.java ---- 子類別 import java.awt.*; public class Bird extends Shape { Graphics m_gr; public Bird(Graphics gr) { super(gr); m_gr = gr; } public void onPaint(){ super.onPaint(); // 畫圖(海鷗)指令 m_gr.setColor(Color.cyan); m_gr.drawArc(30,80,90,110,40,100); m_gr.drawArc(88,93,90,100,40,80); m_gr.setColor(Color.white); m_gr.drawArc(30,55,90,150,35,75); m_gr.drawArc(90,80,90,90,40,80); }} //-------------------------------------------------------// JMain.java ---- 主程式 import java.awt.*; import javax.swing.*; class JP extends JPanel { public void paintComponent(Graphics gr){ super.paintComponents(gr); 88 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ IGraph cc = new Bird(gr); cc.paint(); }} public class JMain extends JFrame { public JMain(){ setTitle(""); setSize(350, 250); } public static void main(String[] args) { JMain frm = new JMain(); JP panel = new JP(); frm.add(panel); frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frm.setVisible(true); }} 此程式畫出兩隻海鷗如下圖: 首先,主程式的指令:cc.paint()呼叫子類別 Bird 的 paint(),但是 Bird 並沒 有 paint()函數,於是採用父類別 Shape 的預設 paint()函數。此預設函數呼叫 onPaint(),就反向呼叫了子類別的 onPaint()。請注意,是父類別 paint()呼叫子類 別的 onPaint();並不是父類別 onPaint()來呼叫子類別的 onPaint()。反而是子類別 onPaint()呼叫父類別的 onPaint()。 4.1.5 補充說明(二): ※ 當你修改/res/values/strings.xml 內容之後,記得要存檔,為什麼呢?因為這樣 可以更新 R.java 的內容,讓 menu.add(0, ADD_ID, R.string. menu_add)指令能找 到所要的字串。 ※ 請你花一點時間認識一下 R.java 的角色和特性,茲說明如下: 第 4 章 Android 應用程式設計的基礎手藝:12 技 89 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ---- 它(R.java)是連結*.java 的程式碼檔案和*.xml 佈局檔案的中介橋樑。 ---- 在 res/layout/裡含有許*.xml 檔案。Eclipse 根據這些.xml 檔內容而自動產 生一個「R」類別(在/src/目錄區裡)。程式師不能用手工去更動它。 ---- 當這些.xml 檔案有更新時,Eclipse 就會在你確認並將*.xml 存檔時,自動 更新它(即 R.java 檔案)的內容。 ---- 它是程式裡可使用資源(/res/)的索引,對應到*.xml 檔案或字串。讓您的 AP 很方便透過它來取得相關的資源。也就是說,*.java 程式碼透過這索 引 就 能 方 便 地 取 得 所 需 要 的 資 源 。 例 如 , 在 setContentView (R.layout.main) 指 令 裡 的 R.layout.main 就 是 一 個 索 引 項 , 指 引 到 main.xml,就使用了 main.xml 所預設的(Default)陽春型畫面佈局了。 4.2 #2: 如何呈現按鈕(Button)之 1 按鈕可說是最常用的螢幕控制單元,本節就來說明如何定義 Android 的 Button 按鈕,也演練其操作。 4.2.1 操作情境: 1. 此程式一開始,畫面出現兩個按鈕如下: 2. 如果按下按鈕,畫面標題(Title)區顯示出字串:”this is OK button”。 3. 如果選取,程式就結束了。 90 Android 應用框架原理與程式設計 36 技 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 4.2.2 撰寫步驟: Step-1: 建立 Android 專案:ex02。 Step-2: 撰寫 Activity 的子類別:ex02,其程式碼如下: // ----- ex02.java 程式碼 ----package com.misoo.ex02; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class ex02 extends Activity implements OnClickListener { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Button btn = (Button)findViewById(R.id.button); Button btn2 = (Button)findViewById(R.id.button2); btn.setOnClickListener(this); btn2.setOnClickListener(this); } public void onClick(View arg0) { switch (arg0.getId()) { case R.id.button: setTitle("this is OK button"); break; case R.id.button2: this.finish(); break; } }} Step-3: 修改/res/layout/main.xml 的內容,更改為:

Top_arrow
回到顶部
EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。