chrometophone (5)

ええと、とりあえず c2dm ディレクトリの中にある com.google.android.c2dm パケジの基底クラス関連について掘削再開。ちなみに RETRY なソレについては、Manifest への記載漏れって事に勝手にしちゃいました。わははは。

直前エントリにて

自分で sendBroadcast して自分で受けてる、って書いてますが、あれは誤りですね。そのあたりをきちんと整理する上でもこちら側をきちんと整理しておく必要あり。

C2DMBaseReceiver

ええと、このクラスは抽象クラスになってて IntentService を継承しております。定義されてるメソドは以下。

  • String な引数が必要なコンストラクタ
    • これ、superclass がそーなってるので、というカンジ
  • protected abstract void onMessage(Context, Intent)
  • public abstract void onError(Context, String)
  • public void onRegistered(Context, String)
    • これ abstract ではないのですが、中身の記述がありません
  • public void onUnregistered(Context)
    • これも上記と同様
  • public final void onHandleIntent(Intent)
  • static void runIntentInService(Context, Intent)
    • これ、BroadcastReceiver な C2DMBroadcastReceiver から呼び出されますが、詳細は別途
  • private void handleRegistration(Context, Intent)

順にざっくり確認していきます。

コンストラクタ

ええと、定義は以下ッス。

    /**
     * The C2DMReceiver class must create a no-arg constructor and pass the 
     * sender id to be used for registration.
     */
    public C2DMBaseReceiver(String senderId) {
        // senderId is used as base name for threads, etc.
        super(senderId);
        this.senderId = senderId;
    }

これはスーパークラス側でデフォルトコンストラクタ書いてね、って事なのかな。ちなみに何故かは分からんのですが、IntentService ではデフォなソレがありません。

IntentService(String name)
Creates an IntentService.

これもデフォルトは subclass 側で書けよな、って事なのだろうか。抽象メソドとか中身が空なのについてはスルーで次。

onHandleIntent メソド

これですが、startService で queue に入ったナニが順に取り出されて順次呼び出されていくメソドとなっております。Intent な action としては以下の三つのみな模様。

  • com.google.android.c2dm.intent.RETRY
    • C2DMessaging.register メソドが呼び出されます
  • com.google.android.c2dm.intent.REGISTRATION
    • handleRegistration メソドが呼び出されます
  • com.google.android.c2dm.intent.RECEIVE
    • onMessage メソド (subclass で実装) が呼び出されます

あと、finally なブロックで mWakeLock.release() という命令が記述されてます。ちょっと気になるので諸々確認、と思ったんですが次の runIntentInService で出てきますのでスルー。

runIntentInService メソド

Power Manager によれば PowerManager#newWakeLock で PowerManager.WakeLock なオブジェクトが取得できるとの事。例示されてるソレを以下に引用。

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
 wl.acquire();
   ..screen will stay on during this section..
 wl.release();

acquire でロックを取って、release で解放するんかな。runIntentInService では以下な形になっております。

        if (mWakeLock == null) {
            // This is called from BroadcastReceiver, there is no init.
            PowerManager pm = 
                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 
                    WAKELOCK_KEY);
        }
        mWakeLock.acquire();

ええと PARTIAL_WAKE_LOCK という事なので CPU のみ使いまっせ、という事かな。
で、続きですが、ちょっとしたおまじないをして startService してます。

        // Use a naming convention, similar with how permissions and intents are 
        // used. Alternatives are introspection or an ugly use of statics. 
        String receiver = context.getPackageName() + ".C2DMReceiver";
        intent.setClassName(context, receiver);
        
        context.startService(intent);

これで onHandleIntent が呼び出されます。
ちなみに BroadcastIntent のみで起動される事を前提にすると

  • BroadcastReceiver な C2DMessaging#onReceive が BroadcastIntent を受け取る
  • C2DMessaging#onReceive から C2DMBaseReceiver.runIntentInService が呼び出される
    • 全てのエントリポイントがここ、と言っても過言ではないと思われます
    • ここで PowerManager.WakeLock#acquire でロック確保して startService してます
  • C2DMBaseReceiver#onHandleIntent に制御が移る
    • startService した結果、ここに dispatch されるはず
    • 全てのエギジットポイントがここ、と言っても (ry
    • ここで PowerManager.WakeLock#release してます
handleRegistration メソド

com.google.android.c2dm.intent.REGISTRATION な action だった場合の処理。

  • 登録成功
  • 登録失敗
  • 登録解除
    • これは UNREGISTER のレスポンスなのかな

ええと登録解除の場合、

  • C2DMessaging.clearRegistrationID 呼び出し
  • onUnregistered メソド呼び出し
    • これってサブクラスで override したらそっちが呼ばれるのかなぁ
    • 微妙。。

で return しております。
次は午前中にハマッた登録失敗なソレです。基本的には SERVICE_NOT_AVAILABLE な場合の再送処理のみが記述されております。
で、登録成功時の処理ですが以下。

            try {
                onRegistrered(context, registrationId);
                C2DMessaging.setRegistrationId(context, registrationId);
            } catch (IOException ex) {
                Log.e(TAG, "Registration error " + ex.getMessage());
            }

この onRegistered も抽象メソドではないなぁ。subclass の記述を見てみたんですが、override しております。直感的には同じ階層のメソドが呼ばれそうな気がするけど違うんだろうなぁ。何故に abstract なメソドとして定義してないのかが謎すぎる。

C2DMBroadcastReceiver クラス

Manifest に記述されている

        <receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver"

ですな。基本的には以下な記述のみ。

    public final void onReceive(Context context, Intent intent) {
        // To keep things in one place.
        C2DMBaseReceiver.runIntentInService(context, intent);
        setResult(Activity.RESULT_OK, null /* data */, null /* extra */);        
    }

特にコメントは無し。

C2DMessaging クラス

何も継承しておらず、メソドは全て static という Android 道のド真ん中を突き進むクラスですな。これってもしかしてアプリ毎に一つ必要だったりするんかな、って思ったんですがそうでも無いのかな、と以下なコメントから思いました。

/**
 * Utilities for device registration.
 *
 * Will keep track of the registration token in a private preference.
 */

ちなみに微妙に気になっているのが register な処理。

    /**
     * Initiate c2d messaging registration for the current application
     */
    public static void register(Context context,
            String senderId) {
        Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
        registrationIntent.setPackage(GSF_PACKAGE);
        registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
                PendingIntent.getBroadcast(context, 0, new Intent(), 0));
        registrationIntent.putExtra(EXTRA_SENDER, senderId);
        context.startService(registrationIntent);
        // TODO: if intent not found, notification on need to have GSF
    }

Intent こさえて startService しとりますな。ええと定数の定義も以下に纏めて引用 (順番も適当に弄ってます)。

    public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
    public static final String GSF_PACKAGE = "com.google.android.gsf";
    public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
    public static final String EXTRA_SENDER = "sender";

ええと、これって sendBroadcast ではなくて startService で良いのか。ドキュメントによればサンプルが以下、ってありますな。

Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
registrationIntent.putExtra("sender", emailOfSender);
startService(registrationIntent);

ちなみに空っぽな PendingIntent 作って putExtra しとりますが、上記コメントによれば boilerplate とあるので特に気にしない事にします。

一応

c2dm の中にある com.google.android.c2dm なパケジの中身な方々のチェック完了って事でエントリ投入します。