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

2010年6月22日 星期二

Android 原始碼徹底研究系列 - 鬧鐘程式, ContentProvider (5)

Android Source Code Internals - Alarm Clock, ContentProvider (5)

這一系列的 鬧鐘應用原始碼的剖析研究,這一篇應該是壓軸了。在這鬧鐘應用原始碼系列研究的最後,要和大家介紹的是,在鬧鐘應用中如何建立與使用 ContentProvider 這個元件。

剛學 Android 的人,應該都看過這個關於 Android 4 大元件 的相關文件。從這個文件中,我們知道這 4 大元件,分別是 Activities, Services, Broadcast receivers 和 Content providers。在程式中,只要你有用到這 4 大元件,都必須要在 AndroidManifest.xml 中宣告。從鬧鐘應用的 AndroidManifest.xml 內容來看,除了 Service 之外,他涵蓋了其他三大 Android 元件,這也是為何我先挑他出來介紹的主要原因之一。

因此,依照慣例,我們先打開 AlarmClock 的 AndroidManifest.xml 檔案,你會看到 #13 有宣告 AlarmProvider 這個 content provider。

如果你對甚麼是 ContentProvider 還不了解的話,建議你一定要先看看下列文章:

為了資料的隱密與保護性,在 Android 中,各個應用程式所儲存的資料,都在自己的保護區內。就算你知道他的檔案名稱與路徑,你也會因為權限的關係,無法存取別人的資料。這樣的作法,資料保護的目的性是達到了,可是卻犧牲了合作性。怎麼說?例如,在手機上,所有聯絡人的資料,都是由聯絡人這個應用所創建與管理的。如果你想寫個寄信程式,並且想讓使用者從現有的聯絡人中,挑選 email 地址。好了,這問題來了,這聯絡人的資料是由另一個應用所掌管的,你的應用想要存取別的應用所掌管的資料,看來是行不通的。在 Android 上,這樣的功能,應該怎麼做?

為了解決這資料互通的需求,Android 提出了 ContentProvider。簡單地說,ContentProvider 的出現,就是要讓某個應用所儲存的資料,可以被其他應用所讀取或寫入。而且 ContentProvider 本身還提供了權限管理的機制,讓只有獲得授權的第三方應用,可以透過他來存取資料。

ContentProvider 並沒有規定後台的資料儲存媒介。你可以用 SQL 資料庫,當然也可以用檔案,或其他你方便使用的方式。因此,規定一個統一存取的介面是必要的。ContentProvider 為這統一的存取介面,定義了以 URI 為基礎的 REST 存取方式。而所有要透過 ContentProvider 存取資料的應用,都必須要先透過 ContentResolver,幫你找到適合的 ContentProvider 才行。

最上面那個圖,就是鬧鐘應用中,和 ContentProvider 相關的方塊圖。鬧鐘應用將使用者所設定的所有鬧鈴,都儲存在一個 SQLite 資料庫 (alarms.db) 中。為了讓自己與其他應用也可存取這鬧鈴設定,AlarmProvider.java 是 ContentProvider 的實作,並提供底下兩種 REST 存取 APIs。

例如,處理這兩個 REST APIs 的查詢關鍵地方就在:

要實作出一個 ContentProvider,除了要自行定義可供外部使用的 REST APIs 外,你還要定義底下函式:

關於這些函式的實作細節,就請你就自行參閱 AlarmProvider.java

好了,你已經看到鬧鐘應用如何實作 ContentProvider,以及他所提供出來的 APIs。接下來,我們來看看要如何透過 ContentProvider,來存取這些鬧鈴設定。

在鬧鐘應用中,所有和 ContentProvider 相關的存取,都在 Alarms.java 中。

我們來看殺掉鬧鈴設定的功能是如何實現的:

看到這,我們可以了解,即使要在自己的應用中殺掉 ID 為 5 的鬧鈴設定,你只要用底下方式呼叫即可:

在鬧鐘應用中,看似提供 ContentProvdier 這樣的介面是多餘的,畢竟只有他自己一個人在用。不過,在 Android 中,強調的是大家分工合作,透過 Intent 及 ContentProvdier 的機制,讓各個應用程式之間,能更完美無縫地的整合在一起。因此在知道鬧鐘應用的 ContentProvdier 介面後,如果日後你想在你的程式中,加上增減鬧鈴的功能,你就不用自己做了,完全可以透過現有的鬧鐘應用來完成,這樣的模式對使用者也好。反過來想,如果你的應用也能提供 Intent 與 ContentProvider 的話,這樣是不是也能讓別人和你更容易地整合在一起呢?


繼續閱讀全文...

2009年9月21日 星期一

Android 原始碼徹底研究系列 - 鬧鐘程式, Alarm Reminder (4)

Android Source Code Internals - Alarm Clock, Alarm Reminder (4)

在了解鬧鐘程式各個功能是如何實現後,這次我們要來追蹤『提醒鬧鈴時間到達』這個功能是如何實現的。

要追蹤應用程式的原始碼,先觀察 AndroidManifest.xml 通常是最好的方式。

首先,我們先打開 AlarmClock 的 AndroidManifest.xml 檔案。注意看 #52~#64 這兩個 broadcast receivers。

AlarmReceiver 應該就是當鬧鈴時間到達時,要執行的程式。而 AlarmInitReceiver 則是當手機重開機,時間被重新設定,或是時區改變時,所要執行的程式。

讓我們先打開 AlarmInitReceiver.java 看看。#31~#42 這個 onReceive() 內的程式就是關鍵。

從其呼叫的函式名稱,不難理解出這段程式的主要工作,就是重新設定下一個鬧鈴。在這裡的 Intent.ACTION_BOOT_COMPLETED ,就是手機重開機時,所發出的系統 Intent。

接著,我們打開 Alarms.java 看看 setNextAlert() 這個函式到底在做些甚麼事。

看起來,enableAlert() 就是負責設定下一個鬧鈴的主要函式。

從程式中,我們了解到他先透過 AlarmManagerset() 函式,設定鬧鈴時間,以及鬧鈴時間到達時,所要喚起的程式。由於是透過 PendingIntent.getBroadcast() 取得 PendingIntent 的 instance,因此我們知道這個被喚起的程式應該是個 broadcast receiver。

那到底是那個 broadcast receiver 會被喚起呢?從程式看來,應該就是喚起會接收 ALARM_ALERT_ACTION action 的 broadcast receiver。在此的 ALARM_ALERT_ACTION 就是 "com.android.alarmclock.ALARM_ALERT" 字串。

讓我們再翻開 AndroidManifest.xml 瞧瞧,會接收 com.android.alarmclock.ALARM_ALERT 的 broadcast receiver,不就是 AlarmReceiver?!

打開 AlarmReceiver.java。仔細研讀一下,不難理解這段程式的主要工作就是:

  1. 先喚醒可能正在沉睡中的 CPU 與螢幕
  2. 暫時解開鍵盤或螢幕鎖
  3. 執行 AlarmAlert 這個 Activity,也就是你最終看到的鬧鈴提醒對話框。

最後,你有沒有發現,一般我們設定的 notification icon 都是出現在 status bar 的左手邊。而 AlarmClock 的鬧鈴 icon 卻是出現在右手邊的系統 icon 區?想要知道他是如何做到的?再回頭看 Alarms.java 中的 enableAlert(),其最後會呼叫 setStatusBarIcon() ,我想答案應該就在其中。

打開 setStatusBarIcon(),其內容如下:

竟然是透過送出 ACTION_ALARM_CHANGED Intent,來設定 notification icon 的。看來,這是個未公開的用法。了解這些特別的用法,這不就是你研讀系統原始碼的好處之一嗎?


繼續閱讀全文...

2009年8月24日 星期一

Android 原始碼徹底研究系列 - 鬧鐘程式, Gallery & Adapter (3)

Android Source Code Internals - Alarm Clock, Gallery & Adapter (3)

在 AlarmClock 這個程式中,點一下主畫面上的時鐘,他會在下面列出一些鐘面圖樣,讓使用者更換時鐘的鐘面。上圖,就是這個選擇鐘面功能的執行畫面。水平拖移底下的鐘面清單,你可以看到上面的時鐘也會跟著換成新的鐘面。

這次我們就花點時間來研究看看,這樣的功能是如何實現出來的。

要追蹤這樣的功能是如何實現的,還是得先從主畫面的 layout 設計檔著手。打開 res/layout/alarm_clock.xml 檔案,你可以發現主畫面的時鐘應該是放在 clock_layout 這個 LinearLayout 中。

因此接著,我們打開主畫面的 Activity 檔 AlarmClock.java ,尋找 clock_layout 這個關鍵字。果然在 #234~#241 中,發現如下的程式片段:

原來,這個選擇鐘面的功能,是由 ClockPicker 這個 Activity 所負責實現的。

接著,在 ClockPicker 的 onCreate() 函式中,我們可以知道這個 Activity 所使用的畫面設計檔就是 res/layout/clockpicker.xml

同樣地,只要觀察這個 clockpicker.xml layout 設計檔,你可以發現原來選擇鐘面畫面下的鐘面清單,就是由 Gallery 這個元件所實現出來的。

原來,Galley 不僅僅用來顯示圖片,他也可以用來當成類似 horizontal ListView ,當成一種選單元件的功能。

先查看一下 Gallery 的繼承樹,原來他也是從 AdapterView 繼承下來。因此要對 Gallery 客製化,方法應該和 ListView 相同。

在 Android 中,所有的 AdapterView 子類,都允許你透過繼承 BaseAdpater 的方式,塞一個自己客製化過的 Adapter 給 AdapterView。

要如何寫一個自己的 Adapter?有興趣繼續研究的你,建議你打開這個不到 120 行的程式,ClockPicker.java,好好讀一下。


繼續閱讀全文...

2009年8月3日 星期一

Android 原始碼徹底研究系列 - 鬧鐘程式, App Widgets (2)

Android Source Code Internals - Alarm Clock, App Widgets (2)

從 Android v1.5 開始,終於開放 APIs,讓第三方開發者可以寫些 widgets,讓使用者可以放在手機的桌面上。一般手機就有內建幾個小 widgets,其中的 "Analog Clock" widget,就是由 Alarm Clock 這隻鬧鐘程式所提供的。

這次我們就來研究一下,這個 Analog Clock widget 是如何寫出來的。

不過,如果你對如何寫 App Widgets 一點概念都沒有的話,底下有幾個很好的參考資料,一定要先讀過:

要寫個 App Widget,首先你要先用 XML 寫個 AppWidgetProviderInfo,而且這個檔案建議放在 res/xml 中。在鬧鐘程式中,你可以找到 res/xml/analog_appwidget.xml 這個檔案。它的內容如下:

由 minWidth 及 minHeight 這兩個屬性來看,就可以了解,這個 App Widget 要佔桌面 2x2 cells 大小的空間。

updatePeriodMillis 的值為 0?那這個時鐘是如何自己動的呢?這我在 Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1) 一文中,就已經解釋過 Analog Clock 是如何自我更新的。還不清楚的,先讀一下這篇。

initialLayout 這個屬性,從名字看起來,就知道它是用來描述這個 App Widget 的畫面設計。由其值我們可以知道,這個 App Widget 的畫面設計,是在 res/layout/analog_appwidget.xml 中。它的內容如下:

果然不出我們所料,這個 App Widget 的畫面設計,就只是一個簡單的 AnalogClock 。時鐘與時,分針圖檔就是分別透過 dial, hand_hour, hand_minute 這三個屬性來指定。

要注意的是,目前在 App Widget 的畫面設計檔中,你只能使用 FrameLayout, LinearLayout, RelativeLayout, AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar, TextView 等元件,其他元件都不能用。

寫好 AppWidgetProviderInfo XML 檔與 App Widget 畫面設計檔,別忘了,還要在 AndroidManifest.xml 中,宣告你的 App Widget Provider。

在這裡,他指明了這個 App Widget Provider 是要由 AnalogAppWidgetProvider 這個類別來處理。這個類別的原始碼就是 AnalogAppWidgetProvider.java

在這個類別中,主要的工作就是建立 App Widget,並透過 AppWidgetManager 發佈到桌面上。

鬧鐘程式所提供的 "Analog Clock" widget,其實功能很陽春,也沒有用到所有 App Widgets 提供的所有功能。不過,對於想要了解如何寫個 App Widget 的你,應該是有很大的幫助。如果你想要找個較為完整的 App Widget 範例,我建議你看一下 API Demos 這個範例程式。


繼續閱讀全文...

2009年7月13日 星期一

Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1)

Android Source Code Internals - Alarm Clock, AnalogClock (1)

鬧鐘的原始程式碼在 這裡

PS: 請不要向我要原始碼,所有Android原始碼都可已自行下載,詳情請見 https://source.android.com/source/building.html

一執行這個鬧鐘程式,畫面上第一眼看到的,就是一個大大的時鐘。因此我們今天,就先從這個時鐘下手。讓我們一探究竟,看他是如何實現的。

要了解這個時鐘,是用什麼元件做出來的。有兩個方法,第一個就是用 SDK 內自帶的 Hierarchy Viewer 工具;另一個,就是直接觀察 res/layout 中的畫面設計檔。這次,我們直接用第二個方法。

翻開 AlarmClock/res/layout 目錄的內容,你會發現有好幾個 clock_xxx.xml 的檔案。打開其中的 clock_droid2.xml 檔。內容如下所示:

嗯,看起來,這個時鐘就是用 android.widget.AnalogClock 這個元件直接實現的。只要透過 android:dial, android:hand_hour, android:hand_minute 這三個 XML 屬性,指定鐘面,時針與分針的圖檔,一個完整的時鐘,就會顯現出來。最上方的圖片,就是這個時鐘顯示的樣子。

都不需要任何額外的設定,這個時鐘元件就會自己依照目前的時間,將時、分針顯示在正確的位置,那他是如何定時更新時、分針位置?另外,注意到了嗎,時、分針的圖檔,只有一份。從這就猜到,這個時鐘還會自動將時、分針圖旋轉至正確的角度,貼在螢幕上。讓我們來看看,這個 AnalogClock 元件是如何實現這些功能的。

打開 AnalogClock 元件原始碼,扣掉最前面的註解不算,整個原始碼也才不到 230 行。這麼少的程式碼,卻能將這個時鐘功能做得這麼完整。這也是我常最推薦,一定要看的元件原始碼之ㄧ。

這次,我們要看的重點有兩個。

  1. 在元件程式中,要如何隨時都知道時間的改變?
  2. 在 Android 中,要如何將圖片做任意角度的旋轉,並貼在螢幕上?

在元件程式中,要如何隨時都知道時間的改變?

原來在 onAttachedToWindow() 中,AnalogClock 透過 registerReceiver() 註冊了三個由系統發出的 Actions,ACTION_TIME_TICK, ACTION_TIME_CHANGEDACTION_TIMEZONE_CHANGED

在 Android 中,系統會透過 Intent 傳送某些系統通知訊息,這些通知訊息又稱為 Action。在 Intent 類別的說明中,你可以找到所有的 Action 定義。

在你的程式中,想要收到這些系統通知訊息,有兩個方法。第一個方法就是在 AndroidManifest.xml 中定義 receiver 的類別,這個方法在日後的介紹中會提到。第二種方法就是利用 registerReceiver(),向系統註冊接收的類別。由於 AnaloClock 是以元件的方式存在,如果每次使用這個元件,都還要在 AndroidManifest.xml 中定義,那就太麻煩了。因此, AnaloClock 採用的是第二種方法。

其中,ACTION_TIME_TICK,就是由系統在每分整,傳送出來的 Action。ACTION_TIME_CHANGED 則是當使用者更改系統時間時,所送出的 Action。當你的手機跨越時區,系統則會送出 ACTION_TIMEZONE_CHANGED 給有註冊的程式。

一但這些 Actions 發生時,他會呼叫在程式最後 new 出來的 BroadcastReceiver 物件, mIntentReceiver,中的 onReceive() 回呼函式。

首先,在 onReceive() 中,先檢查如果是時區有改變的話,當然你要重新設定 mCalendar 變數。

onTimeChanged() 函式,只是重新抓出最新的時、分資料。

在 onReceive() 的最後,再呼叫 View.invalidate()。這個動作,會觸發 View.onDraw() 函式的呼叫,並在螢幕上畫上最新的時,分針。

在 Android 中,要如何將圖片做任意角度的旋轉,並貼在螢幕上?

Android 的 2D 繪圖功能,其實是很強的,尤其是 matrix 這部份。透過 matrix,你就可以輕易地,控制整個繪圖座標體系的位移、旋轉、傾斜,放大等功能。舉個例子來說,如果你要將圖片旋轉 30 度後,再貼在螢幕上。傳統的作法,都是直接先將圖片的內容,逐點旋轉 30 度。但是在 Android 平台上,你不用這麼麻煩。你只要將整個座標系統轉個 30 度,之後不管你是畫線,還是貼圖,最後呈現在螢幕上的都是轉 30 度的效果。

想要了解 AnalogClock 是如何選轉時、分針圖片,那看 onDraw() 這個函式就對了。我將 onDraw() 中,和這部份選轉時、分針圖片相關的,列在下面。其中的 canvas.rotate() 是關鍵,這個函式就是在將座標系統轉個角度。看完這個例子,有沒有發覺要在 Android 上旋轉、放大或縮小圖片,其實都是只要兩三行程式,就可搞定的事。

AnalogClock 就剩 constructors,與其他 onMeasure(), onSizeChanged(), onDetachedFromWindow() 等函式還沒提到,我想這部份就留給你自己去發掘。


繼續閱讀全文...