Dealing with implicit intent future deprecation in

2019-03-13 01:11发布

问题:

To transmit data to other applications I've been using implicit intents as in examples below:

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

But this behavior is not recommended anymore in 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.

From android source code more precisely "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));
            }
        }
    }

How can I deal with this ?

回答1:

Yes, when running in a device with Android 5.0 this code will either show a warning (if your app's targetSdkVersion is < 21) or crash outright (if targeting Lollipop itself). Check the source code for validateServiceIntent() in ContextImpl.java:

This code is dangerous as it might allow a malicious receiver to register itself and intercept (or alter) these calls.

The most reasonable alternative is probably to specify the package name of the app you want to call, by using Intent.setPackage(). When done this way, the intent is no longer implicit, and it will work. For example:

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

Of course, if the receiver is inside your app, there are easier alternatives (but that does not seem to be the case, from your description of the issue).



回答2:

As said in other comments and answers, the change is only relevant to bindService and not sendBroadcast, so if that's the case (as in your sample code) - you don't need to change anything.

If you do use bindService, the way to make an implicit Intent to an explicit Intent is to use a ComponentName and set it on the service Intent using the setComponent or setClass method.

From the Intent class documentation:

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.



回答3:

As @Commonsware pointed out in his blog, the 3 ways to resolve this are:

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

And use the intent to bind the service.

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

If the query returns more than one info, this could mean a malicous services is listening.

3. setPackage()

If you have the package name, you could just set the package name as @matiash said in his post:

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