App component:「インテントの解決」の処理の流れ

投稿日:  更新日:

「インテントの解決」はシステム内でアプリケーションコンポーネントを選択する処理のことです。

この選択する処理の流れが複雑で、ちょっと癖がありあます。

ここでは「インテントの解決」の処理の流れについてまとめます。

スポンサーリンク

「インテントの解決」とは

アプリケーションコンポーネントの連携において、システムは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}
intent-filerをダンプする(logFilter:ResolveInfoのkotlin拡張関数)
fun ResolveInfo.logFilter(tag: String) {
    val _componentName = this.activityInfo.name
    val _filter = this.filter	// PackageManager.GET_RESOLVED_FILTERによる抽出の結果
    if(_filter != null) {
        val _actions = _filter.actionsIterator()?.let { it.toLogString() }?:"null"
        val _categories = _filter.categoriesIterator()?.let { it.toLogString() }?:"null"
        val _schemes = _filter.schemesIterator()?.let { it.toLogString() }?:"null"
        val _authorities = _filter.authoritiesIterator()?.let { it.toLogString() }?:"null"
        val _paths = _filter.pathsIterator()?.let { it.toLogString() }?:"null"
        val _types = _filter.typesIterator()?.let { it.toLogString() }?:"null"
        Log.i(tag, "Component=${_componentName} \nAction=${_actions} \nCategory=${_categories} \nScheme=${_schemes} \nAuthority=${_authorities} \nPath=${_paths} \nType=${_types}")
    }
    else {
        Log.i(tag, "Component=${_componentName}")
    }
}
Iteratorをダンプする(toLogString:Iteratorのkotlin拡張関数)
fun <T>Iterator<T>.toLogString(): String {
    val _strbuf = StringBuffer("{")
    this.forEach {
        when {
            (it is IntentFilter.AuthorityEntry) -> _strbuf.append("${it.host}:${it.port},")
            (it is PatternMatcher) -> _strbuf.append("${it.path},")
            else -> _strbuf.append("${it},")
        }
    }
    _strbuf.deleteCharAt(_strbuf.lastIndex)
    _strbuf.append("}")
    return _strbuf.toString()
}

上記の例に挙げたのは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-componentI-component
actionM-actionsI-action
categoryM-categoriesI-categories
I-category(I-categoriesの中の一つ)
dataM-datas(URIで表現)I-data(URIで表現)
M-schemes(URIのscheme部)I-scheme(URIのscheme部)
typeM-typesI-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がない場合は必ず不一致になります。

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のあり・なし」が成り立ちます。

data,typeの判定

    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の全てが一致しなくてもよい点に注意してください。

categoryの判定

    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;
    }
スポンサーリンク

関連記事:

アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 アプリケーションコンポーネントの概要をまとめます。 ...
アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 Activityはアプリケーションコンポーネントの1つです。アプリケーションの画面と処理を構築します。 Activityについてまとめます。 ...
アプリを構成する最上位の構成要素がアプリケーションコンポーネント(App component)です。 Content Providerはアプリケーションコンポーネントの1つです。アプリケーションが管理するデータを公開(共有)します。 Content Providerについてまとめます。 ...
スポンサーリンク