• 如何自定义Intent.createChooser的显示结果


    Intentandroid核心的概念之一,Intent为android系统提供了真正的开放。android的姿态是开放了,但却没有做到位。

    拿“发邮件”这一功能来说,为了使用Intent机制来发送邮件,调用方需要知道Intent的规则,如action、uri、MIMEtype和category。但是在哪里能找到这些规则呢?官方文档里没有,还好能求助google,然后就发现StackOverflow有无数的人提过或者困惑于这个问题。从google搜索结果里没有看到导向官方文档的链接,由此大胆推测,官方文档确实没有相关的说明。

    再者,从编写mail应用程序的角度来说,他们也迷茫-到底要支持那些Intent呢?我的Intent-filter到底要怎么写?难道是android team的geek精神作祟,一定要广大开发者分析gmail的apk才甘心?忍不住吐槽这直接导致了用户体验不统一,程序猿编程不统一

    以上感受均来自我的一个非常合理的需求 - 发送多个附件的邮件

    开始,我是这么做的

        Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{emailTo});
        emailIntent.putExtra(Intent.EXTRA_CC, new String[]{emailCC});
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject); 
        emailIntent.putExtra(Intent.EXTRA_TEXT, emailText);
        ArrayList<Uri> uris = new ArrayList<Uri>();
        for (String file : filePaths) {
            File fileIn = new File(file);
            Uri u = Uri.fromFile(fileIn);
            uris.add(u);
        }
        emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
        context.startActivity(Intent.createChooser(emailIntent, "Send mail..."));

    结果除了我要的mail app,各种打酱油的Bluetooth、Evernote等等都出现在了选择列表中,选择太多对用户不是好事儿。这不是我想要的
    为了限定所列出的选择全部都是mail application,我指定了Uri

        Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
        Uri uri = Uri.parse("mailto:"+emailTo+"?subject="+subject+"&body="+emailText);
        emailIntent.setData(uri);
        for (String file : filePaths) {
            File fileIn = new File(file);
            Uri u = Uri.fromFile(fileIn);
            uris.add(u);
        }
        emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
        context.startActivity(Intent.createChooser(emailIntent, "Send mail..."));
    

    此时chooser界面显示只有mail application了,但选择gmail时,程序崩溃;选择系统默认的邮件程序同样崩溃;只有K9 mail能够正常运行。查看了android源代码中mail application的源代码,发现在Uri的schema是mailto时,确实不支持附件;而K9源代码却处理了这种情况。
    于是决定不 使用由Intent.createChooser启动的界面其实就是ChooserActivity.java,而是自定义chooser界面。做了才发现,如果尽可能保持与ChooserActivity风格一致,工作量非常之大最终放弃,去看看Intent还有没有什么可以挖掘的。

    无心插柳柳成荫,发现Intent有个EXTRA_INITIAL_INTENTS文档中这样描述

    A Parcelable[] of Intent or LabeledIntent objects as set with 
    putExtra(String, Parcelable[]) of additional activities
     to place a the front of the list of choices, 
    when shown to the user with a ACTION_CHOOSER. 

    再看看ChooserActivity和ResolverActivity的源代码,EXTRA_INITIAL_INTENTS指定的Intent会“有条件地”显示在选择界面上。什么条件呢?那就是一定要存在能够处理Intent.createIntent函数第一个参数Intent的Activity。于是代码如下

        Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{emailTo});
        emailIntent.putExtra(Intent.EXTRA_CC, new String[]{emailCC});
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject); 
        emailIntent.putExtra(Intent.EXTRA_TEXT, emailText);
        ArrayList<Uri> uris = new ArrayList<Uri>();
        for (String file : filePaths) {
            File fileIn = new File(file);
            Uri u = Uri.fromFile(fileIn);
            uris.add(u);
        }
        emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
    
        PackageManager pm = context.getPackageManager();
        Intent sendMultipleIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        List<ResolveInfo> aList = pm.queryIntentActivities(sendMultipleIntent, PackageManager.MATCH_DEFAULT_ONLY);
        Intent mailIntent = new Intent(Intent.ACTION_SEND);
        mailIntent.setData(Uri.fromParts("mailto", "", null));
        List<ResolveInfo> bList = pm.queryIntentActivities(mailIntent, PackageManager.MATCH_DEFAULT_ONLY);
        //TODO find out all target ResolbeInfo using aList and bList
        List<ResolveInfo> targets = ...;
        ArrayList<Intent> targetIntents = new ArrayList<Intent>();
        for (ResolveInfo info : targets) {
            ActivityInfo ai = info.activityInfo;
            Intent intent = new Intent(emailIntent);
            intent.setPackageName(ai.packageName);
            intent.setClass(ai.packageName, ai.name);
            targetIntents.add(intent);
        }
        Intent chooser = Intent.createChooser(targetIntents.remove(0), "Send mail...");
        chooser.putParcelableArrayListExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents);
        context.startActivity(chooser);

    最终展现给用户的选择包括,符合createChooser第一个参数的应用(其实是指定了packageName和className的Intent,以及由EXTRA_INITIAL_INTENTS指定的应用

    注意,这里没有使用Intent.setComponent来明确指定要启动的Activity,而是通过setPackageName和setClass来指定。这是因为,在createChooser处理过程中,第一个参数intent指定的component会ResolverActivity中强制被设置为null这样的结果是什么呢当component被设置为null后,targetIntents.remove(0)其实就是emailIntent,那么createChooser会显示所有满足emailIntent的应用,然后在加上由EXTRA_INITIAL_INTENTS指定的应用。你会发现选择界面中出现了重复的应用。

  • 相关阅读:
    [小北De编程手记] : Selenium For C# 教程目录
    [小北De编程手记] [Lesson 02] AutoFramework构建 之 Page Objects
    [小北De编程手记] Lesson 01
    [小北De编程手记] : Lesson 06 玩转 xUnit.Net 之 定义自己的FactAttribute
    [小北De编程手记] : Lesson 05 玩转 xUnit.Net 之 从Assert谈UT框架实践
    [小北De编程手记] : Lesson 04 玩转 xUnit.Net 之 Fixture(下)
    [小北De编程手记] : Lesson 03 玩转 xUnit.Net 之 Fixture(上)
    [小北De编程手记] : Lesson 02 玩转 xUnit.Net 之 基本UnitTest & 数据驱动
    [小北De编程手记] : Lesson 01 玩转 xUnit.Net 之 概述
    [小北De编程手记] : Lesson 08
  • 原文地址:https://www.cnblogs.com/james1207/p/3323128.html
Copyright © 2020-2023  润新知