Android中用戶管理的一些總結(jié)

用戶管理

  • 如何創(chuàng)建另外一個(gè)用戶扮超?如何區(qū)分訪客用戶及其他用戶取刃?各種用戶的區(qū)別是什么?
  • Android是怎么限制最多用戶數(shù)量的瞒津?Flyme最多可以創(chuàng)建幾個(gè)蝉衣,在哪里控制了?
  • 切換用戶做了些什么操作巷蚪?第三方應(yīng)用有什么辦法知道用戶切換了病毡?如何知道自己當(dāng)前處于哪個(gè)用戶?
  • 用戶切換后屁柏,原來(lái)的進(jìn)程怎么處理的啦膜?SystemUI是新的進(jìn)程嗎?微信是開(kāi)了兩個(gè)進(jìn)程嗎淌喻,進(jìn)程ID是怎么分配的僧家,有什么特點(diǎn)?
  • 不同用戶是如何分別存儲(chǔ)數(shù)據(jù)的裸删?多個(gè)用戶之間如何共享數(shù)據(jù)八拱?

UserManagerService創(chuàng)建過(guò)程

 mUsersDir = new File(dataDir, USER_INFO_DIR);
 
// Make zeroth user directory, for services to migrate their files to that location
File userZeroDir = new File(mUsersDir, "0");
userZeroDir.mkdirs();
FileUtils.setPermissions(mUsersDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
    |FileUtils.S_IROTH|FileUtils.S_IXOTH,
    -1, -1);

創(chuàng)建0號(hào)用戶的路徑并設(shè)置對(duì)應(yīng)的路徑權(quán)限,下面貼一段網(wǎng)上找到的標(biāo)志位與權(quán)限之間的關(guān)系(GRP=group, OTH=other)


S_IFMT
    type of file 
S_IFBLK
    block special 
S_IFCHR
    character special 
S_IFIFO
    FIFO special 
S_IFREG
    regular 
S_IFDIR
    directory 
S_IFLNK
    symbolic link 

File mode bits:

S_IRWXU
    read, write, execute/search by owner 
S_IRUSR
    read permission, owner 
S_IWUSR
    write permission, owner 
S_IXUSR
    execute/search permission, owner 
S_IRWXG
    read, write, execute/search by group 
S_IRGRP
    read permission, group 
S_IWGRP
    write permission, group 
S_IXGRP
    execute/search permission, group 
S_IRWXO
    read, write, execute/search by others 
S_IROTH
    read permission, others 
S_IWOTH
    write permission, others 
S_IXOTH
    execute/search permission, others 
S_ISUID
    set-user-ID on execution 
S_ISGID
    set-group-ID on execution 
S_ISVTX
    on directories, restricted deletion flag 

存儲(chǔ)用戶信息的文件路徑在: data/system/users/userlist.xml

createUser 用戶創(chuàng)建

用戶創(chuàng)建過(guò)程首先會(huì)檢查 uid 是否符合要求.

    /**
     * Enforces that only the system UID or root's UID or apps that have the
     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
     * permission can make certain calls to the UserManager.
     *
     * @param message used as message if SecurityException is thrown
     * @throws SecurityException if the caller is not system or root
     */
    private static final void checkManageUsersPermission(String message) {
        final int uid = Binder.getCallingUid();
        if (uid != Process.SYSTEM_UID && uid != 0
                && ActivityManager.checkComponentPermission(
                        android.Manifest.permission.MANAGE_USERS,
                        uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("You need MANAGE_USERS permission to: " + message);
        }
    }


然后會(huì)進(jìn)入到 CreateUserInternal() 方法中,在該方法中首先會(huì)檢查 用戶是否被賦予了 DISALLOW_ADD_USER 權(quán)限,該權(quán)限禁止用戶添加用戶.

        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
                UserManager.DISALLOW_ADD_USER, false)) {
            Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
            return null;
        }

創(chuàng)建用戶的過(guò)程中會(huì)創(chuàng)建 UserInfo 對(duì)象, UserInfo 對(duì)象中的 partial 屬性表明該 UserInfo 對(duì)象并沒(méi)有完全創(chuàng)建.接著會(huì)創(chuàng)建用戶的目錄,調(diào)用 getUserSystemDirectory 創(chuàng)建某個(gè)用戶對(duì)應(yīng)的路徑.
這里會(huì)判斷系統(tǒng)中 EFS 這一功能是否打開(kāi). EFS(文件加密系統(tǒng)) ,如果打開(kāi)了 EFS 功能涯塔,就會(huì)創(chuàng)建一個(gè)加密路徑 /data/secure/system/, 否則創(chuàng)建的就是普通路徑 /data/system .

    userInfo.partial = true;
    Environment.getUserSystemDirectory(userInfo.id).mkdirs();
    /**
     * Gets the system directory available for secure storage.
     * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system).
     * Otherwise, it returns the unencrypted /data/system directory.
     * @return File object representing the secure storage system directory.
     * @hide
     */
    public static File getSystemSecureDirectory() {
        if (isEncryptedFilesystemEnabled()) {
            return new File(SECURE_DATA_DIRECTORY, "system");
        } else {
            return new File(DATA_DIRECTORY, "system");
        }
    }   

最后系統(tǒng)會(huì)發(fā)出 ACTION_USER_ADDED 這個(gè)廣播,只有聲明了 MANAGE_USERS 這個(gè)權(quán)限才能接受到這個(gè)廣播.

            if (userInfo != null) {
                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
                        android.Manifest.permission.MANAGE_USERS);
            }

用戶的權(quán)限類別

  • FLAG_PRIMARY : 只有一個(gè)用戶能設(shè)置擁有這個(gè)標(biāo)志位肌稻。意味著這個(gè)標(biāo)志位TBD.
  • FLAG_ADMIN : 擁有創(chuàng)建和刪除用戶的權(quán)限.
  • FLAG_GUEST : 訪客用戶的標(biāo)識(shí).
  • FLAG_RESTRICTED : 受限,可能無(wú)法安裝應(yīng)用匕荸,或認(rèn)證wifi路徑.
  • FLAG_MANAGED_PROFILE : 表明該user是另外一個(gè)用戶的 profile.
  • FLAG_DISABLED : 表明該用戶被禁用.

各種用戶擁有不同的權(quán)限組合. 例如 USER_OWNERPRIMARYADMIN 的組合.

第三方應(yīng)用獲取當(dāng)前用戶

getCurrentUser() 是AMS中的一個(gè)接口爹谭,用來(lái)獲取當(dāng)前用戶,這里會(huì)檢查 是否有 INTERACT_ACROSS_USERSINTERACT_ACROSS_USERS_FULL 這兩個(gè)權(quán)限.

    @Override
    public UserInfo getCurrentUser() {
        if ((checkCallingPermission(INTERACT_ACROSS_USERS)
                != PackageManager.PERMISSION_GRANTED) && (
                checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED)) {
            String msg = "Permission Denial: getCurrentUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        synchronized (this) {
            int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
            return getUserManagerLocked().getUserInfo(userId);
        }
    }

切換用戶

切換用戶用的是 AMS 中的 SwitchUser() 函數(shù).

    @Override
    public boolean switchUser(final int userId) {
        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
        String userName;
        synchronized (this) {
            UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
            if (userInfo == null) {
                Slog.w(TAG, "No user info for user #" + userId);
                return false;
            }
            if (userInfo.isManagedProfile()) {
                Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
                return false;
            }
            userName = userInfo.name;
            mTargetUserId = userId;
        }
        mHandler.removeMessages(START_USER_SWITCH_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
        return true;
    }

這里首先會(huì)調(diào)用 enforceShellRestriction() 函數(shù)檢查是否是 通過(guò) shell終端來(lái)調(diào)用這個(gè)方法來(lái)切換用戶.

如果 userHandle 小于0或者該用戶受到限制,就會(huì)拋出 SecurityException 這個(gè)異常.

    enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);



    void enforceShellRestriction(String restriction, int userHandle) {
        if (Binder.getCallingUid() == Process.SHELL_UID) {
            if (userHandle < 0
                    || mUserManager.hasUserRestriction(restriction, userHandle)) {
                throw new SecurityException("Shell does not have permission to access user "
                        + userHandle);
            }
        }
    }

最后會(huì)發(fā)送 START_USER_SWITCH_MSG 這個(gè)廣播,AMS本身接收這個(gè)廣播后會(huì)調(diào)用 startUser() 方法來(lái)調(diào)起新用戶.

startUser() 函數(shù)很長(zhǎng).

首先會(huì)檢查 用戶是否有 INTERACT_ACROSS_USERS_FULL 這個(gè)權(quán)限榛搔,如果沒(méi)有就會(huì)直接拋出異常.

       if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: switchUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS_FULL;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

接著這里有一行代碼诺凡,作用暫時(shí)未知.

                mStackSupervisor.setLockTaskModeLocked(null, false, "startUser");

這里會(huì)判斷是否處于前臺(tái)狀態(tài)东揣,如果處于前臺(tái)狀態(tài),會(huì)進(jìn)行一個(gè)動(dòng)畫(huà)的切換.

            if (foreground) {
                mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
                       R.anim.screen_user_enter);
            }

這里判斷如果處于前臺(tái)狀態(tài)腹泌,就要隱藏掉切換后用戶不可見(jiàn)的 display , 這里 mWindowManager.lockNow(null) 的邏輯被屏蔽掉了.如果不處于前臺(tái)狀態(tài)嘶卧,這里就沒(méi)有屏蔽圖層,只是切換了用戶的profile真屯,然后會(huì)在 mUserLru 中記錄最近使用的user.

                if (foreground) {
                    mCurrentUserId = userId;
                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
                    updateCurrentProfileIdsLocked();
                    mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
                    //FLYME:huangxiaotao@SHELL.Feature modify for the guest mode {@
                    if (mzIsFalse()) {
                     // Once the internal notion of the active user has switched, we lock the device
                        // with the option to show the user switcher on the keyguard.
                        mWindowManager.lockNow(null);
                    }
                    //@}
                } else {
                    final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
                    updateCurrentProfileIdsLocked();
                    mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
                    mUserLru.remove(currentUserIdInt);
                    mUserLru.add(currentUserIdInt);
                }

這里會(huì)更新一波 UserState , 這里的 UserState 有些類似操作系統(tǒng)里的進(jìn)程切換概念脸候,
分為 STATE_BOOTING , STATE_RUNNING , STATE_STOPPING , STATE_SHUTDOWN 這幾個(gè)狀態(tài).

                final UserState uss = mStartedUsers.get(userId);

                // Make sure user is in the started state.  If it is currently
                // stopping, we need to knock that off.
                if (uss.mState == UserState.STATE_STOPPING) {
                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
                    // so we can just fairly silently bring the user back from
                    // the almost-dead.
                    uss.mState = UserState.STATE_RUNNING;
                    updateStartedUserArrayLocked();
                    needStart = true;
                } else if (uss.mState == UserState.STATE_SHUTDOWN) {
                    // This means ACTION_SHUTDOWN has been sent, so we will
                    // need to treat this as a new boot of the user.
                    uss.mState = UserState.STATE_BOOTING;
                    updateStartedUserArrayLocked();
                    needStart = true;
                }

                if (uss.mState == UserState.STATE_BOOTING) {
                    // Booting up a new user, need to tell system services about it.
                    // Note that this is on the same handler as scheduling of broadcasts,
                    // which is important because it needs to go first.
                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
                }

接著發(fā)送用戶切換的MSG,這個(gè)message有兩秒的延遲,兩秒后會(huì)停止當(dāng)前屏幕上用戶切換的活動(dòng).

                if (foreground) {
                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
                            oldUserId));
                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
                            oldUserId, userId, uss));
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
                }

會(huì)調(diào)用到 mWindowManager.stopFreezingScreen() 這個(gè)接口.

            if (!uss.switching && !uss.initializing) {
                mWindowManager.stopFreezingScreen();
                unfrozen = true;
            }

然后會(huì)把后臺(tái)的訪客模式用戶停掉.

        stopGuestUserIfBackground();

接著會(huì)發(fā)送調(diào)起用戶的廣播

  if (needStart) {
                    /// M: Mobile Management @{
                    mAmPlus.monitorBootReceiver(true, "User(" + userId + ") Bootup Start");
                    /// @}
                    // Send USER_STARTED broadcast
                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                    broadcastIntentLocked(null, null, intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, Process.SYSTEM_UID, userId);
                }

切換用戶的操作就到這里結(jié)束了.

用戶數(shù)量的控制

關(guān)于用戶數(shù)量的控制绑蔫,原生代碼并沒(méi)有對(duì)用戶數(shù)量加以限制运沦,F(xiàn)lyme中對(duì)用戶的限制數(shù)量為1.

可創(chuàng)建的最大用戶數(shù)量由 UserManager 中的 getMaxSupportedUsers() 方法控制.可以在 framework/base 工程下的 config.xml 文件中進(jìn)行配置.


    /**
     * Returns the maximum number of users that can be created on this device. A return value
     * of 1 means that it is a single user device.
     * @hide
     * @return a value greater than or equal to 1
     */
    public static int getMaxSupportedUsers() {
        // Don't allow multiple users on certain builds
        if (android.os.Build.ID.startsWith("JVP")) return 1;
        // Svelte devices don't get multi-user.
        if (ActivityManager.isLowRamDeviceStatic()) return 1;
        return SystemProperties.getInt("fw.max_users",
                Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
    }

配置選項(xiàng)如下.

1598    <!--  Maximum number of supported users -->
1599    <integer name="config_multiuserMaximumUsers">1</integer>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市配深,隨后出現(xiàn)的幾起案子携添,更是在濱河造成了極大的恐慌,老刑警劉巖篓叶,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烈掠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缸托,警方通過(guò)查閱死者的電腦和手機(jī)左敌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俐镐,“玉大人矫限,你說(shuō)我怎么就攤上這事∨迥ǎ” “怎么了叼风?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棍苹。 經(jīng)常有香客問(wèn)我无宿,道長(zhǎng),這世上最難降的妖魔是什么枢里? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任孽鸡,我火速辦了婚禮,結(jié)果婚禮上栏豺,老公的妹妹穿的比我還像新娘梭灿。我一直安慰自己,他們只是感情好冰悠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著配乱,像睡著了一般溉卓。 火紅的嫁衣襯著肌膚如雪皮迟。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天桑寨,我揣著相機(jī)與錄音伏尼,去河邊找鬼。 笑死尉尾,一個(gè)胖子當(dāng)著我的面吹牛爆阶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沙咏,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼辨图,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肢藐?” 一聲冷哼從身側(cè)響起故河,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吆豹,沒(méi)想到半個(gè)月后鱼的,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痘煤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年凑阶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衷快。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宙橱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出烦磁,到底是詐尸還是另有隱情养匈,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布都伪,位于F島的核電站呕乎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏陨晶。R本人自食惡果不足惜猬仁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望先誉。 院中可真熱鬧湿刽,春花似錦、人聲如沸褐耳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铃芦。三九已至雅镊,卻和暖如春襟雷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仁烹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工耸弄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卓缰。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓计呈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親征唬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捌显,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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