为啥没有找到处理 Intent 的 Activity?
Posted
技术标签:
【中文标题】为啥没有找到处理 Intent 的 Activity?【英文标题】:Why is no Activity found to handle Intent?为什么没有找到处理 Intent 的 Activity? 【发布时间】:2019-06-26 19:26:59 【问题描述】:我不想采用常规的getPackageManager().getLaunchIntentForPackage("com.example.app")
方式,而是想自己创建启动意图。
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage("com.example.app");
startActivity(intent);
如果 com.example.app
已安装、启用且清单正确,为什么 android 找不到 Activity? (它与getLaunchIntentForPackage
完美配合。)
【问题讨论】:
com.example.app 用清单文件中的 package="com.example...." 检查这个。 欢迎访问:***.com/a/30617251/6672577 @Opridaycom.example.app
的清单文件是正确的,它包含正确的包名称 (com.example.app
)。这与我尝试与intent.setPackage("com.example.app");
一起使用的包名称相同。没有错字。
@Opriday 我已经访问了您的链接,但在那里找不到任何相关内容。我应该查找哪些信息?
+1 。这实际上是一个很好的问题。让我们想知道您的意图和 getLaunchIntentForPackage() 创建的意图之间有什么区别。试试 Log.d(TAG, intent.toString() + " vs " + intent2.toString())。 (我添加了我的解决方法作为答案。)
【参考方案1】:
'要接收隐式意图,您必须在意图过滤器中包含 CATEGORY_DEFAULT 类别。' - 你的接收应用有这个吗?
例子:
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
摘自: https://developer.android.com/guide/components/intents-filters#Receiving
您还可以检查以确保有一个活动可以接收您的广播:
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
摘自: https://developer.android.com/training/basics/intents/sending#java
【讨论】:
这不是一个隐含的意图。这是明确的,因为包名是使用intent.setPackage("com.example.app")
指定的。 "通过提供目标应用的包名或完全限定的组件类名,显式意图指定哪个应用程序将满足该意图。" - source
接收应用在其LAUNCHER
意图过滤器中没有DEFAULT
类别,只有其他地方。
如果我尝试使用其他具有DEFAULT
类别的接收器应用程序,那将是完美的开始。但是DEFAULT
不应该是必需的,因为我的意图是明确的。如果我可以通过查看接收器应用程序的清单找到正确的活动,为什么 Android 不能为我做呢?它还知道类别、操作和包名称。如果getLaunchIntentForPackage
在没有DEFAULT
的情况下也能完美运行,那么我的方法也应该可以。【参考方案2】:
startActivity
将所有意图视为声明CATEGORY_DEFAULT
即使您的代码中没有intent.addCategory(Intent.CATEGORY_DEFAULT);
。
即使你加了intent.removeCategory(Intent.CATEGORY_DEFAULT);
。
即使您的意图很明确*:intent.setPackage("com.example.app");
.
* 提供“目标应用的包名或完全限定的组件类名”。
...除非没有
如果你设置了目标activity的类名,系统将不会去寻找CATEGORY_DEFAULT
:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClassName("com.example.app", "com.example.app.NameOfTheActivityToBeStarted");
startActivity(intent);
标题来源:the <category> element's page 上的蓝色注释。 显式意图的定义来源:developer.android.com。
【讨论】:
即使intent没有action或category,系统也会找到activity,比如:Intent intent = new Intent(); intent.setClassName("com.example.app", "com.example.app.NameOfTheActivityToBeStarted"); startActivity(intent);
【参考方案3】:
我了解到您正在尝试启动具有已知包名称 (com.example.app
) 的已知应用程序的启动器活动。我假设您有有关该应用程序的信息。因此,您可以像这样通过显式意图启动它:
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.app", "com.example.app.MainActivity"));
if(intent.resolveActivity(getPackageManager()) != null)
startActivity(intent);
编辑:对两个意图对象进行分析(intent1
== 你自己的意图 VS intent2
== 从getLaunchIntentForPackage()
创建的意图),区别是
意图1:
act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] pkg=com.example.app
意图2:
act=android.intent.action.MAIN 猫=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.example.app cmp=com.example.app/.MainActivity
我将不得不相信,您为创建自己的意图对象所做的一切不足以使显式意图起作用。您必须向 Android 提供有关您的意图的更多信息,例如具体的组件名称(如我上面的回答所示)。
【讨论】:
+1 为您提供帮助,您了解 我的示例代码的目标,resolveActivity
将我引导至其文档,其中包含有用的详细信息。但您的回答不是关于我的问题的目标:更深入地了解为什么找不到活动。 My own answer 已经有了使用setClassName
的解决方法(与您的setComponent
相同,只是更方便)。您的评论中有一个好主意(比较意图),但您的回答目前不包含新的细节,只是我回答的已知信息的一个子集。
(顺便说一句,你上面有一条评论starting with“我考虑过使用intent.setComponent(...),但是”,这与这个答案。)
谢谢。我确实认识到这不是您目标的答案。我现在没有足够的时间,所以我把我脑子里想的都给了你。我会在今天晚些时候回复你。干杯!
我更新了我的答案。查看由 getLaunchIntentForPackage() 创建的显式意图 VS 意图,看起来您缺少工作意图的值。应使用 setClassName() 或 setComponent() 来完成有关您的意图的信息。
有助于查看差异:0x10000000 标志 (Intent.FLAG_ACTIVITY_NEW_TASK
) 和组件名称。这证明系统应该在这两种情况下都找到正确的活动。但事实并非如此,所以问题仍然存在:为什么。活动存在,即使是孩子也能找到(知道动作、类别和包)。系统是否在寻找我告诉它寻找的东西?我们已经有了答案:不,it's looking for the default category too。但目前还不清楚为什么。如果有明确的意图,为什么要查找默认类别?【参考方案4】:
这是将 android.content.Intent#CATEGORY_DEFAULT 添加到所有 startActivity 代码的函数。
ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags)
try
return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
PackageManager.MATCH_DEFAULT_ONLY | flags
| ActivityManagerService.STOCK_PM_FLAGS, userId);
catch (RemoteException e)
return null;
/**
* Resolution and querying flag: if set, only filters that support the
* @link android.content.Intent#CATEGORY_DEFAULT will be considered for
* matching. This is a synonym for including the CATEGORY_DEFAULT in your
* supplied Intent.
*/
public static final int MATCH_DEFAULT_ONLY = 0x00010000;
这是一切开始的代码 http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/app/ContextImpl.java#766
【讨论】:
+1。很高兴看到代码(在调用startActivity
之后运行并且)在组件不为空时忽略标志(使setComponent
和setClassName
方法工作而不是不足的setPackage
方法)。我想这与described here 的逻辑相同,但我不确定也找不到代码。我们现在非常接近在代码级别上完全了解平台的行为。【参考方案5】:
您要求查看在startActivity
之后执行的代码,就在这里。
在您的应用中:Activity.startActivity(Intent)
调用Activity.startActivity(Intent, Bundle)
,调用Activity.startActivityForResult(Intent, int)
,调用FragmentActivity.startActivityForResult(Intent, int)
,调用Activity.startActivityForResult(Intent, int)
,调用Activity.startActivityForResult(Intent, int, Bundle)
,调用Instrumentation.execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)
,调用
IActivityManager.startActivity(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle)
最后一行的调用是远程进程调用,这意味着在您的应用进程中,该方法在代理 IActivityManager
实例上调用,该实例将其转发到另一个进程,在本例中为系统进程。
到目前为止,还没有进行 Intent 过滤。
在Android的系统进程中IActivityManager
解析为ActivityManagerService
并且:
ActivityManagerService.startivity(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle)
调用ActivityManagerService.startActivityAsUser(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle, int)
,调用ActivityStackSupervisor.startActivityMayWait(IApplicationThread, int, String, Intent, String, IVoiceInteractionSession, IVoiceInteractor, IBinder, String, int, int, ProfilerInfo, WaitResult, Configuration, Bundle, boolean, int, IActivityContainer, TaskRecord)
,调用ActivityStackSupervisor.resolveActivity(Intent, String, int, ProfilerInfo, int)
,调用IPackageManager.resolveIntent(Intent, String, int, int)
这是添加 MATCH_DEFAULT_ONLY 的地方,正如 nkalra0123 所说。
另外,这是另一个远程方法调用。 IPackageManager
被解析为 PackageManagerService
,然后从那里变成这样:
PackageManagerService.resolveIntent(Intent, String, int, int)
调用PackageManagerService.queryIntentActivities(Intent, String, int, int)
,它尝试获取 Intent 包的所有活动。这会从你的包中获取 Activity,然后调用PackageService.ActivityIntentResolver.queryIntentForPackage(Intent, String, int, ArrayList<PackageParser.Activity>, int)
,它会获取你的包中的 IntentFilters,然后调用PackageService.ActivityIntentResolver.queryIntentFromList(Intent, String, boolean , ArrayList<F[]>, int)
,它调用IntentResolver.buildResolveList(...)
,它针对您的 Intent 中的数据运行它找到的所有 IntentFilter,考虑我们是否需要 CATEGORY_DEFAULT
,并相应地将匹配的 IntentFilter 添加到列表中。
所有这些调用方法调用然后返回,最终某个对象会发现没有匹配的 IntentFilters。我在这里省略了,因为这是答案的相关部分。
【讨论】:
【参考方案6】:您需要为所需的应用程序创建一个组件名称,例如:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(ComponentName.createRelative("com.whatsapp",".Main"));
intent.setPackage("com.whatsapp");
组件名代表你需要打开的activity,完整的包名,第二个参数是该包的类名。
【讨论】:
【参考方案7】:让我在此添加一些其他信息。如here 所述,没有组件名称,意图是隐含的。
组件名称是可选的,但它是关键信息 这使意图明确,这意味着意图应该是 仅交付给组件名称定义的应用组件。 没有组件名称,意图是隐含的,系统 决定哪个组件应该根据另一个接收意图 意图信息(例如动作、数据和类别——描述 以下)。如果你需要在你的应用中启动一个特定的组件,你 应指定组件名称。
Context.startActivity 方法需要 DEFAULT
类别,以在未明确指定其组件名称时解析您的活动。查看this link 中的第一个示例。
希望这会有所帮助。
【讨论】:
以上是关于为啥没有找到处理 Intent 的 Activity?的主要内容,如果未能解决你的问题,请参考以下文章
为啥行为不同?- android:launchMode="singleTask" , android:taskAffinity="" 和 Intent.FLAG
没有找到处理 Intent 的活动:android.intent.action.EDIT
没有找到处理意图的活动:android.intent.action.VIEW