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 的測試成本,將不下於開發成本。

沒有留言:

張貼留言