顯示具有 fundamental 標籤的文章。 顯示所有文章
顯示具有 fundamental 標籤的文章。 顯示所有文章

2015年2月11日 星期三

(Android API Guides系列)Android 應用程式架構基礎(上)


  好久沒有更新啦~寒假到了人就特別閒一點。因為本人最近剛好在學Android,坊間的教材參差不齊,入門用雖有好書,但我個人覺得還是Google自己寫的比較完整一點(廢話...),且坊間參考書也多是參考Google寫的內容加以編纂。所以才突發奇想來翻譯一下(不過因為原來的也是有些冗長,我會把重要的地方盡量挑出來省自己和大家的時間),不知道可以翻幾篇(眼神死)。不過我是完全不專業的非本科系大四生一枚,只能希望有錯誤的地方大家多多指正,有問題的話....應該是幾乎沒辦法回答XDD
  
  以下內容都是針對Google API Guide - Application Fundamentals做翻譯。


  進入正題,相信在學習程式語言的朋友們應該都有聽過Java,雖然我完全沒有學過它,但靠著大二時修的C#(念作"see sharp,"Visual C系列的一種結合C與C++的物件導向語言,詳見維基百科),關於物件導向我於是也能略懂略懂。Android app就是以Java語言編寫而成的,寫好的程式碼會交給Google開源釋放出的SDK(Software Development Kit, 軟體開發套件)去編譯(Compile, 把人用的原始語言轉換成電腦看得懂的目標語言的動作),同時也會把程式需要用到的外部資料(例如圖片、音訊檔,或是版面配置檔等xml資源檔案)一起包含進去變成一個附檔名為.apk的APK(Android Package,應用程式封裝),也就是平常我們下載APP時會在手機裡的檔案夾看到的安裝檔。

  APP一旦安裝好了,該APP就會建立起自己的一塊安全領域(security sandbox):
  • Android的作業系統(OS, operating system)是支援全多工的 Linux-based系統,每一個APP都能夠獨立運作。 
  • Android系統預設上,會指派一組唯一的Linux ID給每一個APP(這組ID只會被此系統使用,即APP本身並不會看到),而這組ID會被Android系統用來識別出那些檔案是屬於哪個APP使用。 
  • 每個應用程式程序都擁有獨立的虛擬機器 (VM, Visual Machine), 所以APP的程式碼會獨立地運作,在它自身的Linux程序中。 
  • Android會在任一個有關於此APP的組件(Activities, Services...等,後面會再提到)被要求執行時啟動這個Linux程序,然後在不再需要或是系統必須空出記憶體給其他APP使用時關閉。
  因此,我們說Android系統滿足了最小許可權原則. 也就是說,每個APP在預設上只能利用其所必要之系統部分。 這使得APP在其使用上有保護整體裝置運作的安全性,只在使用者給予權限時能夠作動。

  不過其實,還是有方法可以讓APP之間分享資料或是主動存取系統服務的:
  • 我們其實可以安排讓兩個不同的APP分享同樣的Linux ID,藉此讓它們都能存取各自的資料。為了保留系統資源,共有同樣ID的APP甚至還能夠在同一個Linux程序下執行並共有同一台虛擬機器(惟這兩個APP必須也使用同樣的憑證) 
  • 我們都知道,APP可以要求使用者提供權限以存取使用者的裝置資訊,例如通訊錄、簡訊、SD卡資料、相機、藍芽...等等。這些權限必須在安裝時被使用者允許,否則將無法進行安裝程序。
  以上大概已經介紹了Android app存在於系統中的基本樣貌,在剩下來的文章中我們將繼續看到:
  • 定義APP的核心架構組件(Core Framework Components) 
  • 清單檔(Manifest file),可用來宣告APP之組成內容及必要之裝置特性。 
  • 分離於APP程式碼,且讓使用者的APP能夠在不同的裝置組態中最佳化的資源檔(Resource files)

App核心架構組件(App Components)


  App組件是組成Android應用程式的積木,每一個組件都可以是一個不同的程式進入點,但這僅是對手機系統而言;對使用者來說,這些進入點可能必須互相關聯(也就是說使用者無法直接跳過某個組件進到下個組件)。但是,這些組件在系統中確實是獨立存在的個體且各自擁有明確的定位:每一個組件都能幫助定義開發者設計的應用程式之整體表現。

  架構中共有四種不同的組件,各自有不同的目的及生命週期(Life cycle, 定義該組件如何創生及消滅):
Activities(近似於網頁的頁面,或是C#之form元件)
  Activities代表著單一個視窗(screen)以及其使用者介面(UI, user interface)。舉例來說,一個郵件APP可能會有一個activity呈現出一列新的郵件,另一個activity讓使用者撰寫郵件,再一個讓使用者閱讀郵件。雖然他們可能呈現出一體感(介面上、使用者操作使用上),但其實他們是獨立運作的個體(透過intent來聯繫,透過bundle來傳遞資料,後面會再提)。 
  如此一來,就能夠讓activities達到共用的目的,使用者可以在不同的APP裡開啟同樣的activity(如果在郵件APP中我們允許此行為,可能可以利用public class的方式實作)。舉例來說,假設今天另有一個拍照APP想要把照片利用郵件傳遞給別的使用者,我們可以設計使拍照APP多一個撰寫郵件的功能,而這只需要連接到剛才提到的郵件APP中的撰寫郵件activity就能達成。
  最後要提的是,每個activity都是繼承(extend)於Activity類別的子類別(subclass),這點在實際撰寫程式時會注意到。有興趣的人可以進一步參閱Google Activities developer guide.
Services(用在通常會在背景運作且不會太耗電的程序)

  Services與Activities的差異在其背景工作/進行遠距程序的能力,且Service沒有UI。舉例來說,一個service可以讓你在播放音樂的同時,操作各種不同的APP;或是可以讓你在和某APP的activities互動時,繼續從網路獲取資料。Service可以由另一種組件,例如activity,啟動或是結合,使得這些組件互相關聯。
  同樣地,service是繼承於Service類別下的子類別,有興趣的人可以進一步參閱Google Services developer guide
Content providers(管理資料的部門)

  Content provider用來做資料管理,從字面上可以理解為一種提供資料、內容的部門。通常我們可以在Android系統中以檔案系統, SQLite資料庫, 網路, 或是其他你的APP能存取的常駐儲存位置(雲端、SD卡等)。利用content provider,其他APP可以查閱或甚至更改資料內容(如果該content provider提供權限的話)。舉例來說,有用來管理用戶的連絡資訊的content provider,當有任何APP需要存取或變更關於聯絡人的資訊時,就能夠透過適當的content provider(例如ContactsContract.Data)達到目的。因此當然也能設定為只能被特定的APP給存取,例如開發者專為其APP設計之資料庫,或是用來儲存筆記的Note Pad 範例程式。(Note Pad程式碼可以從Android Studio的Import sample取得,在選單列的File-->Import sample找尋即可)      
  利用content provider建立instance(屬於某類別的物件,關於物件導向介紹,請看這裡)時,此instance的類別也是隸屬於ContentProvider的子類別,且必須實作標準的應用程式介面(API, 軟體系統溝通的橋樑)來讓其他APP也能獲取此content provider的內容。更多訊息請參照這裡

Broadcast receivers(接收通知的捕手)


  Broadcast receiver是用來回應從系統其他部分傳來的通知(system-wide broadcast announcement)的組件,在手機裡,許多receivers都是來自系統,例如螢幕關閉通知、低電量通知、檔案已下載完成通知等等。在APP裡也可以啟動通知,舉例來說,在使用A程式時,你可能會收到來自B程式的broadcast,通知你某某檔案已經下載完成,可以使用。雖然broadcast receiver不會呈現UI,但可以建立狀態列通知(status bar notification, 就是螢幕上方那條)來告知使用者:有廣播事件(broadcast event)被觸發了。更普遍的情況是,broadcast receiver扮演著連接其他組件的"通道"的角色,且通常只用來執行小量的工作。例如根據被觸發的event來觸發一個service以完成某目標,但本身只是中介的角色。


  Broadcast receiver實作於BroadcastReceiver類別下,且每一個broadcast都是以Intent物件的形式傳遞。更多訊息請參照這裡

  Android系統的一個獨特部分是其可以讓任何一個APP啟動另一個APP的組件。舉例來說,如果你想要讓使用者拍張照,你可以找到另一個APP所開發的拍照activity,並利用它來為你的程式拍張照。神奇的是,你不需要合併或甚至連結該activity的程式碼,你只需要啟動該activity並拍張照。拍完之後,照片甚至可以傳回到你的APP裡頭供你使用。從使用者經驗的角度來說,這就好像相機內建在你的APP裡面一樣。

  當系統啟動一個Android組件時,同時也啟動了該APP的程序(如果該程序還沒執行)和實作了一個該組件類別之instance。舉例來說,如果你的APP開啟了拍照APP中的拍照activity,則該activity實際上是運作在拍照APP的程序之中。因此,有經驗的程式設計師會發現Android apps沒有一個單一的進入點(像是Arduino, Processing, C#等都會有的main()或setup()或loop()方法)。
  由於前面提到Android系統獨立運作各APP並設定存取權限的緣故,我們的APP無法直接活化另一APP裡的組件。但是這些APP上層的Android系統就可以。所以,如果想要做這件事,我們需要一個叫做Intent(坊間翻作意圖)的物件來乘載給系統的要求。如果系統認可,系統就會下海幫你完成這件事情。

活化組件(Activating Components)


  上面提到的四種組件中,有三種:Activities, services, and broadcast receivers能夠被Intent這種非同步(asynchronous message, 我的理解是因為其無法馬上要求回覆的特性,因此稱為非同步)的物件給活化。Intents會在運行時把獨立的組件們給連結起來(你可以把Intents想像為郵差或信使,他們會從一個組件傳遞要求到另一個組件)以傳遞資料或要求動作,因此也有參考書稱之為超連結元件。
  
  Intent的instance可以用Intent intent=new Intent();的方式產生,再由開發人員決定要賦予其甚麼樣的任務(此時再決定要活化甚麼組件,可以是開發人員自己編寫的activity);或是也可以直接定義intent本身的任務(直接指明要活化的組件,通常是內建的內容),如Intent intent=new Intent(動作,內容)的方式,其中Intent方法的參數分別是動作(例如瀏覽網站就用內建的ACTION_VIEW方法、撥號則是ACTION_DIAL...)及內容(例如瀏覽網站時就是Uri.parse("要瀏覽的網址")、撥號則是Uri.parse("tel:電話號碼"))。
  
  如上所述,對於activities和services而言,intent定義了要進行的動作(例如看某張圖片或傳某種資料),也可以指明將要進行操作的資料之統一資源標示符URI(Universal Resource Identifier,可以當作是資料的ID,如上面用到的Uri.parse()方法就是利用這個ID去解析所要的網頁、電話號碼)。舉例來說,一個intent也許會傳遞一個由某個activity發出的,要求show出一張照片或打開一個網頁的指令,這時候就會用到URI。在某些例子之中,我們可以讓一個由intent活化的activity再用intent的形式傳回結果(可能用bundle的形式打包資料),舉例來說,我們可以在activity中創造一個intent,來讓使用者挑選一個聯絡人,再把使用者挑選的結果用另一個intent傳回來給activity,這時這個intent也會包含了關於該聯絡人的URI。關於URI,這裡有更詳細的解說。
  
  對broadcast receiver來說,intent僅定義了需要被廣播的通知內容(例如低電量通知的intent中可能只有一個已知的字串"電源不足"。

  最後一個組件類型content provider,無法被intent活化。這類組件的活化條件是收到來自ContentResolver的要求時。Content resolver物件處理了所有跟content provider的資料交換,因此發出要求的組件不需要也無法直接觸及content provider,這種類似白手套的機制是為了系統的資訊安全,在計算機科學中被稱為抽象化(Abstraction),是一種把系統理想化進而提高可開發性的概念。在這裡的意義是我們可以只關心content resolver這個現成類別的方法如何被我們利用,不需去管在檯面下的資料如何被交換。
  Google提供了分別的方法以活化不同類別的組件:
  

想知道更多有關intent的資訊的人,可以參照Intents and Intent Filters (官方提供);更多關於活化特定組件的資訊也可以參照以下連結 : Activities, Services, BroadcastReceiver , Content Providers(官方提供)

  這篇上集就先到這裡,主要我是希望能詳實地呈現原汁原味,所以沒什麼刪減(最基本的東西是最難刪減的,因為最重要....)。有些地方可能翻得不太好,有些地方我引入自己學習Android半個月以來(無誤)的經驗,同時因為我把目標觀眾設定為"已經安裝好Android Studio或Eclipse,且已經看過一些參考書,實作過一點東西,但想知道更多更詳細的人",所以我不會教大家怎麼設置開發環境,請大家動動手指Google一下相信一定有成千上萬種教學XDD。

P.S.下集不知道甚麼時候會寫好XDD





2015年2月10日 星期二

(Android API Guides系列)Android 應用程式架構基礎(下)

  大家好,睽違了2天相信大家都很期待下集,那就讓我們繼續看完關於Google釋出的API解說中最基本但也最重要的部分:Application Fundamentals吧!
  
  在本集中我們將會看到定義程式內容和性質的Manifest files(清單檔)和存放各種App會使用的資源的Resource(資源檔)檔案。

Manifest files(清單檔)


  在Android系統啟動一個APP組件之前,它必須先到這個通常在專案中名為AndroidManifest.xml的檔案去檢查該組件是否存在之後才能啟動。所以我們必須在這個xml檔案(我們可以在Android Studio的專案中關於Manifest的資料夾中找到它)中宣告所有的成員。

  除了宣告組件存在之外,manifest檔還有以下主要功能:
  • 識別該APP需要的使用者權限,例如網際網路存取、讀取通訊錄等
  • 宣告最小相容的API等級(minimum API Level,也就是用來組成、編譯你的程式的API版本,越新通常會包含越多功能,1.0.2版預設為8)
  • 宣告會用到的軟硬體成員,例如相機、藍芽裝置、多點觸控螢幕。
  • 外部API函式庫(那些不包含在Android framework APIs裡面的,例如Google Maps library)

1.宣告組件


如上所述,宣告組件成員是manifest檔案主要任務,方法可以如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:icon="@drawable/app_icon.png" ... >
        <activity android:name="com.example.project.ExampleActivity"
                  android:label="@string/example_label" ... >
        </activity>
        ...
    </application>
</manifest>
<application>標籤裡,android:icon指向某個決定APP圖示的.png Resource檔。在<activity>標籤裡,android:name定義了開啟的activity的類別名稱(ExampleActivity)。android:label則定義了此activity的顯示名稱。

  Activities, services, content providers這些組件,如前述地,在我們的Java程式碼中寫完完整功能之後,我們還必須在manifest中宣告之後才能被系統"看到",否則無法運作。但是,Broadcast receivers是個例外,它可以像他們另外三個夥伴一樣在manifest檔中宣告,也可以動態地在Java程式碼裡空降(就像BroadcastReceiver一樣)並驕矜地呼叫registerReceiver()來完成系統註冊(讓系統知道它存在)這個trivial的動作。

想知道更多有關如何為你的APP組成一個manifest清單檔請看The AndroidManifest.xml File

2.宣告組件功能


如前所述,在談到活化組件時,我們提到可以用Intent來開啟三種組件(activities, services, and broadcast receivers)。方法上我們可以直接言明目標組件是誰,在Intent中直接指向它。然而Intent強大的地方就是在其隱匿性(Implicit),怎麼說呢?隱匿性只需要我們描述動作類型(或是也可以進一步告訴Intent這個信使我們想要操作的資料),這樣系統就能夠幫我們找到一個裝置中的組件(可以是別的APP的成員)來幫我們完成這個動作。如果有複數的組件能執行Intent所描述的動作,使用者也可以自行選擇要使用哪個組件。(編按:聽起來真的很強大啊XD)

  至於系統要怎麼識別出那些組件能達成Intent的任務呢?原來就是manifest裡面的宣告組件功能的區塊啊!當我們在APP的清單檔中宣告了一個activity,我們可以選擇性地把宣告"這個activity具有甚麼功能"的Intent filter給函括進去,這樣一來從其他APP來的Intent就可以取用我這個activity的功能了!<intent-filter>的用法如下:

<manifest ... >
    ...
    <application ... >
        <activity android:name="com.example.project.ComposeEmailActivity">
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <data android:type="*/*" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>
  注意到<action android:name="android.intent.action.SEND" />這行定義了要捕捉的動作名稱"SEND",下一行定義了檔案類型,再下一行則是Intent的類型(其實這邊我不懂"*/*",知道的人可以教我XD)。如此一來,如果從另一個APP傳出了含有ACTION_SEND動作(編寫並寄送郵件)的intent,系統可能會開啟我們寫的activity來讓使用者有另外一種選項編寫並寄送郵件。
想知道更多有關如何建立intent filter的人可以參考Intents and Intent Filters document。

3.宣告APP之系統需求


  簡單來說就是為了在上架到Google Play商店時能夠讓使用者快速知道你的APP與其裝置之相容性,我們必須把相關資訊放在manifest檔案裏頭。舉例來說,如果你的APP需要用到相機且必須要在Android 2.1(API Level 7)以上運行,你可以這樣做:
<manifest ... >
    <uses-feature android:name="android.hardware.camera.any"
                  android:required="true" />
    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
    ...</manifest>
這樣一來,沒有相機(沒有才怪)或是Android系統舊於2.1版的可憐孩兒們就不能從Google Play安裝你的APP了(哭哭)。不過為了吸收最大利益廣大市民福祉,你也可以android:required屬性改為false,這樣一來系統會在執行時檢查裝置是否有相機,你也可以用此方式disable掉一部分需要用到相機的功能,使APP能正常運作。

  關於裝置相容性,等不及的人可以看Device Compatibility document。


App Resources(APP資源檔)

一個完整的APP是由Java程式碼和我們會用到的資源(例如圖像、音訊、以及其他關於視覺設計的部分)組成。而這些資源都會被定義在XML格式的資源檔中(Android Studio專案中的res資料夾裡面),舉例來說,你可以在xml資源檔中定義你設計的activity中存在的動畫(animation)、選單(menu)、造型(style,color)、佈局(layout)。因為這些資源與程式碼被分開來管理,所以在更新以上部分的內容時,不需要再更改程式碼,這使得你的APP在適應不同裝置時(例如不同語言、不同螢幕大小的手機)能更快達到最佳化。

  至於要怎麼做到最佳化呢?我們需要一點前情提要:
SDK build tool(軟體開發建置工具)會為每一個APP專案用到的資源定義一個唯一的整數ID,以用來當作Java程式碼及其他xml資源檔參考(reference)此資源的識別碼(以人比喻的話,相似於每個人的身分證字號的意思)。舉例來說,當你的APP包含了一個名為logo.png的圖檔(假設你存在res/drawable/資料夾中),SDK tools就會幫你在R.java這個資源類別檔裡頭自動產生一個名為R.drawable.logo的ID,讓你可以在Java程式碼中用這個ID來取得該圖檔物件。(如果在xml檔中的話就要用@drawable/logo來取得。)

  回到最佳化的話題,由於資源檔與程式碼分開管理的特性(一個在xml檔中定義,一個在Java檔中定義),我們能達到適應不同裝置組態的目標。舉例來說,你在xml中定義一個UI string時,可以用任何語言,因此你可能會有很多個語系的同一個UI string在不同的資源檔中。當你需要為你的程式附加不同語言選項時,你就可以讓系統自動去找到合適的xml檔案。比如說,利用為資源檔資料夾加入語言修飾詞(language qualifier)的動作(例如 res/values-fr/xml資源檔案),再加上使用者裝置的語言設定(前例為法文,-fr),Android系統可以幫你決定適當的xml檔案,再生成你的UI。

  為不同的資源檔版本,Android支援許多不同的修飾詞。這些修飾詞可以是添加在資源目錄的短字串以決定適當的裝置組態,如上述的-fr可以讓系統知道這是定義法文版本的xml資源的資料夾。另一個例子,你可能需要產生不同的佈局檔以應付不同的手機螢幕方向/尺寸,舉例而言,當螢幕放直(portrait orientation)時,你可能想要一些按鈕佈局改為垂直方向;當螢幕橫放時(landscape orientation)時,按鈕要放水平。這些都能透過適當的修飾詞讓系統自動決定。
  想知道更多你可以加入程式的資源類型和不同的組態下的資源檔要如何產生可以參考這裡:Providing Resources
  關於Android Fundamentals的介紹就到這邊,謝謝各位觀看。希望沒有太難懂......其實我寫這些文章一大部分是要以後自己看的,我就是上一篇文章中提到的目標觀眾"有一點基礎,但是希望知道更多的人"。希望能和也在學習或老手複習的人一起變強,所以有任何的疑問或是建議也請大家多多提出~
前年暑假去東京玩,在橫濱拍的照片XD