在API 30中,intent.resolveActivity返回null

126

查看intent.resolveActivity != null但启动意图会引发ActivityNotFound异常,我编写了使用深度链接打开浏览器或应用程序的代码:

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模拟器中找不到浏览器,而常见的解决方法有效:

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.resolveActivityInfo或者intent.resolveActivity会返回null。但是对于PDF查看器,它有效

我们应该取消使用intent.resolveActivity吗?


7
假设您的目标 API 级别为 30,这似乎是由于这个原因:Android 11 中的包可见性。确实,当我在清单文件中加上一个适当的<queries>元素并测试您的第一个代码片段时,它按预期工作。如果您不想包含这样的<queries>元素,则可以继续使用 try-catch - Mike M.
@MikeM.,谢谢!你能把它发表为答案吗?我稍后会测试它。 - CoolMind
哦,抱歉,我误读了你的评论。我以为你会在测试后发布它。我现在无法提供一个合适的答案,但是等我有空了,我会回来解答的。如果你只是想完成这个问题,请随意自己发布一个问题。我并不是非常担心声望或其他什么。 :-) 干杯! - Mike M.
抱歉让你等这么久。我真的想找到一些与你的具体示例更相关的文档或源代码,但我没有找到。然后我有点忘了它。我的错。干杯! - Mike M.
11个回答

160

这似乎是由于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>

目前可用的信息不是非常具体,但它确实指出:
PackageManager返回有关其他应用程序的结果的方法(例如queryIntentActivities()),根据调用应用程序的声明进行过滤
尽管您的示例正在使用Intent方法-即resolveActivityInfo(),但实际上是在内部调用PackageManager“查询”方法。可能不可行列出受此更改影响的每种方法和功能的详尽列表,但可以安全地假设如果涉及PackageManager,则最好检查其对新限制的行为。

13
除了警告 "元素类别不允许在此处" 之外,这对我很有效。 - Westy92
2
你能解释一下为什么推荐使用BROWSABLE类别吗?没有它似乎也能正常工作。 - yuval
10
希望能有某种警告或错误提示,提醒您需要在清单中添加此内容。 - lasec0203
1
@Westy92 我猜 <category> 只属于 <intent-filter>,对于 <intent> 我们不需要它。 我在 Android 官方文档中搜索了一些示例,发现它只列在 <intent-filter> 下。 https://developer.android.com/guide/components/intents-filters#ExampleFilters - Madhan
无法打开"https://youtu.be"链接。这就是为什么我选择了使用try catch解决方案的原因。 - undefined
显示剩余3条评论

118

感谢Mike M.,我加入了对BrowserCameraGallery的查询。将它们放在AndroidManifest中的任何部分(<application>标记之前或之后)。

查看MediaStore.ACTION_IMAGE_CAPTUREIntent.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>
Dylan 的回答对我来说不是必须的,因此我仍然拥有。
<uses-feature
    android:name="android.hardware.camera"
    android:required="false"
    />

正确的答案,谢谢,现在在安卓11一加设备上正常工作。 - Amardeepvijay
这里<data android:scheme="https" />和<data android:scheme="http" />有什么区别吗?如果我在应用程序的不同按钮中可以访问https和http,那该怎么办?我需要添加两个<!-- Browser --> <intent> <action android:name="android.intent.action.VIEW" /> <data android:scheme="http" /> </intent> <!-- Browser --> <intent> <action android:name="android.intent.action.VIEW" /> <data android:scheme="https" /> </intent>或者在一个标签中使用<data android:scheme="http" /><data android:scheme="https" />? - Raii
2
@Raii,目前我不清楚。我在两种情况下都使用了http。你想知道设备是否有浏览器吗?还是你想通过链接启动你的应用程序?在第二种情况下,你需要了解深层链接。 - CoolMind
@CoolMind,我只想进入Google Play商店下载我的应用程序。多年来,我一直使用了一些代码行(商店链接为https),但最近我发现了警告,并看到了这篇文章。我需要添加<queries>...</queries>来处理此问题,我看到有一个答案说http,另一个答案说https。@@ - Raii
2
@Raii,谢谢!你只能在https://地址中使用https。目前对于你的任务来说已经足够了。如果你将https替换为http,那么你将打开https://http://域名。所以,这取决于你更喜欢哪一个。 - CoolMind

44

我在尝试发送一封电子邮件,因此我需要在清单文件中设置查询参数,就像这样:

<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)
        }
    }

14
这并没有在 Android 官方链接中提到,该链接教授如何发送电子邮件:https://developer.android.com/guide/components/intents-common#Email。我经常讨厌这个平台,让我想把所有头发都拔光。 - Muhammad Ahmed AbuTalib
3
完全同意@muhammad-ahmed-abutalib。对我来说,<data android:scheme ="mailto"/>有效。 - Favolas
这个解决方案对我有效,但是如果我从电子邮件应用程序返回,则需要点击两次按钮才能打开活动,即使“intent.resolveActivity(context.packageManager)”不为null。 - FabioR

31

如果需要启动一个Activity(而不仅仅是检查是否存在),按照这个建议,我做了删除。

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

而写了一个替代品。

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

不需要在清单文件中添加任何<queries>。 在Api 28和Api 30上测试通过。


这是一个需要启动活动的情况。但有时候你需要知道是否有应用程序存在,而不是启动它们。 - CoolMind
谢谢!我没有注意到。我编辑了答案。 - Avital
1
@CoolMind 你本可以提供一个例子。我只能想到如果没有安装相关应用程序,则隐藏一些控件。例如:如果没有相机应用程序,我就不需要照片按钮。 - The incredible Jan
@TheincredibleJan,是的。如果你想要一个例子,可能会有这样的情况,你必须选择是启动一个应用程序还是自己处理。例如,如果在此设备上注册了您公司的应用程序,则应用程序将显示一个屏幕,否则将显示常规屏幕。或者您的应用程序可以通过Deep Link打开并编辑电子邮件消息,然后通过电子邮件客户端发送它。但是,如果没有电子邮件客户端,它应该打开另一个屏幕或关闭自己。我没有尝试过这些方案。 - CoolMind

6

对于Avital的回答,要想启动一个意图并知道它是否被启动,你不需要声明任何内容:

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

1
您可以在AndroidManifest中添加QUERY_ALL_PACKAGES权限。它不需要运行时权限请求。
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

6
是的,但是Google Play限制使用高风险或敏感权限,包括QUERY_ALL_PACKAGES权限,该权限可以查看给定设备上已安装应用程序的清单。 - CoolMind

1
我的解决方案
将此添加到清单中。
<queries>
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>
</queries>

使用这个函数

private File create_image() throws IOException {
    @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "img_" + timeStamp;
    File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
    return new File(storageDir, imageFileName + ".jpg");
}

请在 Android 10 上使用此功能

<application
     android:requestLegacyExternalStorage="true"
     ...

</application>

0

由于用户提出的问题没有设置任何限制,因此您可以简单地这样做,并且它将直接导航到带有传递的电子邮件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

1
我想,如果设备包含电子邮件应用程序,它就会起作用。如果没有,它将崩溃。 - CoolMind
是的,我们可以这样假设,但现在安卓设备上没有一个不带有电子邮件或Gmail应用程序的设备,否则我们可以使用上述答案,也可以使用try catch。 - Eddie Brock

0
如果您正在使用意图操作 ACTION_INSERT,根据文档,您需要在使用的活动中添加带有操作INSERT和指定mimeTypeintent-filter,以使intent.resolveActivity(view.context.packageManager) != null 返回 true。
<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>

0
“除了下面的代码,Mike补充的内容对我很有用。”
<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接