基于已安装的Android包名进行意图选择器的自定义过滤

61

我想利用内置的意图选择器来显示一个自定义过滤应用程序列表,供用户选择并启动。

我知道如何获取已安装包的列表:

final Intent myIntent = new Intent(android.content.Intent.ACTION_MAIN);  
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(myIntent, 0);
此时我想根据包名称中包含的特定字符串(或字符串变化)筛选列表,这一点我也能够处理。但是在这里,我的问题出现了。据我所知,Intent.createChooser() 只接受一个目标 Intent 作为参数。我希望有一个重载版本以基于包名和类名的列表形式接受多个意图。但是我没看到类似的内容。我是否错过了什么?因此,问题是:是否可以使用内置 chooser 实现此功能,还是必须使用 AlertDialog Builder 构建自己的 chooser?我希望避免后者。提前感谢。
7个回答

112

这是我想出来的一个方案。我用它来为选择器中的每个选择项提供不同的意图数据,但您也可以轻松地从列表中删除一个意图。

List<Intent> targetedShareIntents = new ArrayList<Intent>();
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
shareIntent.setType("text/plain");
List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(shareIntent, 0);
if (!resInfo.isEmpty()) {
    for (ResolveInfo resolveInfo : resInfo) {
        String packageName = resolveInfo.activityInfo.packageName;
        Intent targetedShareIntent = new Intent(android.content.Intent.ACTION_SEND);
        targetedShareIntent.setType("text/plain");
        targetedShareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "subject to be shared");
        if (TextUtils.equals(packageName, "com.facebook.katana")) {
            targetedShareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "http://link-to-be-shared.com");
        } else {
            targetedShareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "text message to shared");
        }
        targetedShareIntent.setPackage(packageName);
        targetedShareIntents.add(targetedShareIntent);
    }
    Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(0), "Select app to share");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[targetedShareIntents.size()]));
    startActivity(chooserIntent);
}

编辑

使用此方法时,我获取到一些应用程序的名称被显示为Android系统。如果有人遇到此错误,请在 targetedShareIntents.add(targetedShareIntent); 之前添加以下行:

 targetedShareIntent.setClassName(
                        resolveInfo.activityInfo.packageName,
                        resolveInfo.activityInfo.name);

来源:Android share intent Twitter: How to share only by Tweet or Direct Message?


9
谢谢,还有一个小提示:为了保留从 getPackageManager().queryIntentActivities(shareIntent, 0); 获取到的应用程序顺序,请调用 Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size()-1), "Select app to share");,选择器会将其初始化的意图添加到 extra_initial_intents 的末尾 :) - Berťák
1
做了更多的研究,发现使用targetedShareIntent.setPackage(packageName)(http://developer.android.com/reference/android/content/Intent.html#getPackage())非常重要,以便仅允许来自特定包的意图(这将是从目标共享意图中选择的一个)。然后附加自定义意图。为了进一步改进此代码,可以使用`targetedShareIntent.setClassName(packageName,className)`进行更详细的指定。 - Makibo
8
我为一个可重复使用的解决方案创建了一个要点,基于这个答案 https://gist.github.com/mediavrog/5625602。 - Makibo
2
我无法获取/导入StringUtils @gumbercules - Crishnan Kandada
1
@Makibo 我也是用同样的方法来防止进一步的选择器类型意图。但似乎只能获取应用程序名称,而不能获取活动名称。如果有两个在同一个应用程序中处理意图类型的活动。只有图标不同。标签是相同的。你有什么想法来解决这个问题吗?提前感谢。 - shiami
显示剩余4条评论

26
选择器只有一个额外的参数,即 Intent.EXTRA_INITIAL_INTENTS。它的描述如下:

Parcelable[] 是 Intent 或 LabeledIntent 对象,是通过 putExtra(String, Parcelable[]) 设置的,用于在使用 ACTION_CHOOSER 呈现给用户时,将其他活动放在选择列表前面。

我在 Android 源代码中没有找到排除其他活动的方法,因此似乎无法使用选择器实现您想要做的操作。 编辑: 这真的很容易找到。只需检查 ChooserActivityResolverActivity 源代码。这些类非常小。

看起来我所寻找的不可能实现,所以你是最接近(也是唯一)的。 - Kon
这是正确的答案。经过尝试和阅读文档,可以确定无法从列表中删除项目。 - Thomas Dignan
没错。我已经尝试了各种方法,但都没有结果。最后,我不得不自定义我的“createChooser()”方法,就像Intent.createChooser()一样。 - cmoaciopm
1
定制解决方案,此链接 -> https://dev59.com/jWkw5IYBdhLWcg3wqMY1 - deadfish

7

我做了一个小修改,使得你可以按名称列出想要分享的应用程序。这几乎与你已经发布的内容相同,只是添加了按名称分享应用程序的功能。

String[] nameOfAppsToShareWith = new String[] { "facebook", "twitter", "gmail" };
String[] blacklist = new String[]{"com.any.package", "net.other.package"};
// your share intent
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "some text");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "a subject");
// ... anything else you want to add invoke custom chooser
startActivity(generateCustomChooserIntent(intent, blacklist));

private Intent generateCustomChooserIntent(Intent prototype,
            String[] forbiddenChoices)
    {
        List<Intent> targetedShareIntents = new ArrayList<Intent>();
        List<HashMap<String, String>> intentMetaInfo = new ArrayList<HashMap<String, String>>();
        Intent chooserIntent;

        Intent dummy = new Intent(prototype.getAction());
        dummy.setType(prototype.getType());
        List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(dummy,0);

        if (!resInfo.isEmpty())
        {
            for (ResolveInfo resolveInfo : resInfo)
            {
                if (resolveInfo.activityInfo == null
                        || Arrays.asList(forbiddenChoices).contains(
                                resolveInfo.activityInfo.packageName))
                    continue;
                //Get all the posible sharers
                HashMap<String, String> info = new HashMap<String, String>();
                info.put("packageName", resolveInfo.activityInfo.packageName);
                info.put("className", resolveInfo.activityInfo.name);
                String appName = String.valueOf(resolveInfo.activityInfo
                        .loadLabel(getPackageManager()));
                info.put("simpleName", appName);
                //Add only what we want
                if (Arrays.asList(nameOfAppsToShareWith).contains(
                        appName.toLowerCase()))
                {
                    intentMetaInfo.add(info);
                }
            }

            if (!intentMetaInfo.isEmpty())
            {
                // sorting for nice readability
                Collections.sort(intentMetaInfo,
                        new Comparator<HashMap<String, String>>()
                        {
                            @Override public int compare(
                                    HashMap<String, String> map,
                                    HashMap<String, String> map2)
                            {
                                return map.get("simpleName").compareTo(
                                        map2.get("simpleName"));
                            }
                        });

                // create the custom intent list
                for (HashMap<String, String> metaInfo : intentMetaInfo)
                {
                    Intent targetedShareIntent = (Intent) prototype.clone();
                    targetedShareIntent.setPackage(metaInfo.get("packageName"));
                    targetedShareIntent.setClassName(
                            metaInfo.get("packageName"),
                            metaInfo.get("className"));
                    targetedShareIntents.add(targetedShareIntent);
                }
                String shareVia = getString(R.string.offer_share_via);
                String shareTitle = shareVia.substring(0, 1).toUpperCase()
                        + shareVia.substring(1);
                chooserIntent = Intent.createChooser(targetedShareIntents
                        .remove(targetedShareIntents.size() - 1), shareTitle);
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                        targetedShareIntents.toArray(new Parcelable[] {}));
                return chooserIntent;
            }
        }

        return Intent.createChooser(prototype,
                getString(R.string.offer_share_via));
    }

这个解决方案和Makibo发布的几乎一样,只是添加了一个小功能,以便通过添加名称来轻松选择想要共享的应用程序,这样即使它们更改包名称或类似的东西,你也不会遇到任何问题。只要它们不更改名称。


1
这个代码可以运行,但是在 Android 6 上会出现应用程序名称为空的情况(例如 "Gogobot"),并且还会导致显示的项目网格中出现空单元格(在我的 Nexus 5 上,最后一行之前的倒数第二行会出现一个空单元格)。 - android developer

6

我实现了自定义打开选择器。

特点:

  • 应用按字母顺序排序
  • 处理用户定义的默认应用程序
  • 处理没有应用程序的情况
  • 过滤自己

public static Intent createOpenFileIntent(Context context, String pathToFile) {
    File file = new File(pathToFile);
    String extension = extensionFromName(file.getName());
    String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
    if (mimeType == null) {
        //If android doesn't know extension we can check our own list.
        mimeType = KNOWN_MIME_TYPES.get(DataViewHelper.extensionFromName(file.getName()));
    }

    Intent openIntent = new Intent();
    openIntent.setAction(android.content.Intent.ACTION_VIEW);
    openIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    openIntent.setDataAndType(Uri.fromFile(file), mimeType);

    // 1. Check if there is a default app opener for this type of content.
    final PackageManager packageManager = context.getPackageManager();
    ResolveInfo defaultAppInfo = packageManager.resolveActivity(openIntent, PackageManager.MATCH_DEFAULT_ONLY);
    if (!defaultAppInfo.activityInfo.name.endsWith("ResolverActivity")) {
        return openIntent;
    }

    // 2. Retrieve all apps for our intent. If there are no apps - return usual already created intent.
    List<Intent> targetedOpenIntents = new ArrayList<Intent>();
    List<ResolveInfo> appInfoList = packageManager.queryIntentActivities(openIntent, PackageManager.MATCH_DEFAULT_ONLY);
    if (appInfoList.isEmpty()) {
        return openIntent;
    }

    // 3. Sort in alphabetical order, filter itself and create intent with the rest of the apps.
    Collections.sort(appInfoList, new Comparator<ResolveInfo>() {
        @Override
        public int compare(ResolveInfo first, ResolveInfo second) {
            String firstName = packageManager.getApplicationLabel(first.activityInfo.applicationInfo).toString();
            String secondName = packageManager.getApplicationLabel(second.activityInfo.applicationInfo).toString();
            return firstName.compareToIgnoreCase(secondName);
        }
    });
    for (ResolveInfo appInfo : appInfoList) {
        String packageName = appInfo.activityInfo.packageName;
        if (packageName.equals(context.getPackageName())) {
            continue;
        }

        Intent targetedOpenIntent = new Intent(android.content.Intent.ACTION_VIEW)
                .setDataAndType(Uri.fromFile(file), mimeType)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .setPackage(packageName);
        targetedOpenIntents.add(targetedOpenIntent);
    }
    Intent chooserIntent = Intent.createChooser(targetedOpenIntents.remove(targetedOpenIntents.size() - 1), context.getString(R.string.context_menu_open_in))
            .putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedOpenIntents.toArray(new Parcelable[] {}));

    return chooserIntent;
}

public static String extensionFromName(String fileName) {
    int dotPosition = fileName.lastIndexOf('.');

    // If extension not present or empty
    if (dotPosition == -1 || dotPosition == fileName.length() - 1) {
        return "";
    } else {
        return fileName.substring(dotPosition + 1).toLowerCase(Locale.getDefault());
    }
}

3
为了获得与操作选择器显示的标签相同的标签,您应该使用ResolveInfo.loadLabel(packageManager)而不是packageManager.getApplicationLabel(ResolveInfo.activityInfo.applicationInfo)。结果并非始终相同。(在第三步排序时覆盖compare()时使用此方法)。例如,对于Dropbox,loadLabel()将返回“添加到Dropbox”,而getApplicationLabel()将返回“Dropbox”。 - Pooks

1
这个解决方案基于这篇文章https://rogerkeays.com/how-to-remove-the-facebook-android-sharing-intent
// get available share intents
final String packageToBeFiltered = "com.example.com"
List<Intent> targets = new ArrayList<Intent>();
Intent template = new Intent(Intent.ACTION_SEND);
template.setType("text/plain");
List<ResolveInfo> candidates = this.getPackageManager().queryIntentActivities(template, 0);

// filter package here
for (ResolveInfo candidate : candidates) {
    String packageName = candidate.activityInfo.packageName;
    if (!packageName.equals(packageToBeFiltered)) {
    Intent target = new Intent(android.content.Intent.ACTION_SEND);
    target.setType("text/plain");
    target.putExtra(Intent.EXTRA_TEXT, "Text to share"));
    target.setPackage(packageName);
    targets.add(target);
   }
}
if (!targets.isEmpty()) {
Intent chooser = Intent.createChooser(targets.get(0), "Share dialog title goes here"));
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targets.toArray(new Parcelable[]{}));
startActivity(chooser);
}

1
我试图使用ShareActionProvider进行相同的操作。 gumbercules帖子中的方法与ShareActionProvider不兼容,因此我复制并修改了与ShareActionProvider相关的类,以允许自定义筛选ShareActionProvider建议。
唯一的更改是ActivityChooserModel.loadActivitiesIfNeeded()方法。在我的情况下,我想要过滤掉YouTube包。
public static final String YOUTUBE_PACKAGE = "com.google.android.youtube";

private boolean loadActivitiesIfNeeded() {
    if (mReloadActivities && mIntent != null) {
        mReloadActivities = false;
        mActivities.clear();
        List<ResolveInfo> resolveInfos = mContext.getPackageManager()
                .queryIntentActivities(mIntent, 0);
        final int resolveInfoCount = resolveInfos.size();
        for (int i = 0; i < resolveInfoCount; i++) {
            ResolveInfo resolveInfo = resolveInfos.get(i);
            // Filter out the YouTube package from the suggestions list
            if (!resolveInfo.activityInfo.packageName.equals(YOUTUBE_PACKAGE)) {
                mActivities.add(new ActivityResolveInfo(resolveInfo));
            }
        }
        return true;
    }
    return false;
}

https://github.com/danghiskhan/FilteredShareActionProvider上查看相关编程代码。


0
对于Kotlin用户来说,如果你的目标是API 24,那么这段代码对我来说是有效的。你应该使用Intent.EXTRA_EXCLUDE_COMPONENTS而不是默认情况下被忽略的Intent.EXTRA_INITIAL_INTENTS
    @RequiresApi(Build.VERSION_CODES.N)
private fun goToEmailFilteredApps(email: String) {

    val intent = Intent(Intent.ACTION_SEND)
    intent.setTypeAndNormalize("message/rfc822")
    intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))

    val chooser = Intent.createChooser(intent, "Send email")

  val emailAppsPackageNames = arrayListOf(
        "com.google.android.gm",                // Gmail
        "com.microsoft.office.outlook",         // Outlook
        "com.yahoo.mobile.client.android.mail", // Yahoo Mail
        "pl.onet.poczta",                       // Onet Mail
        "pl.interia.poczta_next",               // Interia Mail
        "pl.wp.pocztao2",                       // o2 Mail
        "pl.wp.wppoczta",                       // WP  Mail
    )
    val targets = ArrayList<ComponentName>()
    try {
        val queries = requireActivity().packageManager.queryIntentActivities(intent, 0)
        for (candidate in queries) {
            val packageName = candidate.activityInfo.packageName
            if (!emailAppsPackageNames.contains(packageName.lowercase())) {
                targets.add(ComponentName(packageName, candidate.activityInfo.name))
            }
        }
        chooser.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, targets.toTypedArray())
        startActivity(chooser)
    } catch (_: ActivityNotFoundException) {
    }
}

这是结果 输入图像描述

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接