Android の Intent はどんな時に “同じ Intent” として扱われるか

2015.02.28

概要

突然ですが、 Android に対して通知を発行するため等に使用する PendingIntent は、その一意性を Intent の一意性をもって定義しています。
このように、 Android 開発を行っていると何かと出くわすことの多い Intent が同一かどうかの判定 について解説します。

注: 実際には、 PendingIntent は IIntentSender を見ていて、そこに設定されている requestCode も一意性に含まれるのですが、ちょっと深く入りすぎるのでここでは"Intent を見ている"とさせて下さい。

Intent の同一性は Intent.filterEquals で確認される

ふたつの Intent が同一なものかどうか確認する際には、その確認元( AndroidSDK や OSS )によって Intent.filterEquals が呼びだされます。
このメソッドの定義および実装は次のとおりです。

public boolean filterEquals(Intent other) {
   if (other == null) {
       return false;
   }
   if (mAction != other.mAction) {
       if (mAction != null) {
           if (!mAction.equals(other.mAction)) {
               return false;
           }
       } else {
           if (!other.mAction.equals(mAction)) {
               return false;
           }
       }
   }
   if (mData != other.mData) {
       if (mData != null) {
           if (!mData.equals(other.mData)) {
               return false;
           }
       } else {
           if (!other.mData.equals(mData)) {
               return false;
           }
       }
   }
   if (mType != other.mType) {
       if (mType != null) {
           if (!mType.equals(other.mType)) {
               return false;
           }
       } else {
           if (!other.mType.equals(mType)) {
               return false;
           }
       }
   }
   if (mPackage != other.mPackage) {
       if (mPackage != null) {
           if (!mPackage.equals(other.mPackage)) {
               return false;
           }
       } else {
           if (!other.mPackage.equals(mPackage)) {
               return false;
           }
       }
   }
   if (mComponent != other.mComponent) {
       if (mComponent != null) {
           if (!mComponent.equals(other.mComponent)) {
               return false;
           }
       } else {
           if (!other.mComponent.equals(mComponent)) {
               return false;
           }
       }
   }
   if (mCategories != other.mCategories) {
       if (mCategories != null) {
           if (!mCategories.equals(other.mCategories)) {
               return false;
           }
       } else {
           if (!other.mCategories.equals(mCategories)) {
               return false;
           }
       }
   }

   return true;
}

上記から、 Intent の一意性は次の要素によって定まっていることがわかります。

  1. mAction (setAction メソッドで設定できる String プロパティ)
  2. mData (setData メソッドで設定できる Uri プロパティ)
  3. mType (setType メソッドで設定できる String プロパティ)
  4. mPackage (setPackage メソッドで設定できる String プロパティ)
  5. mComponent (setComponent や、よく使用されるコンストラクタ で設定できる、 ComponentName プロパティ)※詳細は後述
  6. mCategories (addCategory メソッドで追加できる、Stringのセット)

このうち、とりわけ重要な要素となっているのが、 mComponent プロパティです。
まずは、 Intent クラスに定義された次のコンストラクタ定義を確認してください。

public Intent(Context packageContext, Class<?> cls) {
   mComponent = new ComponentName(packageContext, cls);
}

これは、私たちが普段 Intent intent = new Intent(this, MyReallyCoolActiviy.class); といった具合に使用しているコンストラクタです。
上記のコンストラクタでは、 mComponent 以外の値が設定されていないことがわかります。
つまり、 私たちが別途 intent.setAction("MY_AWESOME_ACTION");intent.setData(Uri.parse("custom:uri")); のような設定をしない限りは、次の2つの Intent は同一です:

Intent myAwesomeIntent = new Intent(this, HogeActivity.class);
Intent myCoolIntent = new Intent(this, HogeActivity.class);

Log.d(TAG, "filterEquals: " + myAwesomeIntent.filterEquals(myCoolIntent)); // -> true

そして、上述の仕様を把握すれば、 Intent に任意の一意性を持たせることが出来ます。

Intent に任意の一意性を付加する

概要で説明したとおり、 Android 開発では何かと Intent が同じかどうかが問題になることがあります。
一例ですが、通常のアクティビティ呼び出しや、サービス呼び出しに使用する Intent にであれば、次のように任意に一意性を与えることが出来ます。

public static Intent createIntentWithId(String intentId) {
    Intent intent = new Intent(this, HogeActiviy.class);
    intent.setData(Uri.parse("myApp://intentId/" + intentId));
    return intent;
}

(前述のとおり)上の例で使用しているコンストラクタは mComponent のみを設定します。よって、他の項目については私たちが設定できます。
上の例では、 mData に値を設定することで一意性を付加しています。

setType(String type) メソッドは、 mType を設定するだけでなく、 mData に null を設定します。このメソッド使用する際には注意してください。

まとめ

Intent の filterEquals の仕様を理解して、うまく Intent を管理しましょう。