「インテントの解決」はシステム内でアプリケーションコンポーネントを選択する処理のことです。
この選択する処理の流れが複雑で、ちょっと癖がありあます。
ここでは「インテントの解決」の処理の流れについてまとめます。
目次
「インテントの解決」とは
アプリケーションコンポーネントの連携において、システムはIntentに指定された「起動したいコンポーネント」を参照し、コンポーネントのリストから要望に合うものを選びます。
これを「インテントの解決」と呼びます。

「起動したいコンポーネント」は、コンポーネント名(明示的な呼び出しの場合)、action・category・data(暗黙的な呼び出しの場合)といったパラメータを使って表現されます。
※詳細は「アプリケーションコンポーネント(App component)」を参照してください。
コンポーネントの選択はこのパラメータを判断材料にして行われるのですが、パラメータ値の有無や優先順位などが関係して、処理の流れが複雑になっています。
「インテントの解決」の結果を参照
コンポーネントの起動を行うこと無しに「インテントの解決」を実施し、その結果を見る事が出来ます。
<activity
android:name=".Sample_Activity"
android:exported="true" >
<intent-filter>
<action android:name="com.example.action.DEMO" />
<category android:name="com.example.category.STAGE_0" />
<data
android:scheme="myfile"
android:host="com.example.sample"
android:port="55000"
android:path="/sdcard/photo"
android:mimeType="image/jpg"
/>
</intent-filter>
</activity>
queryIntentXXXを使う方法(起動可能な候補のリスト)
val _intent = Intent()
.setAction("com.example.action.DEMO")
.addCategory("com.example.category.STAGE_0")
.setDataAndType(
Uri.parse("myfile://com.example.sample:55000/sdcard/photo"),
"image/*"
)
val _resolved = packageManager.queryIntentActivities( // List<ResolveInfo>
_intent,
PackageManager.GET_RESOLVED_FILTER // intent-filterの情報を抽出
)
if(_resolved.size > 0) { // 選択されたコンポーネントがある?
_resolved.forEach { it.logFilter("${TAG}-Resolved") }
}
... I/MainActivity-Resolved: Component=com.example.Sample_Activity
Action={com.example.action.DEMO}
Category={com.example.category.STAGE_0}
Scheme={myfile}
Authority={com.example.sample:55000}
Path={/sdcard/photo}
Type={image/jpg}
上記の例に挙げたのはActivityの場合です。同様な関数がコンポーネント毎に用意されています。
val _resolved1: List<ResolveInfo> = packageManager.queryIntentActivities(
intent: Intent, flags: Int)
val _resolved2: List<ResolveInfo> = packageManager.queryIntentServices(
intent: Intent, flags: Int)
val _resolved3: List<ResolveInfo> = packageManager.queryBroadcastReceivers(
intent: Intent, flags: Int)
val _resolved4: List<ResolveInfo> = packageManager.queryIntentContentProviders(
intent: Intent, flags: Int)
これらは、コンポーネントの起動を行う前に、選択されるコンポーネントの有無を確認する手段として役立ちます。
resoltXXXを使う方法(起動可能な候補)
val _intent = Intent()
.setAction("com.example.action.DEMO")
.addCategory("com.example.category.STAGE_0")
.setDataAndType(
Uri.parse("myfile://com.example.sample:55000/sdcard/photo"),
"image/*"
)
val _resolved = packageManager.resolveActivity( // ResolveInfo?
_intent,
PackageManager.GET_RESOLVED_FILTER
)
_resolved?.logFilter("${TAG}-Resolved") // null⇒選択されたコンポーネントがない
... I/MainActivity-Resolved: Component=com.example.Sample_Activity
Action={com.example.action.DEMO}
Category={com.example.category.STAGE_0}
Scheme={myfile}
Authority={com.example.sample:55000}
Path={/sdcard/photo}
Type={image/jpg}
上記の例に挙げたのはActivityの場合です。同様な関数がコンポーネント毎に用意されています。
val _resolved1: ResolveInfo = packageManager.resolveActivity(
intent: Intent, flags: Int)
val _resolved2: ResolveInfo = packageManager.resolveService(
intent: Intent, flags: Int)
// resolveReceiverは存在しない
val _resolved4: ProviderInfo = packageManager.resolveContentProvider(
String authority, flags: Int)
これらは、コンポーネントの起動を行う前に、選択されるコンポーネントの有無を確認する手段として役立ちます。
enabledとexportedを考慮しない
上記の方法はマニフェストのenabledとexported(呼び出しの拒否・許可)を考慮しません。「インテントの解決」のみの結果です。起動可能な候補に過ぎません。
ですので、「起動できる・できない」を確定したければ、コンポーネントのenabledとexportted(ActivityであればResolveInfo#activityInfo.enabled,exported)を参照する必要があります。
解決の処理の流れ0:フローチャート中のパラメータ
解決の処理は、リストからコンポーネントを取り出しつつ、コンポーネントの情報と条件を比較し、一致したものを選択する流れになっています。

以下は、流れを図示したフローチャート中に登場するパラメータ名です。コンポーネントの情報と条件を、接頭語の「M-」と「I-」で区別しています。
| パラメータ | コンポーネントの情報 (マニフェストに指定) | コンポーネントの条件 (Intentインスタンスに指定) |
|---|---|---|
| コンポーネント名 | M-component | I-component |
| action | M-actions | I-action |
| category | M-categories | I-categories I-category(I-categoriesの中の一つ) |
| data | M-datas(URIで表現) | I-data(URIで表現) |
| M-schemes(URIのscheme部) | I-scheme(URIのscheme部) | |
| type | M-types | I-type |
| ※複数形:複数個のパラメータを指定可能なもの | ||
解決の処理の流れ1:明示・暗黙的の判定
トップ階層のフローです。
始めにI-componentの有無を条件に、明示的・暗黙的のどちらであるかを判定します。
判定後は、どちらか片方の処理のみが行われる点に注意してください。

システムが持つリストからコンポーネントの情報(M-***)を取り出して、Intentインスタンスに指定されたコンポーネントの条件(I-***)と一致確認を行う処理がループされます。
protected List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
List<ResolveInfo> result = new ArrayList<>();
List<ResolveInfo> resolveInfoList = queryOverriddenIntents(intent, flags);
if (!resolveInfoList.isEmpty()) {
result.addAll(
filterResolvedComponent(
resolveInfoList, flags, (resolveInfo) -> resolveInfo.activityInfo));
}
if (isExplicitIntent(intent)) {
ResolveInfo resolvedActivity = resolveActivityForExplicitIntent(intent);
if (resolvedActivity != null) {
result.addAll(
filterResolvedComponent(
Arrays.asList(resolvedActivity), flags, (resolveInfo) -> resolveInfo.activityInfo));
}
} else {
result.addAll(
filterResolvedComponent(
queryImplicitIntentActivities(intent),
flags,
(resolveInfo) -> resolveInfo.activityInfo));
}
return result;
}
private ResolveInfo resolveActivityForExplicitIntent(Intent intent) {
ComponentName component = getComponentForIntent(intent);
for (Package appPackage : packages.values()) {
Activity activity = findMatchingComponent(component, appPackage.activities);
if (activity != null) {
return buildResolveInfo(activity);
}
}
return null;
}
private List<ResolveInfo> queryImplicitIntentActivities(Intent intent) {
List<ResolveInfo> resolveInfoList = new ArrayList<>();
for (Package appPackage : packages.values()) {
if (intent.getPackage() == null || intent.getPackage().equals(appPackage.packageName)) {
for (Activity activity : appPackage.activities) {
IntentFilter intentFilter = matchIntentFilter(intent, activity.intents);
if (intentFilter != null) {
resolveInfoList.add(buildResolveInfo(activity, intentFilter));
}
}
}
}
return resolveInfoList;
}
解決の処理の流れ2:コンポーネント名の判定
明示的なインテントで、コンポーネント名の一致確認を行う部分です。

private static <T extends Component> T findMatchingComponent(
ComponentName componentName, List<T> components) {
for (T component : components) {
if (componentName.equals(component.getComponentName())) {
return component;
}
}
return null;
}
解決の処理の流れ3:コンポーネントの条件の判定(actionの判定)
暗黙的なインテントで、コンポーネントの条件の一致確認を行う部分です。
actionの判定がもっとも優先度が高く、I-actionが必須の条件になっている点に注意してください。I-actionがない場合は必ず不一致になります。

private static IntentFilter matchIntentFilter(
Intent intent, ArrayList<? extends PackageParser.IntentInfo> intentFilters) {
for (PackageParser.IntentInfo intentInfo : intentFilters) {
if (intentInfo.match(
intent.getAction(),
intent.getType(),
intent.getScheme(),
intent.getData(),
intent.getCategories(),
"ShadowPackageManager")
>= 0) {
return intentInfo;
}
}
return null;
}
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag, boolean supportWildcards,
@Nullable Collection<String> ignoreActions) {
if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
return NO_MATCH_ACTION;
}
int dataMatch = matchData(type, scheme, data, supportWildcards);
if (dataMatch < 0) {
if (false) {
if (dataMatch == NO_MATCH_TYPE) {
Log.v(logTag, "No matching type " + type
+ " for " + this);
}
if (dataMatch == NO_MATCH_DATA) {
Log.v(logTag, "No matching scheme/path " + data
+ " for " + this);
}
}
return dataMatch;
}
String categoryMismatch = matchCategories(categories);
if (categoryMismatch != null) {
if (false) {
Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
}
return NO_MATCH_CATEGORY;
}
// It would be nice to treat container activities as more
// important than ones that can be embedded, but this is not the way...
if (false) {
if (categories != null) {
dataMatch -= mCategories.size() - categories.size();
}
}
return dataMatch;
}
private boolean matchAction(String action, boolean wildcardSupported,
@Nullable Collection<String> ignoreActions) {
if (wildcardSupported && WILDCARD.equals(action)) {
if (ignoreActions == null) {
return !mActions.isEmpty();
}
for (int i = mActions.size() - 1; i >= 0; i--) {
if (!ignoreActions.contains(mActions.get(i))) {
return true;
}
}
return false;
}
if (ignoreActions != null && ignoreActions.contains(action)) {
return false;
}
return hasAction(action);
}
...
public final boolean hasAction(String action) {
return action != null && mActions.contains(action);
}
解決の処理の流れ4:data,typeの判定
暗黙的なインテントで、data、typeの一致確認を行う部分です。
判定をバイパスする経路が3つあります。
(1)M-schemesとM-typesの両方がない時
(2)M-schemesがない時
(3)M-typesがない時
つまり、「パラメータdataとtypeを判定の条件に含めたくなければ、M-schemesとM-Types指定しなければよい」ことになります。
ちなみに、schemeはdataに指定するURIの一部のです。URIのフォーマットでshcemeは必須のパラメータになります。従って、「schemeのあり・なし=dataのあり・なし」が成り立ちます。

private int matchData(String type, String scheme, Uri data, boolean wildcardSupported) {
final boolean wildcardWithMimegroups = wildcardSupported && countMimeGroups() != 0;
final List<String> types = mDataTypes;
final ArrayList<String> schemes = mDataSchemes;
int match = MATCH_CATEGORY_EMPTY;
if (!wildcardWithMimegroups && types == null && schemes == null) {
return ((type == null && data == null)
? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);
}
if (schemes != null) {
if (schemes.contains(scheme != null ? scheme : "")
|| wildcardSupported && WILDCARD.equals(scheme)) {
match = MATCH_CATEGORY_SCHEME;
} else {
return NO_MATCH_DATA;
}
final ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;
if (schemeSpecificParts != null && data != null) {
match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart(), wildcardSupported)
? MATCH_CATEGORY_SCHEME_SPECIFIC_PART : NO_MATCH_DATA;
}
if (match != MATCH_CATEGORY_SCHEME_SPECIFIC_PART) {
// If there isn't any matching ssp, we need to match an authority.
final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
if (authorities != null) {
int authMatch = matchDataAuthority(data, wildcardSupported);
if (authMatch >= 0) {
final ArrayList<PatternMatcher> paths = mDataPaths;
if (paths == null) {
match = authMatch;
} else if (hasDataPath(data.getPath(), wildcardSupported)) {
match = MATCH_CATEGORY_PATH;
} else {
return NO_MATCH_DATA;
}
} else {
return NO_MATCH_DATA;
}
}
}
// If neither an ssp nor an authority matched, we're done.
if (match == NO_MATCH_DATA) {
return NO_MATCH_DATA;
}
} else {
// Special case: match either an Intent with no data URI,
// or with a scheme: URI. This is to give a convenience for
// the common case where you want to deal with data in a
// content provider, which is done by type, and we don't want
// to force everyone to say they handle content: or file: URIs.
if (scheme != null && !"".equals(scheme)
&& !"content".equals(scheme)
&& !"file".equals(scheme)
&& !(wildcardSupported && WILDCARD.equals(scheme))) {
return NO_MATCH_DATA;
}
}
if (wildcardWithMimegroups) {
return MATCH_CATEGORY_TYPE;
} else if (types != null) {
if (findMimeType(type)) {
match = MATCH_CATEGORY_TYPE;
} else {
return NO_MATCH_TYPE;
}
} else {
// If no MIME types are specified, then we will only match against
// an Intent that does not have a MIME type.
if (type != null) {
return NO_MATCH_TYPE;
}
}
return match + MATCH_ADJUSTMENT_NORMAL;
}
解決の処理の流れ5:categoryの判定
暗黙的なインテントで、categoryの一致確認を行う部分です。
categoryはコンポーネントの条件の中で複数個の指定が可能なパラメータになっています。ループが存在するのは全てのcategory値を処理するためです。
I-categoriesが無い場合は判定がバイパスされる点に注意してください。
また、I-categoriesの全てが一致するればよく、M-categoriesの全てが一致しなくてもよい点に注意してください。

public final String matchCategories(Set<String> categories) {
if (categories == null) {
return null;
}
Iterator<String> it = categories.iterator();
if (mCategories == null) {
return it.hasNext() ? it.next() : null;
}
while (it.hasNext()) {
final String category = it.next();
if (!mCategories.contains(category)) {
return category;
}
}
return null;
}
関連記事:
