Android多用戶下數(shù)據(jù)隔離方案與常見問題解決思路

Android多用戶下數(shù)據(jù)隔離方案與常見問題解決思路##

1.Android 多用戶概述###

Android從4.2開始支持多用戶模式鱼冀,不同的用戶運(yùn)行在不同的用戶空間糕非,相關(guān)的系統(tǒng)設(shè)置是各不相同而且不同用戶安裝的應(yīng)用和應(yīng)用數(shù)據(jù)也是不一樣的,但是系統(tǒng)中和硬件相關(guān)的設(shè)計(jì)則是共用的站叼。
Android的多用戶實(shí)現(xiàn)主要通過UserManagerService(UMS)對(duì)多用戶進(jìn)行創(chuàng)建娃兽、刪除等操作。

相關(guān)的引入類是

framework/base/services/core/java/com/android/server/pm/UserManagerService.java
framework/base/core/java/android/os/UserManager.java
framework/base/core/java/android/content/pm/UserInfo.java
framework/base/core/java/android/os/UserHandle.java

我們知道尽楔,Linux有原生的用戶管理系統(tǒng)投储。作為一個(gè)多用戶多任務(wù)的分時(shí)操作系統(tǒng),它通過uid和gid來進(jìn)行用戶管理阔馋。
但是Android將Linux的多用戶管理玛荞,移植到了apk管理上。Android給每個(gè)APK進(jìn)程分配一個(gè)單獨(dú)的空間,manifest中的userid就是對(duì)應(yīng)一個(gè)分配的Linux用戶ID呕寝,并且為它創(chuàng)建一個(gè)沙箱勋眯,以防止影響其他應(yīng)用程序(或者其他應(yīng)用程序影響它)。

android用戶空間

用戶ID 在應(yīng)用程序安裝到設(shè)備中時(shí)被分配下梢,并且在這個(gè)設(shè)備中保持它的永久性客蹋。也就是說,原生的Linux用戶id孽江,被用在了apk做自己的標(biāo)識(shí)讶坯。所以,需要有管理當(dāng)前所有app的用戶的概念竟坛,Android引入了UserHandle闽巩。

2.UserHandle,userId, uid, gid###

Android對(duì)于不同UserHandler担汤,認(rèn)為數(shù)據(jù)是相互隔離的涎跨。有不同的用戶數(shù)據(jù),權(quán)限等崭歧。每個(gè)UserHandle有它的userId隅很,主用戶是0。默認(rèn)新創(chuàng)建一個(gè)用戶率碾,userId加1叔营。

在 Android 上,一個(gè)用戶 UID 標(biāo)示一個(gè)應(yīng)用程序所宰。應(yīng)用程序在安裝時(shí)被分配用戶 UID绒尊,應(yīng)用程序在設(shè)備上的存續(xù)期間內(nèi),用戶 UID 保持不變仔粥。對(duì)于普通的應(yīng)用程序婴谱,GID即等于UID蟹但。

GIDS 是由框架在 Application 安裝過程中生成,與 Application 申請的具體權(quán)限相關(guān)谭羔。 如果 Application 申請的相應(yīng)的 permission 被 granted 华糖,而且有對(duì)應(yīng)的GIDS, 那么 這個(gè)Application 的 gids 中將 包含這個(gè) gids瘟裸。記住權(quán)限(GIDS)是關(guān)于允許或限制應(yīng)用程序(而不是用戶)訪問設(shè)備資源客叉。

轉(zhuǎn)換關(guān)系為:

uid = userId * 100000 + appId

3.多用戶下數(shù)據(jù)隔離架構(gòu)###

不同用戶下,應(yīng)用的數(shù)據(jù)需要隔離话告。所以Android通過不同的數(shù)據(jù)路徑來達(dá)成數(shù)據(jù)隔離的效果兼搏。
普通應(yīng)用數(shù)據(jù)的保存路徑為/data/user/userId/packageName
以日歷的數(shù)據(jù)庫的數(shù)據(jù)為例,假設(shè)手機(jī)有userId 0userId 11的數(shù)據(jù)超棺,那么它不同用戶下的存儲(chǔ)路徑為:

user11:/data/user/11/com.android.providers.calendar/databases
user 0:/data/user/0/com.android.providers.calendar/databases

常規(guī)的數(shù)據(jù)差異問題向族,可以導(dǎo)出對(duì)應(yīng)路徑下的數(shù)據(jù)進(jìn)行分析呵燕。

4.Settings多用戶下數(shù)據(jù)隔離架構(gòu)與常見問題###

作為Android設(shè)置項(xiàng)的數(shù)據(jù)庫棠绘,android.provider.Settings是被其他應(yīng)用使用得最多,問題也出現(xiàn)最多的數(shù)據(jù)庫再扭。
android.provider.Settings的操作類有三個(gè)氧苍,System,Secure,Global。他們通過key-value的方式進(jìn)行保存泛范,user0對(duì)應(yīng)的存儲(chǔ)實(shí)體文件為:

/data/system/users/0/settings_system.xml
/data/system/users/0/settings_secure.xml
/data/system/users/0/settings_global.xml

user11對(duì)應(yīng)的存儲(chǔ)實(shí)體文件為:

/data/system/users/11/settings_system.xml
/data/system/users/11/settings_secure.xml

4.1全局變量多用戶隔離問題####

Global作為全局的變量表让虐,多個(gè)用戶只存在一個(gè)文件。也就意味著罢荡,一個(gè)用戶修改會(huì)影響到其他用戶赡突。如果要考慮多用戶下隔離global的開關(guān)項(xiàng),而不能影響原有的業(yè)務(wù)邏輯区赵。只能是考慮在System惭缰,或是Secure下新增key保存當(dāng)前用戶下Global的開關(guān)狀態(tài)。然后在用戶切換的時(shí)候笼才,用System/Secure的狀態(tài)更新Global的狀態(tài)漱受。

用戶切換時(shí)系統(tǒng)有原生的廣播發(fā)出來,需要注意的是骡送,這個(gè)廣播只能是動(dòng)態(tài)注冊的receiver能夠接收昂羡。所以需要考慮數(shù)據(jù)場景。像設(shè)置就不適宜做用戶切換時(shí)修改數(shù)據(jù)的操作摔踱。只能由其他app進(jìn)行操作虐先。

    /**
     * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has
     * the userHandle of the user to become the current one. This is only sent to
     * registered receivers, not manifest receivers.  It is sent to all running users.
     * You must hold
     * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
     * @hide
     */
    public static final String ACTION_USER_SWITCHED =
            "android.intent.action.USER_SWITCHED";

4.2當(dāng)前用戶數(shù)據(jù)異常問題####

從其他app反饋給設(shè)置這邊最多的,就是讀取到的值不對(duì)派敷,需要設(shè)置進(jìn)行分析蛹批。他們使用方式為getInt(cr,name,def);

/**
         * Convenience function for retrieving a single secure settings value
         * as an integer.  Note that internally setting values are always
         * stored as strings; this function converts the string to an integer
         * for you.  The default value will be returned if the setting is
         * not defined or not an integer.
         *
         * @param cr The ContentResolver to access.
         * @param name The name of the setting to retrieve.
         * @param def Value to return if the setting is not defined.
         *
         * @return The setting's current value, or 'def' if it is not defined
         * or not a valid integer.
         */
        public static int getInt(ContentResolver cr, String name, int def) {
            return getIntForUser(cr, name, def, cr.getUserId());
        }

        /** @hide */
        public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
            if (LOCATION_MODE.equals(name)) {
                // Map from to underlying location provider storage API to location mode
                return getLocationModeForUser(cr, userHandle);
            }
            String v = getStringForUser(cr, name, userHandle);
            try {
                return v != null ? Integer.parseInt(v) : def;
            } catch (NumberFormatException e) {
                return def;
            }
        }

從上面代碼可以看到,這個(gè)方法會(huì)跳轉(zhuǎn)到getIntForUser,此時(shí)的userHandler是通過ContentResolver的getUserId獲取到的。但是常駐進(jìn)程在用戶切換的時(shí)候般眉,上下文是否正常切換了赵?如systemUI,就將處理邏輯都在user0創(chuàng)建的進(jìn)程里處理甸赃。用戶切換時(shí)柿汛,并沒有切換到其他用戶創(chuàng)建的進(jìn)程,context也就不會(huì)切換埠对。那么在其他用戶(如當(dāng)前處于userId 11)時(shí)络断,狀態(tài)欄一直獲取到user0的值,自然導(dǎo)致異常项玛。那么對(duì)應(yīng)的解決方案貌笨,可以使用

    getIntForUser(cr, name, def, ActivityManager.getCurrentUser());

ActivityManager.getCurrentUser()通過IPC獲取當(dāng)前前臺(tái)活躍的userId。這樣的話可以獲取到前臺(tái)用戶的值襟沮,而不是一直獲取user 0的锥惋。

4.3Android O上非主用戶監(jiān)聽Global失效問題####

在android O上,其他用戶監(jiān)聽Global里面值的修改开伏,發(fā)現(xiàn)ContentObserver的onChange()不會(huì)被回調(diào)膀跌。

SettingsProvider.call()
->insertGlobalSetting(args...)
->mutateGlobalSetting(args...)

在做global值修改的時(shí)候,可以看到insertSettingLocked第二個(gè)參數(shù)固灵,userId的信息被USER_SYSTEM(也就是user 0)擦除了捅伤。等到修改結(jié)束notifyForSettingsChange的時(shí)候,無論是哪個(gè)用戶修改了global巫玻,onChange都被傳遞給了user 0丛忆。

    mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
                            UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
                            callingPkg, forceNotify, CRITICAL_GLOBAL_SETTINGS); 

針對(duì)這種情況,需要將修改同步給當(dāng)前用戶仍秤。

            //#ifdef VENDOR_EDIT
            //xionglong@Apps.Settings, 2019/03/07, Add for Ali Dual System ,to solve Global's ContentObserver not transfer in work mode  . . .
            if (isGlobalSettingsKey(key)) {
                final long token = Binder.clearCallingIdentity();
                try {
                    notifyGlobalSettingChangeForRunningUsers(key, name);
                } finally {
                    Binder.restoreCallingIdentity(token);
                }
            } 
            //#endif /* VENDOR_EDIT */

        //#ifdef VENDOR_EDIT
        //xionglong@Apps.Settings, 2019/03/07, Add for Ali Dual System ,to solve Global's ContentObserver not transfer in work mode  . . .
        private void notifyGlobalSettingChangeForRunningUsers(int key, String name) {
            // Important: No need to update generation for each user as there
            // is a singleton generation entry for the global settings which
            // is already incremented be the caller.
            final Uri uri = getNotificationUriFor(key, name);
            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying*/ true);
            for (int i = 0; i < users.size(); i++) {
                final int userId = users.get(i).id;
                if (mUserManager.isUserRunning(UserHandle.of(userId))) {
                    mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
                            userId, 0, uri).sendToTarget();
                }
            }
        }
        //#endif /* VENDOR_EDIT */

5.多用戶下界面啟動(dòng)異常問題###

在多用戶場景下熄诡,有出現(xiàn)主用戶界面正常切換,但是在其他用戶下缺拉不起對(duì)應(yīng)Activity的問題徒扶。此類問題分兩種:

  1. 拉起一個(gè)常駐進(jìn)程的界面粮彤。如嘗試打開com.android.phone的一個(gè)界面。因?yàn)閜hone進(jìn)程并不會(huì)在其他用戶創(chuàng)建的時(shí)候創(chuàng)建一個(gè)新的進(jìn)程姜骡。所以在其他用戶活躍的時(shí)候导坟,隸屬于user0的phone進(jìn)程的界面無法顯示在上層。相應(yīng)的解決方案為:
    (1).在Activity里加上android:showForAllUsers="true";
    (2).用startActivityAsUser的方式打開界面圈澈。(注意system app和權(quán)限)
    /**
     * Version of {@link #startActivity(Intent)} that allows you to specify the
     * user the activity will be started for.  This is not available to applications
     * that are not pre-installed on the system image.
     * @param intent The description of the activity to start.
     * @param user The UserHandle of the user to start this activity for.
     * @throws ActivityNotFoundException &nbsp;
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
    public void startActivityAsUser(@RequiresPermission Intent intent, UserHandle user) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

需要注意的是惫周,此時(shí)用user0打開界面,界面里的數(shù)據(jù)都是user0的康栈。在其他用戶界面展示user0的界面递递,需要和產(chǎn)品確認(rèn)是否滿足要求喷橙。

  1. 其他用戶拉起界面失敗。主用戶下無異常登舞,其他用戶異常的話贰逾,需要確認(rèn)是否是拉起正確userId的界面。如上面有描述到的菠秒,systemUI并沒有切換到對(duì)應(yīng)的用戶疙剑,那么它去打開界面是,AMS會(huì)嘗試用user0去打開界面践叠。前臺(tái)活躍的是其他user,所以打開失敗言缤。如關(guān)鍵字ActivityManager: START u0 {flg=0x10808000...
    此時(shí)可以考慮用startActivityAsUser。
    startActivityAsUser需要傳入U(xiǎn)serHandle對(duì)象禁灼」苄可以通過
    /** @hide */
    @SystemApi
    public static UserHandle of(@UserIdInt int userId) {
        return userId == USER_SYSTEM ? SYSTEM : new UserHandle(userId);
    }

獲取到。

6.Context切換問題###

如果有一些場景弄捕,需要用到其他userId的context僻孝,該怎么辦呢?
有一個(gè)方法察藐,可以獲取到對(duì)應(yīng)userId的Context皮璧。

    /**
     * Similar to {@link #createPackageContext(String, int)}, but with a
     * different {@link UserHandle}. For example, {@link #getContentResolver()}
     * will open any {@link Uri} as the given user.
     *
     * @hide
     */
    @SystemApi
    public Context createPackageContextAsUser(
            String packageName, @CreatePackageOptions int flags, UserHandle user)
            throws PackageManager.NameNotFoundException {
        if (Build.IS_ENG) {
            throw new IllegalStateException("createPackageContextAsUser not overridden!");
        }
        return this;
    }

這個(gè)Context沒有Theme舟扎,但是應(yīng)對(duì)一些數(shù)據(jù)場景分飞,如Service,Broadcast,ContentResolver這種場景,再試用不過了睹限。

6.多用戶數(shù)據(jù)問題的常見分析解決思路###

1.確定是否是數(shù)據(jù)不一致導(dǎo)致問題譬猫。從實(shí)際數(shù)據(jù)的存儲(chǔ)位置和代碼里獲取的數(shù)據(jù)進(jìn)行對(duì)比,確認(rèn)數(shù)據(jù)是否存在不一致羡疗。
2.判斷上下文的userId是否發(fā)生變化染服。對(duì)應(yīng)的方法有Context.getUserId,ContentResolver.getUserId,ActivityManagetr.getUserId∵逗蓿活躍的userId以ActivityManagetr.getUserId結(jié)果為準(zhǔn)柳刮。界面切換則搜索關(guān)鍵字ActivityManager: START u...
3.確認(rèn)是上下文userId導(dǎo)致的問題痒钝,根據(jù)場景選擇合適的...AsUser方法秉颗。

參考文檔:
1.多用戶管理UserManager
2.Android逆向之旅---Android中的sharedUserId屬性詳解
3.Android 安全機(jī)制(1)uid 、 gid 與 pid

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末送矩,一起剝皮案震驚了整個(gè)濱河市蚕甥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌栋荸,老刑警劉巖菇怀,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凭舶,死亡現(xiàn)場離奇詭異,居然都是意外死亡爱沟,警方通過查閱死者的電腦和手機(jī)帅霜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呼伸,“玉大人义屏,你說我怎么就攤上這事》浯螅” “怎么了闽铐?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奶浦。 經(jīng)常有香客問我兄墅,道長,這世上最難降的妖魔是什么澳叉? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任隙咸,我火速辦了婚禮,結(jié)果婚禮上成洗,老公的妹妹穿的比我還像新娘五督。我一直安慰自己,他們只是感情好瓶殃,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布充包。 她就那樣靜靜地躺著,像睡著了一般遥椿。 火紅的嫁衣襯著肌膚如雪基矮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天冠场,我揣著相機(jī)與錄音家浇,去河邊找鬼。 笑死碴裙,一個(gè)胖子當(dāng)著我的面吹牛钢悲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舔株,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼莺琳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了督笆?” 一聲冷哼從身側(cè)響起芦昔,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娃肿,沒想到半個(gè)月后咕缎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珠十,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年凭豪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了焙蹭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嫂伞,死狀恐怖孔厉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帖努,我是刑警寧澤撰豺,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站拼余,受9級(jí)特大地震影響污桦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匙监,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一凡橱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亭姥,春花似錦稼钩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至氮块,卻和暖如春绍载,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滔蝉。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塔沃,地道東北人蝠引。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像蛀柴,于是被迫代替她去往敵國和親螃概。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容