Android activity exported属性理解

背景

这么久了,我自己看来对此属性的理解有点小偏差,当然不是表面上的理解误差,而是涉及到具体实现的细节。这里先贴下官方关于此属性的解释:

android:exported
This element sets whether the activity can be launched by components of other applications — "true" if it can be, and "false" if not. If "false", the activity can be launched only by components of the same application or applications with the same user ID.
If you are using intent filters, you should not set this element "false". If you do so, and an app tries to call the activity, system throws an ActivityNotFoundException. Instead, you should prevent other apps from calling the activity by not setting intent filters for it.

If you do not have intent filters, the default value for this element is "false". If you set the element "true", the activity is accessible to any app that knows its exact class name, but does not resolve when the system tries to match an implicit intent.

This attribute is not the only way to limit an activity's exposure to other applications. You can also use a permission to limit the external entities that can invoke the activity (see the permission attribute).

这段文字说明,值得多读几遍!!!

由于我们团队的关系,我们开发的模块经常需要集成到多个app中,而我们不想为某个app单独维护一份代码,即我们的开发中,所有的宿主app用的都是同一套代码。比如就存在类似这样的代码:

<activity
            android:name=".SubActivity"
            android:configChanges="orientation|keyboardHidden"
            android:exported="false" // 注意这行代码!!!
            android:screenOrientation="portrait"
            android:windowSoftInputMode="stateHidden">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="mlpf" />
                <data android:host="sub" />
            </intent-filter>
        </activity>

这样在宿主app里,通过打开mlpf://sub这样的短链就能轻松地来到我们模块的SubActivity。注意这里android:exported=false的设置,因为如果不设置的话,根据Android的规则只要有intent-filter存在,那么exported就是true,即对外暴露的;而这里我们显然不希望是对外暴露的,因为如果这样的话,当安装了多个集成了我们模块的App时,当要打开这样的短链请求时系统就会弹出选择框让用户选择在哪个app里打开,这当然不是我们期望的。

当同一个设备上装了我们的多个app的时候,在8.0之前都是ok的,即A app里的SubActivity和B app里的SubActivity互相是没任何关系的,也是互相看不到对方的,这是我们对exported=false的认识;直到上周某天晚上快要下班了,QA同学拿着升级到8.0的Nexus 6P跟我说,你看你们这个页面跳不过去了,还弹出了个讨厌的没有应用可执行此操作的提示,我当时也是一脸懵逼啊,但心里已经有种不祥的预感,看起来像是google改出来的bug。

我接过设备,点击了几下,确保能复现,然后连着电脑,看了下adb logcat关于ActivityManager相关的输出,果然我们这个intent没有找到对应的cmp(component),而是到了系统的ResolverActivity,ResolverAct大家都知道,当系统找到了多个目标或者没目标时会弹出它提醒用户。这就有点奇怪了,同样的case在7.x的设备上就是好的,虽然行为上也是到了ResolverAct,但ResolverAct内部最终还是导到了本app内部的SubActivity,最终正确调起了。

解惑

有一点我们需要知道,即当我们通过Intent打开act的时候,系统内部会调用
Intent.resolveActivity(pm),其内部又会接着调用PackageManager#resolveActivity。另外你也可以调用PackageManager#queryIntentActivities来查看某个intent究竟可以被哪个act处理。有一点需要特别注意的是,这些方法会考察设备上所有安装的app里的activity,即使是那些被显式标记成了exported=false的act,这就是我上文说到的理解偏差,这让我很惊讶。因为我以前的认识中,既然标记了不对外暴露,那么这些act也不应该被找到才对,但很可惜,看起来Android的实现不是这样的,关于这点,可以参考以下问题:Android queryintentactivities.

7.x(包括)之前虽然也能查到别的app里exported=false的act(这个行为看起来一直都有),但最终会正确打开匹配到的本app里exported=false的act,但在8.0上这个行为break掉了,直接变成了上文提到的“没有应用可执行此操作”,真是一个忧伤的故事。

8.0解决办法

关于8.0的这个问题,AOSP上也有人报了bug:intent有多个match时无法正确跳转。不过看起来仅仅是个没多少关注的P3bug,而且我手头的5x升级到了8.1.0,此问题依然存在,看来指望google修复希望不大。还好我们也有办法处理下,看下Intent.setPackage方法,如下:

/**
     * (Usually optional) Set an explicit application package name that limits
     * the components this Intent will resolve to.  If left to the default
     * value of null, all components in all applications will considered.
     * If non-null, the Intent can only match the components in the given
     * application package.
     *
     * @param packageName The name of the application package to handle the
     * intent, or null to allow any application package.
     *
     * @return Returns the same Intent object, for chaining multiple calls
     * into a single statement.
     *
     * @see #getPackage
     * @see #resolveActivity
     */
    public Intent setPackage(String packageName) {
        if (packageName != null && mSelector != null) {
            throw new IllegalArgumentException(
                    "Can't set package name when selector is already set");
        }
        mPackage = packageName;
        return this;
    }

我们面临的主要问题就是系统API在startActivity的过程中查到了别的app里面的非暴露act,这个方法看起来刚好可以将系统的这个查找行为局限在本app内,所以我们的fix如下:

    if (Build.VERSION.SDK_INT >= 26) {
        intent.setPackage(mContext.getPackageName());
    }

最后,关于exported=false的实现,我个人的看法是应该再提早些,直接一开始在匹配的过程中就找不到这样的act,而不是一股脑全找到(导致本来就1个target满足,结果找了多个出来),等到最后要打开了,看下exported是false,才弹个无权限的错误!!!之前魅族更新了次系统后也出过这问题,弹出让用户选,结果选了之后又告诉用户无权限(因为实际是exported=false的activity)。就像在实现某个方法的时候,有些前置条件不满足,我们应该尽早return,而不是埋头做了很多工作后,才检查一些必要条件,发现不对了才退出,fail fast常常是很好用的策略。

ps:实在是没明白google这里的实现为啥要找到这些实际上private的act,看起来完全是在做无用功啊,反正怎么着都不可能打开,你把它找出来干啥呢!!!有想法的同学可以留言交流下,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270