2014/10/30

GCM推播訊息(notification)延遲問題

最近在開發Android APP,使用到GCM(Google Cloud Message),發現一個問題:
我們的server已經送出notification,可是Android phone/table有時很快(兩三秒)就收到推播訊息,有時則等超過10分鐘才收到。

雖然Google宣稱notification不保證即時送達,也不保證可以送達,不過我們還是希望在免費的服務下,能夠儘可能的即時送達
對有些應用而言,超過5分鐘就真的很久了

工程師個性使然,總是想找出是我們的server+APP有問題,還是其他因素造成。因為有時是即時就能收到,照理說應該不是server或APP的問題
找了andQlimax的po文,覺得他的測試與推論是合理的,也符合我所觀察到的狀況

手機/平板與GCM server間是透過TCP connection來進行通訊。當手機/平板連上網路時,Android就與GCM server建立連線。為了維持連線,手機/平板會定期送出heartbeat訊號到GCM server。andQlimax觀察到如果是使用3G/4G連線,則每28分鐘會送一次heartbeat;若是用WiFi則是15分鐘。(為什麼間距那麼大?這除了要考慮手機平板的耗電外,還有GCM server的負擔等等)

andQlimax認為一般電信服務商(3G/4G)或WiFi router(無線分享器...),就資源分配與使用效率等考量,通常不會無限維持這麼多的TCP connection(因為可以建立的connection數是有限的),因此這些設備(3G/4G:基地台; WiFi:無線分享器/HotSpot)會定期(例如每5分鐘)檢視那些connection是idle的(沒有傳送資料),就將這個connection關掉。

因為Android的heartbeat間距很長,所以手機平板與GCM server間的TCP connection會被關掉,因此當有訊息時,GCM server便無法透過原來建立的connection將訊息送到手機平板,必須等到手機端時間到要發heartbeat時,才會重新建立連線。
所以當connection還沒有被關掉,或是heartbeat週期很快又到了,手機平板就能很快的接收到推播訊息;若是connection被關了,那就得等到下一次送heartbeat時才能收到notification了。

andQlimax在很多論壇與管道都呼籲Google/Android應該修改heartbeat的傳送週期。不過看來到目前Google都尚未採納。

如果,真的有mission critical的應用,也許應考慮是不是用long-polling request等機制來作,而不依賴GCM。

2014/09/17

書摘:托爾斯泰藝術論


托爾斯泰藝術論(What is Art?)

促使人類進步的兩大重要因素:語言文字、藝術。

  • 語言文字;傳達人們的思想與經驗
  • 藝術:傳達感覺與情感

藝術是一種人類的活動,藉此將自己的感受有意識地傳達給他人

藝術感染力的強度取決於三條件:

  • 所傳達之情感的特別度
  • 所傳達之情感的清晰度
  • 藝術家的真誠度


2014/09/15

Android APP無法取得GCM notification token

維護一支具有GCM notification功能的Android APP,因應客戶customization需要,必須更改package name以及修改manifest裡的root package定義,這會影響generated出來的R.java的package定義,因此,須再針對受影響的各程式更改import路徑

這樣的改法應該是最簡單的,比用refactor功能變更各package的name快多了 (之前也遇到一樣的需求,這樣改就OK了,也不會大幅改變svn裡的架構) 

不過卻遇到了一個問題,當device送資料到server時,GCM register ID(token)竟是空的! trace一下程式,我有一個GCMIntentService class是繼承GCMBaseIntentService,原來override上層的onRegistered是會被呼叫的,現在程式一執行,這個callback function竟然一直沒有被叫用,難怪token會是空的。 可是,這又是那裡出錯了?改的東西好像也沒幾個,而且那些應該都不會影響到這部份的功能呀。

找了許多資料,也做了一些嘗試,找到了一個因素,就是這個GCMIntentService必須要在menifest所定義的root package,例如說,menifest定義的package是com.abc.app,則這個service也要在com.abc.app,不可以在com.abc.app.service等其他的package中,即使manifest對service的宣告是指向com.abc.app.service也不行! 所以,只好再用refactor功能,把這個GCMIntentService移到它該在的package中,這樣執行起來就OK了!

2014/08/20

Android開發,appcompat_v7需要進版本管控嗎?

用Eclipse或Android Studio開發Android APP的朋友會發現,在ADT 22.6以後,當建立新的專案時,都會多一個appcompat_v7(或appcompat_v7_xx)的專案。
這專案是什麼?有什麼用途?網路上已有不少文章,大家找一下就可以找到一堆了 (參考)

那當專案要進版本管控(SVN, CVS, etc.)時,這個自動生成的support library project也需要跟著進去嗎?
找了一些文章,似乎沒有找到有較明確的說法。
當然,一起納管一定沒問題,可是納管這個自動生成的project似乎很沒道理。

換個角度來思考,如果我們在沒有這個project的workspace中,可以讓工具自動再生成這個project,讓我們的porject可以正常運作,那這個appcompat_v7就可以不用放進版本管控了。

做一個實驗,模擬一下當從SVN or CVS check-out一個專案時,是否能把整個開發環境建立回來。
開一個新的workspace,把一個之前做好的project給import進來,當然如預期的,會有一大堆的錯誤。錯誤當然都是跟support library有關。

接下來就要把support library project建立起來。相信大家的環境一定會有Android SDK。接著我們就要從下面的目錄把support library給import進來
<android_sdk>\extras\android\support\v7\appcompat  (註:<android_sdk>是指你SDK的安裝目錄)
不過還是一堆error


看一下原專案的properties,其library的名稱是appcompat_v7,但我們import進來的名稱是android-support-v7-appcompat,兩者名稱不同,必須修改一下讓兩者一致。那要修改那一個?我比較偏好改support library project的名稱。因為改properties裡的library名稱,再commit回版本管控時,可能會影響到別的同仁。

在project上right-click,用refactoring的功能來改project name為appcompat_v7 (如果在上一步驟所看到的project name不是這一個,以properties裡的設定為主)

修改完後,Eclipse就開始重新build workspace,這時errors就消失了

試跑了一下,原project的功能都正常。因此整個開發環境都回來了。
透過這個實驗可以得知,我們可以不用把support library project放進版本管控系統中,之後也能把開發環境重建起來。

補註:想瞭解Android support library有那些及分別有那些功能,<android_sdk>\extras\android\support\README.txt這份文件是個好起點

2014/08/15

在Android中周期性更新ListView的內容

在一個Activity裡有一個ListView,需要每10秒鐘更新ListView的資料
用寫Java的傳統想法,寫出了這樣的程式
其中ListItemProducer.getInstance().getList()會傳回ArrayList<string>

ArrayAdapter adapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
  ...
  list = (ListView) findViewById(R.id.listView1);
adapter = new ArrayAdapter(ListActivity.this, android.R.layout.simple_list_item_1, ListItemProducer.getInstance().getList());
list.setAdapter(adapter);

timer = new Timer();
timer.schedule(new MyTask(), 5000, 10000);
}

class MyTask extends TimerTask {

@Override
public void run() {
adapter.notifyDataSetChanged();
}
}

一執行,當掉了,怎麼可能!?
這樣的Java程式我寫了無數次了,不應有錯呀
 靜下心仔細看了一下,原來出現"Only the original thread that created a view hierarchy can touch its views."的exception
Android只允許原始的thread去變動views,上面的MyTask是在另一個thread跑,不被允許去碰views
找找資料,Handler倒是個解決問題的好方向
於是加了這段

private Handler updateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
adapter.notifyDataSetChanged();
}
};

然後把 MyTask改成

class MyTask extends TimerTask {
@Override
public void run() {
try {
updateHandler.sendEmptyMessage(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}

讓MyTask在每次時間到時,透過handler來送message回原來的thread,讓原來的thread去做update views的工作