アンドロイドの明示的な(Explicit)、暗黙的(Implicit)インテント完全に理解する!

By JS | Last updated: December 05, 2018

アンドロイドインテント

Androidはインテント(Intent)と呼ばれるオブジェクトがあります。辞書の意味を探してみると、「意味」、「目的」などの意味です。 インテントを簡単に表現すると、メッセージとすることができるようになります。多くのSDK APIは、インテントを提供し、フレームワーク内部ではインテントがどういう意図で使用された解釈をします。 そして結果を処理したり、戻します。

たとえば、アクティビティを実行するときにインテントは非常に重要です。インテントに実行させるコンポーネントの情報が含まれておりActivityManagerは、これを解釈して、どのアクティビティを実行することになります。 また、ブロードキャストを使用する際もインテントのActionなどの値を渡して、他のエプドゥルにメッセージを転送します。 そしてPackageManagerにアクティビティを検索したりアプリの情報を得ることがときにもインテントを使用します。

明示的インテント(Explicit Intent)

本を読んでいると、暗黙的インテント(Implicit Intent)と明示的(Explicit Intent)という言葉を聞いたことがあると思います。 インテントの意味が明確と明示的インテントといって、不明確と暗黙的インテントと呼ばれます。

たとえば、 MainActivitySubActivityを実行するときに、次のようなコードを使用します。

val intent = Intent(this, SubActivity::class.java)
startActivity(intent)

ここでは、インテントは、明示的インテントと呼ばれます。意図が明確であるはずなのにね。 インテントに SubActivity::class.javaを引数として入れ、低アクティビティを実行くれ明確に意味を伝えるました。 ActivityManagerはそのインテントを解析し、 SubActivityを実行することになります。

暗黙的インテント(Implicit Intent)

一方、暗黙的インテントは、クラス名やパッケージ名を入れてくれません。次のコードは、暗黙的インテントで、デバイスにインストールされてエプドゥル中 アクションが ACTION_DIAL、Uriがtel:5551212なインテントを処理することができるアクティビティを見つけ実行できます。

val intent = Intent(Intent.ACTION_DIAL)
val TEST_DIAL_NUMBER = Uri.fromParts("tel", "5551212", null)
intent.setData(TEST_DIAL_NUMBER)
startActivity(intent)

デバイスにインストールされて電話アプリが実行され、Uriに入力された番号5551212が自動的に入力されました。 (電話アプリでアクティビティが実行され、指定されたUriを読んで番号を入力するように実装がされているものです)

image

暗黙的インテントもActivityManagerがインテントの意図に合った適切なアクティビティを見つけ実行できます。 より正確に言えば、ActivityManagerはPackageManagerに resolveActivity APIを呼び出して最適なアクティビティがどんなものか尋ねます。 これリジョルビング(resolving)と呼びます。

リジョルビングはインテントを解釈して、意図に合ったコンポーネントを探す過程を指します。 次のようなコードで直接 resolveActivityを呼び出して暗黙的インテントを処理することができるアクティビティを知ることができます。

val intent = Intent(Intent.ACTION_DIAL)
val TEST_DIAL_NUMBER = Uri.fromParts("tel", "5551212", null)
intent.setData(TEST_DIAL_NUMBER)
val info: ResolveInfo = packageManager.resolveActivity(intent, 0)
Log.d(TAG, "info : " + info)

ログは、このように出力されます。 PackageManagerはこのインテントを解釈して、意図に最も適したアクティビティである GoogleDialtactsActivityを見つけました。

MainActivity: info : ResolveInfo{8b61959 com.google.android.dialer/com.google.android.apps.dialer.extensions.GoogleDialtactsActivity m=0x208000}

リジョルビング過程を理解するには、インテントが含まれている属性と意味を知っている。

インテントの属性とリジョルビング

Action

アクション(Action)は、インテントを代表する値とすることができます。インテントは必ず1つのアクションだけ持つことができます。 2つ以上のアクションは持てません。 フレームワークインテントコードを見ると、以下のように様々なアクションが定義されており、どのような用途で使用される注釈があります。

Intent.java
public static final String ACTION_MAIN = "android.intent.action.MAIN";
public static final String ACTION_VIEW = "android.intent.action.VIEW";
public static final String ACTION_CALL = "android.intent.action.CALL";
public static final String ACTION_SENDTO = "android.intent.action.SENDTO";
....

いくつかのアクションがどのように使用されるかは、慣例的なものです。 GoogleがAndroidのアプリを作成するときに、電話アプリはAという種類のインテントフィルタ(すぐ下に説明)を使用した場合、 他の3rd partyエプドゥルも、Googleのサンプルアプリと同じようにインテントフィルタを設定する必要がしました。 フレームワーク内部にそのようなインテントを使用したので、従わない場合、フレームワークと互換性がないアプリになることがあるんですよ。

インテントフィルタ(IntentFilter)はAndroidManifest.xmlにコンポーネント(Activityなど)タグの下に登録することができ、 このコンポーネントがどのインテントを処理することができるかどうかPackageManagerに知らせるコードです。もしインテントフィルタを登録しなかった場合、 PackageManagerは、暗黙的なテントをリジョルビングするとき候補群から、そのコンポーネントを除外されます。 したがって、暗黙的インテントにリジョルビングがなければならないコンポーネントは、インテントフィルタを登録する必要があります。

リジョルビングをする基本的なアルゴリズムは、インストールされてアプリのすべてのインテントフィルタとインテントを比較して最も適切なインテントフィルタを探します。そして、そのインテントフィルタを定義したコンポーネントを返します。 アクションは、最も最初に比較する属性としては、もしインテントのアクションとインテントフィルタのアクションが異なる場合、最初にフィルタリングされて候補群から除外されます。 たとえば、 ACTION_MAINを持っているインテントをリジョルビングするときに、このアクションを持っているインテントフィルタを対象に、次のプロパティをチェックします。

Category

カテゴリはリジョルビング過程で使用される第二の属性です。アクションとは異なるインテントフィルタはカテゴリーを1つ以上設定することができます。 暗黙的インテントのリジョルビング対象になるためにインテントフィルタに必ず CATEGORY_DEFAULTが含まれます。 インテントフィルタがインテントのリジョルビング対象となるに、インテントフィルタはインテントが持っているカテゴリをすべて持っていればされます。

たとえばみると、次のようなインテントフィルタが宣言されています。 アクションは、私任意 codechacha.intent.action.TESTに決めました。 そしてカテゴリは段階的に1つずつ新しいカテゴリーが追加されました。

<activity android:name=".SubActivity">
    <intent-filter>
        <action android:name="codechacha.intent.action.TEST"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
<activity android:name=".SubActivity2">
    <intent-filter>
        <action android:name="codechacha.intent.action.TEST"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.APP_BROWSER"/>
    </intent-filter>
</activity>
<activity android:name=".SubActivity3">
    <intent-filter>
        <action android:name="codechacha.intent.action.TEST"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.APP_BROWSER"/>
        <category android:name="android.intent.category.APP_CALCULATOR"/>
    </intent-filter>
</activity>

次のように新しいカテゴリーが1つずつ追加されたインテントにリジョルビングをしてみました。最初のインテントは、 DEFAULTカテゴリのみ持っています。 そのため、3つのインテントフィルタがすべてリジョル憑依対象となります。第二インテントは、 DEFAULTAPP_BROWSERを持っています。最初のインテントフィルタは対象から除外されますが 2番目と三番目は対象に含まれます。第三インテントは、カテゴリーが3つあります。最後インテントフィルタのみリジョルビングの対象に含まれます。

(ちなみに、Intentオブジェクトにカテゴリを何も設定してくれなければ、フレームワーク内部で CATEGORY_DEFAULTを設定できます。)

val intent = Intent("codechacha.intent.action.TEST")
intent.addCategory(Intent.CATEGORY_DEFAULT)
val info: ResolveInfo = packageManager.resolveActivity(intent, 0)
Log.d(TAG, "1) : " + info)

val intent2 = Intent("codechacha.intent.action.TEST")
intent2.addCategory(Intent.CATEGORY_DEFAULT)
intent2.addCategory(Intent.CATEGORY_APP_BROWSER)
val info2: ResolveInfo = packageManager.resolveActivity(intent2, 0)
Log.d(TAG, "2) : " + info2)

val intent3 = Intent("codechacha.intent.action.TEST")
intent3.addCategory(Intent.CATEGORY_DEFAULT)
intent3.addCategory(Intent.CATEGORY_APP_BROWSER)
intent3.addCategory(Intent.CATEGORY_APP_CALCULATOR)
val info3: ResolveInfo = packageManager.resolveActivity(intent3, 0)
Log.d(TAG, "3) : " + info3)

resolveActivityの結果として何が出力されたログを見ましょう。最初と2番目のテントは、結果として ResolverActivityが戻されました。第三は、 SubActivity3が返されました。 上記のリジョルビング対象に対して話してみました、最初と2番目のテントはリジョルビング対象が二つ以上です。 PackageManagerはこの二つ以上のインテントフィルタで対象を絞り込むことができますが、この中にいくつかのアクティビティが最も適しているかの決定をしました。したがって、ユーザーレポート定めてくれ意味で ResolverActivityを返しました。第三は、リジョルビング対象が1つのみがないので、 SubActivity3が結果として返されました。

MainActivity: 1) : ResolveInfo{8b61959 android/com.android.internal.app.ResolverActivity m=0x0}
MainActivity: 2) : ResolveInfo{518191e android/com.android.internal.app.ResolverActivity m=0x0}
MainActivity: 3) : ResolveInfo{e1e05ff com.codechacha.intent/.SubActivity3 m=0x108000}

ResolverActivityが何なのか気にすることができますよ。私たちがよく見るChooserActivityのようなものです。 以下のコードで実行すれば、リジョルビングされていないResolverActivityが実行されます。 3つのうち1つを選択する必要があり、このインテントのリジョルビング候補群が3つだからです。

val intent = Intent("codechacha.intent.action.TEST")
intent.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(intent)

image

Data

データは、URIをいい、URIは以下のように4つのパートで構成されます。

<scheme>://<host>:<port>/<path>

ex)
content://com.example.project:200/folder/subfolder/etc

データを持っているインテントを生成するコードは以下の通りです。 Uriを文字列に知っていれば Uri.parseを通じてUriオブジェクトにすることができます。 そして setDataにUriを設定することができます。

val intent = Intent()
val uri = Uri.parse("content://com.example.project:200/folder/subfolder/etc")
intent.setData(uri)
Log.d(TAG, intent.data.toString())

Intent.dataをログに出力してみると以下のように出力されます。

MainActivity: content://com.example.project:200/folder/subfolder/etc

MIME Type

MIMEタイプは video/mpegまたはimage/*などのデータのタイプを表現します。 タイプは、開発者が勝手に定義することができますが、他のアプリで利用可能なアプリを作った場合は、Android上で慣例的に使用することが必要です。

AndroidManifest.xmlでインテントフィルタは、以下のように定義することができます。

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

コードでMIMEタイプを設定するには、以下のようにください。

val intent = Intent()
intent.setType("video/mp4")

MIMEタイプとUriを一緒に設定するには、 setDataAndTypeを使用します。別々設定すると、最後に設定されたプロパティのみを適用され、他の属性は削除されます。

intent.setDataAndType(uri, "video/mp4")

Resolving

すぐ上のカテゴリーのプロパティを処理する際にリジョルビングについて簡単に説明しました。 アクションとカテゴリがあればリジョルビングを理解することは簡単ですが、 DataMIME Typeが追加されると、少し混乱します。 また、時間が経過すると、か食べた記憶がアンナニより混乱する。 DataMIME Typeは後で扱いにして、今回の記事では、ActionとCategoryのみリジョルビングを説明します。

リジョルビング(Resolving)という言葉は、使用されていませんが、フレームワーク内部にインテントを解釈する際にresolveという言葉を使用します。 だからリジョルビングを定義言うと、インテントに合ったコンポーネントを見つける過程とすることができます。

リジョルビングを理解する前に、PackageManagerのクエリ(Query)に対して必要です。 PackageManagerは queryIntentActivitiesというAPIを提供しています。インテントと関連があるアクティビティを探してくれるAPIです。 カテゴリー属性を説明する際に使用したアプリでは、次のようなコードでクエリをしてみましょう。

val intent = Intent("codechacha.intent.action.TEST")
intent.addCategory(Intent.CATEGORY_DEFAULT)
var resolveInfo = packageManager.queryIntentActivities(intent, 0)
resolveInfo?.forEach({it ->
    Log.d(TAG, "info: ${it.toString()}")
})

インテントに対してクエリをし、その結果を出力するコードです。下の出力ログを見ると、3つのすべてのSubActivityがクエリの結果に含まれていました。 引数として渡されたインテントのカテゴリーがデフォルト万持っているので、3つのインテントフィルタを通過し、結果として出力されます。

2018-12-06 20:11:06.990 4638-4638/com.codechacha.intent D/MainActivity: info: ResolveInfo{25b9e6b com.codechacha.intent/.SubActivity m=0x108000}
2018-12-06 20:11:06.990 4638-4638/com.codechacha.intent D/MainActivity: info: ResolveInfo{d093dc8 com.codechacha.intent/.SubActivity2 m=0x108000}
2018-12-06 20:11:06.991 4638-4638/com.codechacha.intent D/MainActivity: info: ResolveInfo{e726e61 com.codechacha.intent/.SubActivity3 m=0x108000}

クエリをすると、内部的にデバイスにインストールされたすべてのアプリのアクティビティを比較するようになります。 ログ上に m=に見える16進数は、インテントとインテントフィルタがどのよう似て数字で表現したものです。 0以上であれば、一致するものであり0以下であれば、まったく一致していないということを意味します。

クエリは、マッチのスコアをつけ、全く一致していないことを(matchが0以下である値)フィルタリングします。 そしてリジョルビングはろ過されたコンポーネントのうち誰を実行するかを決定するプロセスです。 候補群の中の特別なコンポーネントがなければ、 ResolverActivityを浮かせ、ユーザーが選択するようにします。しかし、以前にユーザーが選択ハヨトオトダと、システムは再び尋ね見ず、以前に選択したコンポーネントを選択します。

コードで確認して見ますよ。次のコードを実行すると、 ResolverActivityが開かれます。最初のアクティビティを選択して、常にこのアクティビティで離すという意味である alwaysボタンを押します。

val intent = Intent("codechacha.intent.action.TEST")
intent.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(intent)

今、ユーザーが好みの(Preferred)アクティビティを選択したため、再度そのコードを実行すると、 ResolverActivityが出ず、選択された最初のアクティビティが実行されます。 再実行しましょう。 ResolverActivityが出ずに最初のアクティビティが表示されます。

コードで確認試すことができ。今回は resolveActivityにどのようなもの出力されるかを確認してみましょう。 resolveActivityは、クエリの結果の中で最も適したアクティビティを探してくれるAPIとしました。

val intent = Intent("codechacha.intent.action.TEST")
intent.addCategory(Intent.CATEGORY_DEFAULT)
val info3: ResolveInfo = packageManager.resolveActivity(intent, 0)
Log.d(TAG, "info " + info3)

ログを見ると、 ResolveActivityが返されず、SubActivityが戻されました。クエリを通し候補群が3つ絞られ、ユーザーが3つのアクティビティの中の最初のものを選択したので、聞いて見ない最初のアクティビティがリジョル憑依結果として出力されます。

MainActivity: info ResolveInfo{25b9e6b com.codechacha.intent/.SubActivity m=0x108000}

まとめ

インテント、クエリー、リジョルビングに対して簡単に説明しました。インテントのアクションとカテゴリのみを使用する場合、インテントフィルタを設計することは難しいことではありません。しかし、DATAとMIMEを追加した場合、インテントフィルタの設計が少し困難な場合があります。暗黙的インテントという言葉のようにリジョルビングは、常に正確ではありません。意図とは異なる実行されたらインテントの定義が間違っているか同じようなアクティビティがデバイスに存在する可能性があります。このような問題をデバッグするための良い方法は、PackageManagerの queryIntentActivitiesresolveActivityを適切に使用することです。

DataとMIMEタイプには、次の記事アンドロイド、明示的、暗黙的インテント(Explicit、Implicit Intent)(2)で続いていきます。

参考

Related Posts

codechachaCopyright ©2019 codechacha