如何选择处理意图的服务

Posted

技术标签:

【中文标题】如何选择处理意图的服务【英文标题】:How to choose which service to handle an intent 【发布时间】:2012-02-18 07:33:44 【问题描述】:

我有一个应用程序使用包含在应用程序包中的服务。

我现在正在制作一个新应用,它使用相同服务的更新版本,捆绑在自己的应用程序包中。

如果设备上只安装了一个应用程序,这可以正常工作。但是,如果这两个应用程序都安装在设备上,它们都使用最近安装最少的软件包中的服务——即最旧的软件包。这是有问题的,原因有两个: 1)旧服务不处理新调用(显然) 2) 当使用与其他应用捆绑的服务时,服务最终会使用其他应用保存的数据,而不是自己的。

短期内,我真的只是希望每个应用程序都与自己的服务版本对话。是否可以在不让每个捆绑的服务响应一个独特的意图并让每个应用使用该独特的意图的情况下做到这一点?

从长远来看,最好让两个应用程序都与最新版本的服务通信,让它们共享数据,并且在任一应用程序被卸载时都能正常运行。这样做的正确方法是什么?数据主要存储在 SQLite 数据库中,该数据库存储在Context.getDatabasePath() 指定的位置,这当然是特定于应用程序的,如果卸载该应用程序,该数据库将被擦除。

这是我迄今为止尝试过的:

我可以使用PackageManager.queryIntentServices() 来获取包含两个版本的服务的 ResolveInfo 列表。通过查看ResolveInfo.serviceInfo.applicationInfo.packageName,我可以分辨出哪个是哪个。但是,似乎没有任何方法可以使用该 ResolveInfo 来指定我要处理意图的两个服务中的哪一个。

这似乎应该做正确的事:

serviceIntent.setPackage(context.getApplicationContext().getApplicationInfo().packageName);

因为该 packageName 与我要匹配的那个相同。事实上,当我用修改后的意图调用queryIntentServices 时,我得到一个列表,其中仅包含我的包中的服务。但是,当我尝试使用该意图绑定服务时,它会引发安全异常。

java.lang.RuntimeException: Unable to bind to service my.service.package.name.MyServiceClass@40531558 with Intent  act=my.intent.string pkg=my.app.package.name : java.lang.SecurityException: Not allowed to start service Intent  act=RuntimeException: Unable to bind to service my.service.package.name.MyServiceClass  without permission private to package

基于一些谷歌搜索,我还尝试通过以下方式创建意图:

Intent serviceIntent = new Intent().setClassName(
    "my.service.package.name",
    "my.service.package.name.ServiceClassName");

这根本不解决任何服务。

我还尝试将 androidManifest.xml 中服务条目中的 android:exported= 更改为 true、false 或未指定,在两个应用程序中以各种组合方式进行更改,但无济于事。 (我希望如果我将它们都设置为 false,那么每个应用程序将只能看到自己的服务副本。)

【问题讨论】:

我意识到上述尝试之一存在问题。对于Intent.setClassName() 方法,它应该是:serviceIntent = new Intent().setClassName( context.getApplicationContext().getApplicationInfo().packageName, "my.service.package.name.ServiceClassName"); 解析为一个服务,正确的一个,但尝试绑定该服务会引发与使用Intent.setPackage() 相同的安全异常。 【参考方案1】:

这是一个可行的解决方案:

在清单中添加服务的元数据。名称应该类似于“服务启动器”,并且值必须与意图过滤器中的操作名称相同。

<meta-data android:name="Service Launcher"
           android:value="your.service.action.string.here" />

<intent-filter>
    <action android:name="your.service.action.string.here" />
</intent-filter>

然后在您的代码中添加一个访问方法来获取元数据并使用它来构建 Intent。

public static String getServiceIntentAction(Context context) 
    String action = "";
    try 
        PackageManager pm = context.getPackageManager();
        android.content.ComponentName cn = new android.content.ComponentName(context.getPackageName(), "com.your.ServiceClass");
        android.content.pm.ServiceInfo si = pm.getServiceInfo(cn, PackageManager.GET_META_DATA);
        action = si.metaData.getString("Service Launcher");
     catch(android.content.pm.PackageManager.NameNotFoundException nnfe) 
    return action;

然后,任何正在构建 Intent 以启动服务的东西都会调用此方法。在他们的应用中使用该服务的每个用户都可以有不同的操作,以仅通过一个版本的库和清单的更改与他们的应用进行通信。

要让两个应用程序使用相同的服务,服务可能应该作为自己的实体安装。

【讨论】:

【参考方案2】:

您是否尝试过使用类别?对于意图中的每个类别,潜在接收组件必须匹配每个类别才能被视为接收者。

服务 A 的类别为“1”,类别“1”的意图去那里。 服务 B 具有类别“1”和类别“2”(假设它仍然支持版本“1”中的所有内容。这样,旧应用程序可以与新服务通信,但新应用程序无法与旧服务通信。

【讨论】:

这样,我可以强制新应用使用新版本的服务,但旧应用仍会根据安装顺序使用任意服务和数据。 (除非我只在服务 A 中添加了“1 但不是 2”类别,但这会很糟糕。)

以上是关于如何选择处理意图的服务的主要内容,如果未能解决你的问题,请参考以下文章

Android如何根据选择器意图中的用户操作请求权限

如何在 expo 应用程序中处理共享意图(发送图像)?

如何在 Android 11 中创建从图库中选择图像的意图?

android - 如何使用意图选择视频文件并获取它的视频

通过 Android 中的意图限制可选择的文件类型

通过 Android 中的意图限制可选择的文件类型