「インテントの解決」はシステム内でアプリケーションコンポーネントを選択する処理のことです。
この選択する処理の流れが複雑で、ちょっと癖がありあます。
ここでは「インテントの解決」の処理の流れについてまとめます。
目次
「インテントの解決」とは
アプリケーションコンポーネントの連携において、システムは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; }
関連記事: