Why is no Activity found to handle Intent?

2019-04-10 02:48发布

问题:

Instead of going the regular getPackageManager().getLaunchIntentForPackage("com.example.app") way, I want to create the launch intent by myself.

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage("com.example.app");
startActivity(intent);

Why does Android not find the Activity, if the com.example.app is installed, enabled and has a correct manifest? (It works perfectly with getLaunchIntentForPackage.)

回答1:

I understand that you are trying to start the Launcher activity of a known application with known package name (com.example.app). I assume you have information about the application. Thus, you can start it via explicit intent like so:

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.app", "com.example.app.MainActivity"));
if(intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

EDIT: Doing an analysis of the two intent objects (intent1 == your own intent VS intent2 == intent created from getLaunchIntentForPackage()), the difference is

intent1:

{ act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] pkg=com.example.app }

intent2:

{ act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.example.app cmp=com.example.app/.MainActivity }

I will have to believe that what you have done to create your own intent object is not enough for the explicit intent to work. You will have to provide Android more information about your intent such as being specific with the component name (as shown in my answer above).



回答2:

'To receive implicit intents, you must include the CATEGORY_DEFAULT category in the intent filter.' - Does your receiving app have this?

Example:

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

Excerpt from: https://developer.android.com/guide/components/intents-filters#Receiving

You can also check to make sure there is an activity that can receive your broadcast:

 PackageManager packageManager = getPackageManager();
 List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
 boolean isIntentSafe = activities.size() > 0;

Excerpt from: https://developer.android.com/training/basics/intents/sending#java



回答3:

This is the function where android.content.Intent#CATEGORY_DEFAULT is added to all startActivity code.

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;

This is code from where everything starts http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/app/ContextImpl.java#766



回答4:

startActivity treats all intents as if they declared CATEGORY_DEFAULT

  • Even if you don't have intent.addCategory(Intent.CATEGORY_DEFAULT); in your code.

  • Even if you add intent.removeCategory(Intent.CATEGORY_DEFAULT);.

  • Even if your intent is explicit*: intent.setPackage("com.example.app");.
    * Supplies "either the target app's package name or a fully-qualified component class name".

...except if it doesn't

The system will not look for CATEGORY_DEFAULT if you set the class name of the target activity:

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClassName("com.example.app", "com.example.app.NameOfTheActivityToBeStarted");
startActivity(intent);

Source of header: the blue note on the <category> element's page.
Source of the definition of an explicit intent: developer.android.com.



回答5:

You asked to see the code executed after startActivity and here it is.

In your app:
Activity.startActivity(Intent) calls
Activity.startActivity(Intent, Bundle), which calls
Activity.startActivityForResult(Intent, int), which calls
FragmentActivity.startActivityForResult(Intent, int), which calls
Activity.startActivityForResult(Intent, int), which calls
Activity.startActivityForResult(Intent, int, Bundle), which calls
Instrumentation.execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle), which calls IActivityManager.startActivity(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle)

The call on the last line is a remote process call, meaning that in your app process a the method is called on a proxy IActivityManager instance which forwards it to another process, in this case a system process.

Up to this point, no Intent filtering has taken place.

In Android's system process IActivityManager resolved to ActivityManagerService and:

ActivityManagerService.startivity(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle) calls
ActivityManagerService.startActivityAsUser(IApplicationThread, String, Intent, String, IBinder, String, int, int, ProfilerInfo, Bundle, int), which calls
ActivityStackSupervisor.startActivityMayWait(IApplicationThread, int, String, Intent, String, IVoiceInteractionSession, IVoiceInteractor, IBinder, String, int, int, ProfilerInfo, WaitResult, Configuration, Bundle, boolean, int, IActivityContainer, TaskRecord), which calls
ActivityStackSupervisor.resolveActivity(Intent, String, int, ProfilerInfo, int), which calls
IPackageManager.resolveIntent(Intent, String, int, int)

This is the where MATCH_DEFAULT_ONLY is added, as nkalra0123 said.

Also, this is another remote method invocation. IPackageManager gets resolved to PackageManagerService, and from there it goes like this:

PackageManagerService.resolveIntent(Intent, String, int, int) calls
PackageManagerService.queryIntentActivities(Intent, String, int, int), which attempts to get all the Activities for the Intent package. This gets the Activities from your package and then calls
PackageService.ActivityIntentResolver.queryIntentForPackage(Intent, String, int, ArrayList<PackageParser.Activity>, int), which gets the IntentFilters in your package and then calls
PackageService.ActivityIntentResolver.queryIntentFromList(Intent, String, boolean , ArrayList<F[]>, int), which calls
IntentResolver.buildResolveList(...), which runs all the IntentFilters it found against the data in your Intent, taking into account whether or not we need CATEGORY_DEFAULT, and adding the matching IntentFilters to a list accordingly.

All these call method calls then return and eventually some object somewhere will figure out there were no matching IntentFilters. I omit that here because this is the relevant part of the answer.



回答6:

You need to create a component name for the needed app such as:

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(ComponentName.createRelative("com.whatsapp",".Main"));
intent.setPackage("com.whatsapp");

The component name represents the activity that you need to open, full package name and the second parameter is the class name for that package.



回答7:

Let me add some additional info in this. as described here, Without a component name, the intent is implicit.

Component name is optional, but it's the critical piece of information that makes an intent explicit, meaning that the intent should be delivered only to the app component defined by the component name. Without a component name, the intent is implicit and the system decides which component should receive the intent based on the other intent information (such as the action, data, and category—described below). If you need to start a specific component in your app, you should specify the component name.

The DEFAULT category is required for the Context.startActivity method to resolve your activity when its component name is not explicitly specified. Check first example in this link.

Hope this will help.