ラベル GCM(C2DM) の投稿を表示しています。 すべての投稿を表示
ラベル GCM(C2DM) の投稿を表示しています。 すべての投稿を表示

2013年2月4日月曜日

minSdkVersion16以上ではGCMBroadcastReceiverのintent-filterにcategory指定が要らない

4.1以上の端末と、それより下のAPIレベルの端末で、GCMサーバーからメッセージを受信した際のインテントの内容が異なることに気がついた。


4.1(APIレベル16)以上
02-04 13:25:37.552: V/GCMIntentService.onMessage:70(11555): Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x10 pkg=my.app.package cmp=my.app.package/.GCMIntentService (has extras) }


4.0.3(APIレベル15)

02-04 13:46:33.894: V/GCMIntentService.onMessage:70(380): Intent { act=com.google.android.c2dm.intent.RECEIVE cat=[my.app.package] cmp=my.app.package/.GCMIntentService (has extras) }


16以上だと、インテント内にカテゴリー指定がなく、パッケージに変わっている。

GCMのドキュメントにも、minSdkVersionを16以上に指定した場合は、マニフェストに記述するcom.google.android.gcm.GCMBroadcastReceiverのintent-filterで、categoryのname値にパッケージ名を指定する必要がないとある。

Notice that android:name in the category tag must be replaced by your application's package name (and the category tag is not required for applications targeted to minSdkVersion 16 and higher).


メッセージが送られる端末に応じて、インテントにカテゴリーを含めるのか、パッケージ名を含めるのか、振り分けているようだ。


minSDKVersionを16以上にすると、さらに、permission.C2D_MESSAGEの要求も要らなくなるし、マニフェストがスッキリするね。

2012年10月27日土曜日

GCMのIntentServiceでAsyncTaskを実行したらsending message to a Handler on a dead threadの警告が出て悩まされる

GCMサーバーからメッセージが送られてきた時の処理をGCMIntentServiceのonMessage()へ記述している。
my.app.GCMIntentService
  |- com.google.android.gcm.GCMBaseIntentService
      |- android.app.IntentService

onMessage()内でAsyncTaskクラスを作成してexecute()したところ、以下のようなWarningが出た。
10-26 15:24:14.025: W/MessageQueue(482): Handler{44f63ea8} sending message to a Handler on a dead thread
10-26 15:24:14.025: W/MessageQueue(482): java.lang.RuntimeException: Handler{44f63ea8} sending message to a Handler on a dead thread
10-26 15:24:14.025: W/MessageQueue(482):  at android.os.MessageQueue.enqueueMessage(MessageQueue.java:179)
10-26 15:24:14.025: W/MessageQueue(482):  at android.os.Handler.sendMessageAtTime(Handler.java:457)
10-26 15:24:14.025: W/MessageQueue(482):  at android.os.Handler.sendMessageDelayed(Handler.java:430)
10-26 15:24:14.025: W/MessageQueue(482):  at android.os.Handler.sendMessage(Handler.java:367)
10-26 15:24:14.025: W/MessageQueue(482):  at android.os.Message.sendToTarget(Message.java:348)
10-26 15:24:14.025: W/MessageQueue(482):  at android.os.AsyncTask$3.done(AsyncTask.java:214)
10-26 15:24:14.025: W/MessageQueue(482):  at java.util.concurrent.FutureTask$Sync.innerSet(FutureTask.java:252)
10-26 15:24:14.025: W/MessageQueue(482):  at java.util.concurrent.FutureTask.set(FutureTask.java:112)
10-26 15:24:14.025: W/MessageQueue(482):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:310)
10-26 15:24:14.025: W/MessageQueue(482):  at java.util.concurrent.FutureTask.run(FutureTask.java:137)
10-26 15:24:14.025: W/MessageQueue(482):  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
10-26 15:24:14.025: W/MessageQueue(482):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
10-26 15:24:14.025: W/MessageQueue(482):  at java.lang.Thread.run(Thread.java:1096)

コードのスニペット。
@Override
 protected void onMessage(final Context context, Intent intent) {
  Log.i(TAG, "Received message.");
  // IMAGE_IDを取得
  String imgId = intent.getStringExtra(IMAGE_ID);
  
  // Async version
   GetImgTaskTest getImgTaskTest = new
   GetImgTaskTest(context.getApplicationContext());
   getImgTaskTest.execute(imgId, null, null);
 }


StackOverFlowで、このWarningの回避策が触れられていた。

Issue 20915 - android - AsyncTask can get initialized with wrong Looper - Android - An Open Handset Alliance Project - Google Project Hosting

そこでは、onCreate()内でAsyncTaskクラスをロードしておくやり方が紹介されていた。
public class YourApplication extends Application {

    @Override
    public void onCreate() {

        // workaround for http://code.google.com/p/android/issues/detail?id=20915
        try {
            Class.forName("android.os.AsyncTask");
        } catch (ClassNotFoundException e) {
        }

        super.onCreate();
    }
}


これはAsyncTaskクラスのバグだという言及も。

android - onPostExecute not being called in AsyncTask (Handler runtime exception) - Stack Overflow

This is due to a bug in AsyncTask in the Android framework. AsyncTask.java has the following code:

private static final InternalHandler sHandler = new InternalHandler();

It expects this to be initialized on the main thread, but that is not guaranteed since it will be initialized on whichever thread happens to cause the class to run its static initializers. I reproduced this issue where the Handler references a worker thread.


しかし、このClass.forName()を使う方法は、あまりエレガントに思えなかったので、AsyncTaskのスレッドルールについて調べてみた。

AsyncTask | Android Developers "Threading rules" より
・The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.
・execute(Params...) must be invoked on the UI thread.

AsyncTaskのAPIドキュメントにも、UI threadでクラスをロードせよ、execute()はUI threadで実行せよと、しっかり書いてある。


ナゼ?


まず基本的なことから。


・AndroidのUIは基本的にsingle threadモデルであることを理解すべし

AndroidのUIは基本的にsingle threadモデル。全てのアプリは1つのプロセスとスレッドで実行される。そのスレッドのことを別名"main" threadとよんでいる。

UI threadの中で、ネットワーク通信やデータベース検索など、時間がかかる処理を実行した場合、レスポンスが悪くなったり、アプリが止まっているように見えたりしてしまう。よって、以下の2点だけは守ろう。

Processes and Threads | Android Developers "Threads"より
Thus, there are simply two rules to Android's single thread model:

1. Do not block the UI thread
2. Do not access the Android UI toolkit from outside the UI thread


・時間がかかる処理はworker threadで行い、main threadから分けるべし

即座に応答しなくてよい操作だったら、それらは別のスレッド(バックグラウンドやworker threads)で処理できないか確認しよう。

Processes and Threads | Android Developers "Worker threads"より
If you have operations to perform that are not instantaneous, you should make sure to do them in separate threads ("background" or "worker" threads).

で、worker threadの結果は、以下の様なメソッドやクラスを使って反映させよう。



分かった!Warningの理由

そう、つまり、AsyncTaskは、worker threadがUI threadとコミュニケートしながら処理を進めるという操作を、簡単に書けるように作られたクラスなのだ。だから、execute()はUI thread内で実行することを想定している。

AsyncTaskのClass Overviewにも、真っ先に書いてある。

「AsyncTaskはUI threadを適切に、そして簡単に使えるようにしたもの。このクラスを使うと、ThreadやHandlerを使って自分で色々せずに、処理をバックグラウンドで行い、その結果をUI threadへ表示させることができる。」
AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.


ここまで整理すると、Warningが出ていた理由も理解できる。

UI threadで実行するルールになっているAsyncTaskを、IntentService内で、つまりworker thread内でexecute()したていたからだ。

IntentSreviceではAsyncTaskは使えない。

非同期処理と思うとAsyncTaskと直ぐに思ってしまうけれど、常に使える手ではないということだろう。


じゃ、どうするか。


そもそもやりたかった処理は、メッセージを受信したら画像をダウンロードするというもの。その際Activityは起動しない、Notificationのみ表示する。onMessage()はその処理をキックするだけ。

ムム〜。やっぱりThraed回すのが手堅そう。となると、java.uti.concurrent.Executor あたりにヒントがありそう。

2012年8月10日金曜日

GCMの実装でおさえておきたいポイント

前回の記事「Google Cloud Messaging(GCM)とC2DMの主な違いを挙げてみた」を書いていて、覚えておきたいなと思ったポイントがいくつかあったのでメモメモ。


同一メッセージには同一Collapse Keyを指定する

デバイスがオフラインからオンラインへ復帰した時、GCM Serverはストレージされたメッセージをデバイスへ再送する。

たまたま通信状態が悪かったりして、メッセージが届かないと思ったユーザーが、Client Application Serverから同じメッセージを何回もGCM Serverへ送っていたら、同じようなメッセージをいくつもデバイスで受信することになる。

Collapse Keyを同じにしていると、GCM Server側で、全てのメッセージではなく最新のメッセージ1つだけを選んで送る。

Collapse Keyは適当にランダムなどではなく、同一メッセージで統一させた方がいいなあ。


「Device not Registered」エラーが出たら、Client Application ServerからRegistration IDを削除する

デバイス側でGCMを使ったAndroid Applicationが削除された場合、次回メッセージ送信時に「Device not Registered」エラーが出る。

このとき、Client Application Server側で、非有効なRegistration IDをクリーンアップした方がいいなあ。


デバイスのバッテリーを節約するには「delay_while_idle」の指定が効きそう

「delay_while_idle」はC2DM時代からあるパラメータ。デバイスがオンラインでも、unlockされてデバイスの操作が始まらない限りメッセージを送らない、という指定ができる。

メッセージの即時性は無くなるけれど、デバイスが起きているときにだけメッセージが送られるので節電効果があるなあ。

2012年8月9日木曜日

Google Cloud Messaging(GCM)とC2DMの主な違いを挙げてみた

Google I/Oのプレゼン動画から、Google Cloud Messaging(GCM)の特徴やC2DMとの主な違いを挙げてみた。

C2DM時代は・・・
  • 開発者が利用するにはフォームの入力が必要でアクティベーションのメールを受信するという流れ
  • Sender Auth Tokenの取得にClient Loginを使用
  • 一日あたり20万メッセージまでというクオータの制限があった

GCMでは・・・
  • Google APIs Consoleにプロジェクトを追加してGCMを有効にするだけで利用できるようになる
  • 脱Client Login
  • クオータ制限なし

GCMでも変わらないこと
  • 無料のサービス
  • Froyo以上で稼働
  • メッセージ受信にRegistration IDが必要
  • メッセージがデバイスへ届くまでの基本的な流れ(Client Application Server→GCM Server→Android Device)

GCMで変わったこと
  • Project IDが必要
    Sender IDの代わり。Google APIs Consoleでプロジェクトを追加した時に取得。
  • API Keyが必要
    Sender Auth Tokenの代わり。Google APIs Consoleのプロジェクト管理画面で取得。
  • GCM Serverがjson形式のデータにも対応。json形式でリクエストを受け付けたりレスポンスを返したりするようになった。

GCMで追加・改善された機能

1.Message Multicasting
メッセージを複数デバイスへ一斉送信できるようになった。最大1,000台まで。

2.Expiring Messages
メッセージを再送する期間を指定できるようになった。

「time_to_live」パラメータにメッセージが有効な期間を秒単位で設定。0秒から4週間(デフォルト)まで指定可能。0秒の時はGCM Serverにメッセージは保存されずに直ちにデバイスへ送られる。この時デバイスがオフラインだったらメッセージは捨てられる。

指定した値をオーバーした段階でGCM Serverはメッセージをクリーンアップする。

3.Messages with Payload
メッセージにデータを含められるようになった。1メッセージあたり最大4KBまで。主に、チャットなどの簡単なテキスト送信に利用できる。

オフライン時間が長くてメッセージがたくさん溜まってしまっていたら、オンラインへ復帰した時に、GCM Serverは全てのメッセージを削除して「たくさんメッセージが溜まっていますよ」という1メッセージだけをデバイスへ送る。削除されたメッセージ数はGCM Serverからのレスポンスに含まれている。

デバイスがオフライン時にGCM Serverへ保存しておけるのは、最大100メッセージまで。

4.Speed
メッセージ送信がめっちゃ速くなった。GCM Serverへリクエストが届いてからメッセージを送り出すまで4.7ミリ秒。

5.Multiple Senders for Social Updates
1つのAndroid Applicationで複数のプロジェクトのメッセージを受け取れるようになった。自社サービスのメッセージも、Google Plusなどソーシャルサービスなどのメッセージも。ソーシャルサービスのProject IDをどうやって取得するのか、そのフローがよくわからない(´・ω・`)。

最大100個までのProject IDが指定可能。


補足1:GCMの信頼性はいかほど?
Reliable Message Queue(RMQ)の働き

RMQは、GCM Serverとデバイス側のGCM Frameworkとの間で動作するもの。メッセージが確実にデバイスへ届いたのかチェックする。

例えば、GCM Serverがメッセージを送った時、デバイスの接続が切れてしまい、メッセージが届かなかったとしても、接続が復帰してデバイスがGCM Serverへアクセスした際に、メッセージが届いていないことをRMQが探知する。そして、GCM Serverはメッセージを再送する。

ACKを送り合う事で到達の確認をしている。いちいちではなく、ある程度のまとまりでACKを送っている。

このような仕組で信頼性が高いシステムになっているのがGCMの強み。GCMはメッセージを無くさない( ̄^ ̄)


補足2:Android Developer ConsoleでGCM Serverが処理したメッセージの統計が見られるようになった

そのアプリでGCMへ登録された数や、OSバージョンなどの端末情報、メッセージが送られた数やCollapseされた数、GCM Serverからのエラーメッセージなどが見られる。


補足3:ICS以上からGoogle Accountをデバイスへ登録する必要が無くなった

ICS以上から、デバイスへGoogle Accountを事前登録する事無くプッシュサービスが使えるようになったとな。本当かな?こんど検証してみようっと。


おまけ:デバイスからメッセージを送れるようになる?

その機能を含んだAPIはあるけれど開発者向けには公開していない、と。それが出来るようになると、またパラダイムが変わりそう。期待したい。


超おまけ:スピーカーのFrancescoがキュートやわ〜(*^_^*)

2012年2月16日木曜日

GoogleのC2DMサーバーのセキュリティ例外を無視させる

GoogleのC2DMサーバーはオレオレ証明書を使っているので、Javaのプログラムでメッセージをを送るとき、https経由だとSSLHandshakeExceptionが発生してしまう。

それを回避するプログラム。

private static final String SERVER_URL = "https://android.apis.google.com/c2dm/send";

java.net.URL c2dmUrl = new java.net.URL(SERVER_URL);

javax.net.ssl.HttpsURLConnection conn = (javax.net.ssl.HttpsURLConnection) c2dmUrl.openConnection();
conn.setDoOutput(true);

// C2DMサーバーの自己証明書の例外を無視する
javax.net.ssl.KeyManager[] km = null;
javax.net.ssl.TrustManager[] tm = {
 new javax.net.ssl.X509TrustManager() {
  public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1) throws java.security.cert.CertificateException {}
  public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1) throws java.security.cert.CertificateException {}
  public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
 }
};
javax.net.ssl.SSLContext sslcontext= javax.net.ssl.SSLContext.getInstance("SSL");
sslcontext.init(km, tm, new java.security.SecureRandom());
conn.setSSLSocketFactory(sslcontext.getSocketFactory());

この処置でもまだ'HTTPS hostname wrong:'エラーが発生する。証明書のホスト名とアクセスしているホスト名も違うようだ。

よって更にホスト名違いも無視するように設定を追加。

conn.setSSLSocketFactory(sslcontext.getSocketFactory());

// 証明書にあるホスト名とアクセスしているホスト名の違いを無視する
conn.setHostnameVerifier(
 new javax.net.ssl.HostnameVerifier() {
  public boolean verify(String host, javax.net.ssl.SSLSession ses) { return true; }
 }
);


もう、どれだけずさんな証明書なんだか ┐('~`;)┌

2012年1月19日木曜日

C2DMを使ったアプリケーションを作るために必要なもの、必要なこと

必要なもの
  • Google C2DM Server
  • Application Server
  • Android Application
  • Client Application

必要なこと

[Google C2DM Server]
登録に必要なもの
  1. 開発者の連絡先メールアドレス
  2. Sender ID(C2DMサーバーへデータを送信する際に利用されるメールアドレス)

    Sender ID毎に利用できるクオータ値(一日あたり20万メッセージまで)が割り振られる。なので、Sender IDはグローバルなアドレスを設定するのではなく、C2DMを使うアプリケーション毎に個別に設定した方がよさそう。
  3. Android Applicationのパッケージ名(e.g. com.example.xxx)
登録後に、Sender Auth Token を取得する

[Application Server]
  • Client Applicationをデプロイする

[Android Application]
  • C2DMを使ったアプリを開発する

    Android OS 2.2(Froyo)以上が対象

    最低1台の端末にはGoogle Accountの設定が必要
アプリのマニフェストには以下の情報を必ず埋め込む
  1. Sender ID
  2. Android Application のパッケージ名(e.g. com.example.xxx)
  3. アプリはGoogle C2DM サーバーからデータを受信するためにBroadcast Receiver を実装して作る
  • アプリからGoogle のC2DMサーバーへリクエストを送りディバイスを登録する

    レスポンスからRegistration ID を取得する

    Registration IDをアプリ内に保存する

[Client Application]
  • Google のC2DMサーバーへリクエストを送るアプリケーションを開発する
そのアプリケーションでは、HTTPS通信で、以下の情報をC2DMサーバーへ送る
  1. Sender Auth Token
  2. Registration ID
  3. データ※1024バイト以下