处理 Lollipop 中隐含的意图未来弃用

Dealing with implicit intent future deprecation in Lollipop

为了将数据传输到其他应用程序,我一直在使用隐式意图,如下例所示:

Intent intent = new Intent();
intent.setAction("com.example.OpenURL");
intent.putExtra("URL_TO_OPEN", url_string);
sendOrderedBroadcastAsUser(intent);

Intent intent = new Intent();
intent.setAction("com.example.CreateUser");
intent.putExtra("Username", uname_string);
intent.putExtra("Password", pw_string);
sendBroadcast(intent);

Intent intent = new Intent();
intent.setAction("com.example.BackupUserData");
intent.setData(file_uri);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
sendBroadcast(intent);

但是在 Android 5.0

中不再推荐这种行为

http://developer.android.com/about/versions/android-5.0-changes.html

Binding to a Service

The Context.bindService() method now requires an explicit Intent, and throws an exception if given an implicit intent. To ensure your app is secure, use an explicit intent when starting or binding your Service, and do not declare intent filters for the service.

来自 android 源代码更准确 "ContextImpl" class :

private void validateServiceIntent(Intent service) {
        if (service.getComponent() == null && service.getPackage() == null) {
            if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                IllegalArgumentException ex = new IllegalArgumentException(
                        "Service Intent must be explicit: " + service);
                throw ex;
            } else {
                Log.w(TAG, "Implicit intents with startService are not safe: " + service
                        + " " + Debug.getCallers(2, 3));
            }
        }
    }

我该如何处理?

是的,当 运行 在具有 Android 5.0 的设备中时,此代码将显示警告(如果您的应用程序的 targetSdkVersion 小于 21)或彻底崩溃(如果针对 Lollipop本身)。检查 source code for validateServiceIntent() in ContextImpl.java:

此代码很危险,因为它可能允许恶意接收者自行注册并拦截(或更改)这些调用。

最合理的选择可能是使用 Intent.setPackage() 指定要调用的应用程序的包名称。以这种方式完成后,意图不再是隐含的,而且它会起作用。例如:

   intent.setPackage("com.example.app");

当然,如果接收器在您的应用程序中,则有更简单的选择(但从您对问题的描述来看,情况似乎并非如此)。

如其他评论和答案中所述,更改仅与 bindService 相关,与 sendBroadcast 无关,因此如果是这种情况(如示例代码中所示)- 您不需要改变任何东西。

如果您使用 bindService,将隐式 Intent 转换为显式 Intent 的方法是使用 ComponentName 并使用 setComponent or setClass 方法将其设置在服务 Intent 上.

来自 Intent class 文档:

Intent Resolution

There are two primary forms of intents you will use.

Explicit Intents have specified a component (via setComponent(ComponentName) or setClass(Context, Class)), which provides the exact class to be run. Often these will not include any other information, simply being a way for an application to launch various internal activities it has as the user interacts with the application. Implicit Intents have not specified a component; instead, they must include enough information for the system to determine which of the available components is best to run for that intent.

正如@Commonsware 在他的 blog 中指出的那样,解决此问题的 3 种方法是:

1。 resolveService()

Intent i = new Intent("serviceName");
ResolveInfo info = ctx.getPackageManager().resolveService(i, Context.BIND_AUTO_CREATE);
i.setComponent(new ComponentName(info.serviceInfo.packageName,info.serviceInfo.name));

并使用意图绑定服务。

2。 queryIntentServices()

Intent i = new Intent("serviceName");

List<ResolveInfo> infos = ctx.getPackageManager().queryIntentServices(i,Context.BIND_AUTO_CREATE);

if (infos.isEmpty()) {
   throw new IllegalStateException("no service found");
}
if (infos.size() > 1) {
   throw new SecurityException("multiple services found, could be a security issue");
}

i.setComponent(new ComponentName(infos.get(0).serviceInfo.packageName, infos.get(0).serviceInfo.name));

如果查询 returns 多个信息,这可能意味着恶意服务正在侦听。

3。 setPackage()

如果你有包名,你可以像@matiash在他的post中所说的那样设置包名:

Intent i = new Intent(MyClass.class.getName());
i.setPackage(MyClass.class.getPackage().getName())