2012/06/09

Android:IntentFilterにDEFAULT_CATEGORYが必要な理由

IntentFilterにカテゴリandroid.intent.category.DEFAULTを登録しないと、
暗黙的IntentでActivityを呼び出すことができません。

「そういうものだ」で終わってもいいのですが...
なぜわざわざDEFAULTカテゴリを指定しなければならないのか?調査しました。

ディベロッパサイトの-Intents and Intent Filters #Category test-では、
startActivityに渡される暗黙的Intentは、カテゴリ"android.intent.category.DEFAULT"
を含んでいるかのように扱われる
旨の記載があります。
http://developer.android.com/guide/topics/intents/intents-filters.html#ifs

ここの実装部分を見てみます。

Intentを解決するために、PackageManagerとPackageManagerServiceは
"どのActivityを起動するか"を決定する必要があります。

Intent解決の部分を追ってみます。

// startActivity~Intent解決処理開始まで
com.android.server.am.ActivityManagerService.startActivity(IApplicationThread...)
└com.android.server.am.ActivityStack.startActivityMayWait(IApplicationThread...)
 └com.android.server.am.ActivityStack.resolveActivity(Intent...)

startActivityが呼ばれると、ほぃほぃ色々あってActivityStack.resolveActivity()が呼
ばれます。
ここから「誰がこのIntentに応答できるのか?」を解決する為の"Intent解決"処理が開始
されます。

・DEFAULT_CATEGORY必須フラグを立てるresolveActivity
resolveActivityの中身は非常にシンプル。
しかしここで非常に重要なフラグ"PackageManager.MATCH_DEFAULT_ONLY"が設定されます。
resolveIntent(
    intent, resolvedType,
    PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS);


・PackageManager.MATCH_DEFAULT_ONLYは何者か?
ディベロッパサイトを見ると、
int PackageManager.MATCH_DEFAULT_ONLY
http://developer.android.com/reference/android/content/pm/PackageManager.html#MATCH_DEFAULT_ONLY

なにやら、Intent解決用のオプションプラグのよう。
これをONにすると、CATEGORY_DEFAULTをサポートするActivityが抽出される。
つまり、CATEGORY_DEFAULTをサポートしないActivityは抽出されないということ。

本当にそうなのか?もう少し追ってみます。

com.android.server.am.ActivityStack.resolveActivity(Intent...)
└com.android.server.pm.PackageManagerService.resolveIntent(Intent...)
 └com.android.server.pm.PackageManagerService.queryIntentActivities(Intent...)
  └com.android.server.pm.PackageManagerService.ActivityIntentResolver.queryIntent(Intent...)
   └com.android.server.IntentResolver.buildResolveList(Intent...)

Intent処理できるActivity一覧を取得するqueryIntentActivitiesがここででてきましたね!
# 引数にMATCH_DEFAULT_ONLYを指定する理由もこれでわかりますね。

いかにもそれらしい名前の"IntentResolver.buildResolveList()"が今回のメインです。
中身を見てみます。
match = filter.match(action, resolvedType, scheme, data, categories, TAG);
if (match >= 0) {
    if (debug) Slog.v(TAG, "  Filter matched!  match=0x" +
            Integer.toHexString(match));
    if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
        final R oneResult = newResult(filter, match);
        if (oneResult != null) {
            dest.add(oneResult);  // ★Activity候補に追加する
        }
    } else {
        hasNonDefaults = true;
    }
...
ActivityがIntentに応答できる場合match >= 0がtrueとなります。
この時点ではIntentFilterにパスしています。

しかし、下記の条件式...
if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT))
defaultOnlyは、MATCH_DEFAULT_ONLYが指定されるとtrueになります。
filter.hasCategory(Intent.CATEGORY_DEFAULT)は、CATEGORY_DEFAULTを持つフィルタで
あればtrueを返します。
つまり、今回のケースだとこの条件式はfalseとなりActivityは起動対象になれません。


これでCATEGORY_DEFAULTを定義しないと暗黙的IntentにActivityが反応できない理由が
わかりました。


以上です。