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

2016年3月25日 星期五

在 Android 中,有哪些 IPC 方法?

前不久在我的 Facebook 上,問了底下這個問題:

在 Android 中,當 process A 要向 process B 索取動態 parcelable 資料 (動態資料指,資料是經由計算所產生) 時,你會用甚麼方式來做?

1. 最常見的方法是 Service + AIDL (http://developer.android.com/guide/components/aidl.html)。

還有誰要貢獻其他方法?

最後找出將近十種方法,還有範例與文件,有興趣的自行點下面連結參考。

https://www.facebook.com/samlu128/posts/10204283071555842


繼續閱讀全文...

2014年5月12日 星期一

為什麼你的 app 要在每個 Android 版本上測試?

先看一下底下的程式片段。這樣的程式在某些手機上會當掉,你有看出來這問題出在那?

程式中的 registerReceiver(null, IntentFilter) 用的是一個很典型取得當前手機電量的方法。由於 ACTION_BATTERY_CHANGED 是一種 "sticky intent",因此我們可以透過傳 null 給 receiver 及適當的 IntentFilter,來取得該 intent 所攜帶的 bundle 資料。

乍看之下,這樣的程式好像沒有問題。不過我們先來看一下 Context.registerReceiver() 的文件說明。看到沒,文件中的 Note 說明了這個函式不能在 BroadcastReceiver 中被呼叫。

Note: this method cannot be called from a BroadcastReceiver component; that is, from a BroadcastReceiver that is declared in an application's manifest. It is okay, however, to call this method from another BroadcastReceiver that has itself been registered at run time with registerReceiver(BroadcastReceiver, IntentFilter), since the lifetime of such a registered BroadcastReceiver is tied to the object that registered it.

嚴格地說,應該是呼叫 registerReceiver() 的 ctx 不能是一個 BroadcastReceiver 或其繼承物件。程式當掉的原因看起來似乎是清楚了。

不過另個問題來了,多數工程師寫程式時,沒法記住所有的特別限制。而這樣的問題,在編譯階段無法被偵測到,只能靠動態測試。就以我來說,我寫程式多半在最新的 Android 版本 (4.4.2) 上開發、測試。上述的程式在我手機上,當時竟然是可以正確執行無誤。在 app 要釋出前,我還在 4.3.x, 4.2.x 的手機上測過,也都沒有問題。直到一位使用者的回報,我才發現這樣的程式在 4.1.x 的手機上執行,會有問題。

看起來正確的結果應該是,上述程式在4.1(含)以前的手機上執行,都會出問題。但是在4.2(含)之後的手機上,卻可以正確執行無誤。

為什會這樣?我們看看 frameworks\frameworks\base\core\java\android\app\ContextImpl.java 原始碼,就可以找到答案。

底下是 Android 4.1(含)以前的原始碼:

底下是 Android 4.2(含)之後的原始碼:

很不幸,你在文件中找不到這個 API 規格變動的相關說明。這個因 APIs 規格變動,所引起的問題,還不是唯一的例子。事實上,類似的問題,不管是 Android, iOS 或是其它作業系統,都會發生。所以當你開發好你的 app 時,千萬記得要在各個作業系統版本上都測試一遍。只不過要做到這樣,你得先建立自動化測試的機制與完善的測試案例。隨著 Android 作業系統的快速演進與增加,慢慢地你會發現 app 的測試成本,將不下於開發成本。


繼續閱讀全文...

2013年7月17日 星期三

APP定期與SERVER同步資料應該要注意的事

今天一位國外開發者遇到他的應用在定期與 server 同步資料時,因為 2x Battery 會將網路斷開,而造成他的程式始終無法抓到資料,來信問我是否有解法。其實就算使用者沒裝 2x Battery, 手機上的網路還是隨時會處於無法連線的狀態。因此,真要寫一個定期 polling (or post) 的 app。我建議還是要寫個 broadcast receiver 監聽android.net.conn.CONNECTIVITY_CHANGE intent。如果上次同步沒成功,就可在連線恢復時,繼續上次的同步工作。

有興趣知道細節的,可先閱讀底下資料:

原先這文章是發佈在我的 facebook, 有位網友加入討論,我覺得值得在這分享我們的討論給大家。底下就是我們的後續討論:


Julian Shen: 這Intent使用上要稍微小心, 網路有變化就會收到(像是3G<->Wifi),在網路不穩定的環境下就很容易頻繁收到, 在這份 document : http://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html 有特別提到"it's good practice to monitor this broadcast only when you've previously suspended updates or downloads in order to resume them."


盧育聖: Julian Shen 你說的沒錯。

Android's DownloadManager 就是靠 CONNECTIVITY_CHANGE 來恢復網路下載的工作。想深入研究的開發者,可參考底下 Android 原始碼的實作。

  • packages\providers\DownloadProvider\src\com\android\providers\downloads\DownloadReceiver.java
  • packages\providers\DownloadProvider\src\com\android\providers\downloads\DownloadService.java

盧育聖: 另外補充 Julian Shen 提到的會頻繁收到 CONNECTIVITY_CHANGE intent 問題的解法。實務上,這個 receiver 預設是處於 disabled (加 android:enabled="false") 狀態的,只有當你上次同步失敗時,才 enable 這個 receiver,同步好後,記得要 disable 該 receiver。

如何動態 enable/disable receiver,請見:

http://developer.android.com/training/monitoring-device-state/manifest-receivers.html


看到這,你是否有不同的想法或解決方法?也歡迎在此一起討論。


繼續閱讀全文...

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() 就是負責設定下一個鬧鈴的主要函式。

從程式中,我們了解到他先透過 AlarmManager 的 set() 函式,設定鬧鈴時間,以及鬧鈴時間到達時,所要喚起的程式。由於是透過 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月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_CHANGED 及 ACTION_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() 等函式還沒提到,我想這部份就留給你自己去發掘。


繼續閱讀全文...