アンドロイドインテント
Androidはインテント(Intent)と呼ばれるオブジェクトがあります。辞書の意味を探してみると、「意味」、「目的」などの意味です。 インテントを簡単に表現すると、メッセージとすることができるようになります。多くのSDK APIは、インテントを提供し、フレームワーク内部ではインテントがどういう意図で使用された解釈をします。 そして結果を処理したり、戻します。
たとえば、アクティビティを実行するときにインテントは非常に重要です。インテントに実行させるコンポーネントの情報が含まれておりActivityManagerは、これを解釈して、どのアクティビティを実行することになります。 また、ブロードキャストを使用する際もインテントのActionなどの値を渡して、他のエプドゥルにメッセージを転送します。 そしてPackageManagerにアクティビティを検索したりアプリの情報を得ることがときにもインテントを使用します。
明示的インテント(Explicit Intent)
本を読んでいると、暗黙的インテント(Implicit Intent)と明示的(Explicit Intent)という言葉を聞いたことがあると思います。 インテントの意味が明確と明示的インテントといって、不明確と暗黙的インテントと呼ばれます。
たとえば、 MainActivity
でSubActivity
を実行するときに、次のようなコードを使用します。
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を読んで番号を入力するように実装がされているものです)
暗黙的インテントも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つ以上のアクションは持てません。 フレームワークインテントコードを見ると、以下のように様々なアクションが定義されており、どのような用途で使用される注釈があります。
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つのインテントフィルタがすべてリジョル憑依対象となります。第二インテントは、 DEFAULT
とAPP_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)
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
すぐ上のカテゴリーのプロパティを処理する際にリジョルビングについて簡単に説明しました。
アクションとカテゴリがあればリジョルビングを理解することは簡単ですが、 Data
とMIME Type
が追加されると、少し混乱します。
また、時間が経過すると、か食べた記憶がアンナニより混乱する。
Data
とMIME 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の queryIntentActivities
とresolveActivity
を適切に使用することです。
DataとMIMEタイプには、次の記事アンドロイド、明示的、暗黙的インテント(Explicit、Implicit Intent)(2)で続いていきます。
参考
Related Posts
- エラー解決:android gradle plugin requires java 11 to run. you are currently using java 1.8.
- Android - コルーチンとRetrofitによる非同期通信の例
- Android - コルーチンでURL画像を読み込む
- Android - 振動、Vibrator、VibrationEffectの例
- Some problems were found with the configuration of task
- Query method parameters should either be a type that can be converted into a database column or a List
- UbuntuでAndroid 12オープンソースをダウンロードしてビルド
- Android - ViewModelを生成する方法
- Android - Transformations.map(), switchMap() の違い
- Android-Transformations.distinctUntilChanged()소개
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - 携帯電話の電話番号を取得する方法
- Android 12 - Splash Screens
- Android 12 - インクリメンタルインストール
- Android - adbコマンドでbugreportログファイルの抽出
- Android - adbコマンドでAppデータを削除する
- Android - adbコマンドでアプリ無効化、有効化
- Android - adbコマンドで特定のパッケージのPIDを検索
- Android - adbコマンドでパーミッションGrantまたはRevoke
- Android - adbコマンドで特定のパッケージのプロセスの終了
- Android - adbコマンドでapkのインストール、削除、
- Android - adb push、pullでファイルのコピー、ダウンロード
- Android - adbコマンドでscreen capture保存
- Android - adbコマンドでSystemアプリの削除、インストール
- Android - adbコマンドでsettings value確認、変更、
- Android 12 - IntentFilterのexported明示的な宣言
- Android - adbコマンドで工場出荷時の(Factory reset)
- Android - adb logcatコマンドでログ出力
- Android - adbコマンドでメモリダンプ(dump-heap)
- Android - adbコマンドでApp強制終了(force-stop)
- Android - adbコマンドでServiceの実行、終了
- Android - adbコマンドでActivity実行
- Android - adbコマンドでBroadcast配信
- Android - PackageManagerにPackage情報を取得する
- Android - ACTION_BOOT_COMPLETEDイベント受信