intent.resolveActivity returns API 30 中为空

intent.resolveActivity returns null in API 30

看着 intent.resolveActivity != null but launching the intent throws an ActivityNotFound exception 我写道打开一个浏览器或一个带有深层链接的应用程序:

private fun openUrl(url: String) {
    val intent = Intent().apply {
        action = Intent.ACTION_VIEW
        data = Uri.parse(url)
//        setDataAndType(Uri.parse(url), "text/html")
//        component = ComponentName("com.android.browser", "com.android.browser.BrowserActivity")
//        flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_GRANT_READ_URI_PERMISSION
    }
    val activityInfo = intent.resolveActivityInfo(packageManager, intent.flags)
    if (activityInfo?.exported == true) {
        startActivity(intent)
    } else {
        Toast.makeText(
            this,
            "No application can handle the link",
            Toast.LENGTH_SHORT
        ).show()
    }
}

没用。在 API 30 模拟器中找不到浏览器,而常见的 solution 可以工作:

private fun openUrl(url: String) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
    try {
        startActivity(intent)
    } catch (e: ActivityNotFoundException) {
        Toast.makeText(
            this,
            "No application can handle the link",
            Toast.LENGTH_SHORT
        ).show()
    }
}

第一种方法不行,因为intent.resolveActivityInfointent.resolveActivityreturnsnull。但对于 PDF 查看器,它 .

我们应该解散 intent.resolveActivity 吗?

这似乎是由于 the new restrictions on "package visibility" introduced in Android 11

基本上,从 API 级别 30 开始,如果您的目标是该版本或更高版本,则您的应用无法查看大多数外部包或直接与之交互,除非通过一揽子 QUERY_ALL_PACKAGES 许可,或通过在您的清单中包含适当的 <queries> 元素。

确实,您的第一个代码片段在获得该许可或清单中适当的 <queries> 元素的情况下按预期工作;例如:

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
</queries>

目前可用的信息不是非常具体,但确实说明:

The PackageManager methods that return results about other apps, such as queryIntentActivities(), are filtered based on the calling app's <queries> declaration

虽然您的示例使用的是 Intent 方法——即 resolveActivityInfo() – 实际上是在内部调用 PackageManager“查询”方法。受此更改影响的每个方法和功能的详尽列表可能不可行,但可以安全地假设如果涉及 PackageManager,您可能会用新限制检查其行为。

除了下面的代码之外,Mike 所说的对我有用。

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

感谢 ,我添加了对 BrowserCameraGallery 的查询。将它们放在 AndroidManifest 内的任何部分(在 <application> 标签之前或之后)。

看着 MediaStore.ACTION_IMAGE_CAPTURE and Intent.ACTION_GET_CONTENT 我得到了两个动作。

<queries>
    <!-- Browser -->
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="http" />
    </intent>

    <!-- Camera -->
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>

    <!-- Gallery -->
    <intent>
        <action android:name="android.intent.action.GET_CONTENT" />
    </intent>
</queries>

不需要我回答,所以我还有

<uses-feature
    android:name="android.hardware.camera"
    android:required="false"
    />

对我来说,我试图发送一封电子邮件,所以我需要像这样在清单中设置查询:

<queries>
    <intent>
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="*" />
    </intent>
</queries>

然后像这样发送电子邮件并检查电子邮件客户端:

        private fun sendEmail(to: Array<String>) {
        val intent = Intent(Intent.ACTION_SENDTO)
        intent.data = Uri.parse("mailto:") // only email apps should handle this
        intent.putExtra(Intent.EXTRA_EMAIL, to)
//        intent.putExtra(Intent.EXTRA_SUBJECT, subject)
        if (intent.resolveActivity(requireContext().packageManager) != null) {
            startActivity(intent)
        }
    }

对于需要启动Activity的情况(不仅检查是否存在),在this advise之后我删除了

if (intent.resolveActivity(packageManager) != null)
                startActivity(intent);

并写了 instaed

try {
     startActivity(intent);
} catch (ActivityNotFoundException e) {
     Toast.makeText(getContext(), getString(R.string.error), Toast.LENGTH_SHORT).show();
}

无需在 Manifest 处添加任何 <queries>。在 Api 28 和 Api 30 上测试。

您可以在 AndroidManifest 中添加 QUERY_ALL_PACKAGES 权限。它不需要 运行 次权限请求。

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

要迭代 Avital 的答案,如果您想启动一个 Intent 并了解它是否已启动,则无需声明任何内容:

private fun startIntent(intent: Intent): Boolean {
    return try {
        context.startActivity(intent)
        true
    } catch (e: ActivityNotFoundException) {
        Logger.error("Can't handle intent $intent")
        false
    }
}

如果您使用意图操作 ACTION_INSERT,根据文档,您需要将 intent-filter 添加到使用 activity 和操作 INSERT 中,并且使用指定的 mimeType,使 intent.resolveActivity(view.context.packageManager) != null return 为真。

<activity ...>
<intent-filter>
    <action android:name="android.intent.action.INSERT" />
    <data android:mimeType="vnd.android.cursor.dir/event" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

由于用户提出的问题没有设置任何限制,所以你可以这样做,它会使用你在 INTNET 中传递的 EMAIL ID 直接导航到 GMAIL

val emailTo = "user@gmail.com" // just ex. write the email address you want
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("mailto:$emailTo")
startActivity(intent) // <---- this is work for activity and fragment both