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閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375