Android 9.x多用戶機制 2 #Profile User啟動過程

Profile User啟動的主要邏輯在UserController#startUser
客戶端啟動的邏輯為:

iActivityManager.startUserInBackground(userId);

主要調(diào)用邏輯代碼如下:

UserController#startUser

    boolean startUser(
            final int userId,
            final boolean foreground,
            @Nullable IProgressListener unlockListener) {
        //1.檢查權(quán)限
        if (mInjector.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);
        }

        Slog.i(TAG, "Starting userid:" + userId + " fg:" + foreground);

        final long ident = Binder.clearCallingIdentity();
        try {
            final int oldUserId = getCurrentUserId();
            if (oldUserId == userId) {
                return true;
            }

            if (foreground) {
                mInjector.clearAllLockedTasks("startUser");
            }

            final UserInfo userInfo = getUserInfo(userId);
            if (userInfo == null) {
                Slog.w(TAG, "No user info for user #" + userId);
                return false;
            }
            //2.manage profile不允許前臺啟動
            if (foreground && userInfo.isManagedProfile()) {
                Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
                return false;
            }

            if (foreground && mUserSwitchUiEnabled) {
                mInjector.getWindowManager().startFreezingScreen(
                        R.anim.screen_user_exit, R.anim.screen_user_enter);
            }

            boolean needStart = false;
            boolean updateUmState = false;
            UserState uss;

            // If the user we are switching to is not currently started, then
            // we need to start it now.
            synchronized (mLock) {
                //3.創(chuàng)建該用戶的UserState對象嗤攻,用于后續(xù)啟動期間,各個狀態(tài)的保存和切換 
                //第一次執(zhí)行的話,該對象為null,新創(chuàng)建的對象的狀態(tài)處于初始狀態(tài)0(BOOTING)
                uss = mStartedUsers.get(userId);
                if (uss == null) {
                    uss = new UserState(UserHandle.of(userId));
                    uss.mUnlockProgress.addListener(new UserProgressListener());
                    mStartedUsers.put(userId, uss);
                    updateStartedUserArrayLU();
                    needStart = true;
                    updateUmState = true;
                } else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
                    Slog.i(TAG, "User #" + userId
                            + " is shutting down - will start after full stop");
                    mHandler.post(() -> startUser(userId, foreground, unlockListener));
                    return true;
                }
                final Integer userIdInt = userId;
                mUserLru.remove(userIdInt);
                mUserLru.add(userIdInt);
            }
            if (unlockListener != null) {
                uss.mUnlockProgress.addListener(unlockListener);
            }
            if (updateUmState) {
                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            }
            if (foreground) {
                // Make sure the old user is no longer considering the display to be on.
                mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
                synchronized (mLock) {
                    mCurrentUserId = userId;
                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
                }
                mInjector.updateUserConfiguration();
                updateCurrentProfileIds();
                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
                mInjector.reportCurWakefulnessUsageEvent();
                // 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.
                if (mUserSwitchUiEnabled) {
                    mInjector.getWindowManager().setSwitchingUser(true);
                    mInjector.getWindowManager().lockNow(null);
                }
            } else {
                final Integer currentUserIdInt = mCurrentUserId;
                updateCurrentProfileIds();
                //4. 保存一個[userid,profileGroupId]數(shù)組,profileGroupId是用戶創(chuàng)建階段完成初始化賦值的
                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
                synchronized (mLock) {
                    mUserLru.remove(currentUserIdInt);
                    mUserLru.add(currentUserIdInt);
                }
            }

            // Make sure user is in the started state.  If it is currently
            // stopping, we need to knock that off.
            if (uss.state == 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.setState(uss.lastState);
                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
                synchronized (mLock) {
                    updateStartedUserArrayLU();
                }
                needStart = true;
            } else if (uss.state == 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.setState(UserState.STATE_BOOTING);
                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
                synchronized (mLock) {
                    updateStartedUserArrayLU();
                }
                needStart = true;
            }
           //5. 發(fā)送SYSTEM_USER_START_MSG消息侦啸,回調(diào)系統(tǒng)指所有systemServer的onStartUser方法,通知user啟動
            if (uss.state == UserState.STATE_BOOTING) {
                // Give user manager a chance to propagate user restrictions
                // to other services and prepare app storage
                mInjector.getUserManager().onBeforeStartUser(userId);

                // 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));
            }

            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_MS);
            }

            if (needStart) {
                //6. Send ACTION_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);
                mInjector.broadcastIntent(intent,
                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                        null, false, false, MY_PID, SYSTEM_UID, userId);
            }

            if (foreground) {
                moveUserToForeground(uss, oldUserId, userId);
            } else {
                //7. 執(zhí)行Boot操作丧枪,會執(zhí)行執(zhí)行user啟動的各個狀態(tài)光涂,并報個每個階段的進度
                finishUserBoot(uss);
            }

            if (needStart) {
                //8.發(fā)送ACTION_USER_STARTING廣播
                Intent intent = new Intent(Intent.ACTION_USER_STARTING);
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);//靜態(tài)注冊的廣播(AndroidMenifest.xml中注冊的)不能接受該廣播
                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                mInjector.broadcastIntent(intent,
                        null, new IIntentReceiver.Stub() {
                            @Override
                            public void performReceive(Intent intent, int resultCode,
                                    String data, Bundle extras, boolean ordered,
                                    boolean sticky,
                                    int sendingUser) throws RemoteException {
                            }
                        }, 0, null, null,
                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }

        return true;
    }

啟動的流程總結(jié)如下

startUser
  1. 檢查啟動該用戶的是否具有INTERACT_ACROSS_USERS_FULL權(quán)限,如果沒有拧烦,直接拋出異常忘闻,應(yīng)用崩潰
  2. 檢查改profile user是否是后臺啟動,如果是前臺啟動恋博,則直接返回false齐佳;這也是profile user和普通user的區(qū)別,不同user可用通過
    switchUser方法债沮,切換到前臺用戶
  3. 創(chuàng)建改用戶對象的UserState炼吴,第一次啟動時,該用戶的狀態(tài)為0(STATE_BOOTING)
  4. 保存主用戶和該用戶對應(yīng)的profileGroupid的對應(yīng)關(guān)系疫衩,維護一個{userid,profileGroupId}的數(shù)據(jù)結(jié)構(gòu)
  5. 發(fā)送SYSTEM_USER_START_MSG消息硅蹦,回調(diào)系統(tǒng)指所有systemServer的onStartUser方法,通知他們,該user啟動了
  6. Send ACTION_USER_STARTED broadcast
    該廣播特性:只允許動態(tài)廣播監(jiān)聽童芹;接受者具有FOREGROUND優(yōu)先級
  7. 執(zhí)行finishUserBoot操作命爬,結(jié)束Boot狀態(tài),詳細過程見下面的finishUserBoot章節(jié)
  8. 發(fā)送ACTION_USER_STARTING廣播

下面看下辐脖,以上流程的中饲宛,比較重要的階段的代碼邏輯

finishUserBoot

后臺用戶才會執(zhí)行該操作,我們創(chuàng)建的profile User是一個ManagedProfile嗜价,并且必須后臺啟動艇抠;
改方法的主要代理邏輯是執(zhí)行user啟動的各個狀態(tài),并報個每個階段的進度


Profile User啟動過程_finishUserBoot_
  1. 將該用戶的狀態(tài)從STATE_BOOTING--->STATE_RUNNING_LOCKED久锥;狀態(tài)設(shè)置成功的情況系家淤,執(zhí)行以下操作
    發(fā)送消息REPORT_LOCKED_BOOT_COMPLETE_MSG,告訴其他注冊了IUserSwitchObserver的監(jiān)聽模塊瑟由,該用戶完成了BootComplete
    發(fā)送廣播ACTION_LOCKED_BOOT_COMPLETED

  2. 執(zhí)行maybeUnlockUser操作

    maybeUnlockUser

    首先絮重,執(zhí)行解鎖用戶存儲操作
    主要代碼邏輯是通過Ext4Crypt.cpp的e4crypt_unlock_user_key方法,設(shè)置profile user的存儲為unlock狀態(tài)

    TODO: rename to 'install' for consistency, and take flags to know which keys to install
    bool e4crypt_unlock_user_key(userid_t user_id, int serial, const std::string& token_hex,
                                 const std::string& secret_hex) {
        LOG(DEBUG) << "e4crypt_unlock_user_key " << user_id << " serial=" << serial
                   << " token_present=" << (token_hex != "!");
        if (e4crypt_is_native()) {
            if (s_ce_key_raw_refs.count(user_id) != 0) {
                LOG(WARNING) << "Tried to unlock already-unlocked key for user " << user_id;
                return true;
            }
            std::string token, secret;
            if (!parse_hex(token_hex, &token)) return false;
            if (!parse_hex(secret_hex, &secret)) return false;
            android::vold::KeyAuthentication auth(token, secret);
            if (!read_and_install_user_ce_key(user_id, auth)) {
                LOG(ERROR) << "Couldn't read key for " << user_id;
                return false;
            }
        } else {
            // When in emulation mode, we just use chmod. However, we also
            // unlock directories when not in emulation mode, to bring devices
            // back into a known-good state.
            if (!emulated_unlock(android::vold::BuildDataSystemCePath(user_id), 0771) ||
                !emulated_unlock(android::vold::BuildDataMiscCePath(user_id), 01771) ||
                !emulated_unlock(android::vold::BuildDataMediaCePath("", user_id), 0770) ||
                !emulated_unlock(android::vold::BuildDataUserCePath("", user_id), 0771)) {
                LOG(ERROR) << "Failed to unlock user " << user_id;
                return false;
            }
        }
        return true;
    }
    

其次歹苦,執(zhí)行finishUserUnlocking操作
改操作的主要路邏輯如下:
1. 開始進度report,報告unlock進度5%;
2. 回調(diào)UserManager的onBeforeUnlockUser方法青伤,
3. 將profile user的狀態(tài)為從STATE_RUNNING_LOCKED--->STATE_RUNNING_UNLOCKING
4. 將profile user的狀態(tài)更顯到UMS的mUserStates中去
5. report profile user unlock進度20%
6. 處理SYSTEM_USER_UNLOCK_MSG消息
調(diào)用系統(tǒng)中所有systemServer的unlockUser方法 更改profile user的狀態(tài)STATE_RUNNING_UNLOCKING-->STATE_RUNNING_UNLOCKED 將profile user的狀態(tài)更顯到UMS的mUserStates中去 報告unlock進度100% 發(fā)送ACTION_USER_UNLOCKED廣播 發(fā)送ACTION_MANAGED_PROFILE_UNLOCKED廣播 發(fā)送ACTION_USER_INITIALIZE廣播 發(fā)送ACTION_BOOT_COMPLETED廣播

startUser階段,各個系統(tǒng)廣播發(fā)送順序

  1. Intent#ACTION_USER_STARTED} sent to registered receivers of the new user
  2. Intent#ACTION_USER_BACKGROUND} sent to registered receivers of the outgoing user and all profiles of this user. Sent only if {@code foreground} parameter is true
  3. Intent#ACTION_USER_FOREGROUND} sent to registered receivers of the new user and all profiles of this user. Sent only if {@code foreground} parameter is true
    以上兩個廣播殴瘦,只在該方法進行了發(fā)送狠角。
    UserController.java
    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
        long ident = Binder.clearCallingIdentity();
        try {
            Intent intent;
            if (oldUserId >= 0) {
                // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
                List<UserInfo> profiles = mInjector.getUserManager().getProfiles(oldUserId, false);
                int count = profiles.size();
                for (int i = 0; i < count; i++) {
                    int profileUserId = profiles.get(i).id;
                    intent = new Intent(Intent.ACTION_USER_BACKGROUND);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
                    mInjector.broadcastIntent(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                }
            }
            if (newUserId >= 0) {
                // Send USER_FOREGROUND broadcast to all profiles of the incoming user
                List<UserInfo> profiles = mInjector.getUserManager().getProfiles(newUserId, false);
                int count = profiles.size();
                for (int i = 0; i < count; i++) {
                    int profileUserId = profiles.get(i).id;
                    intent = new Intent(Intent.ACTION_USER_FOREGROUND);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
                    mInjector.broadcastIntent(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                }
                intent = new Intent(Intent.ACTION_USER_SWITCHED);
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_FOREGROUND);
                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
                mInjector.broadcastIntent(intent,
                        null, null, 0, null, null,
                        new String[] {android.Manifest.permission.MANAGE_USERS},
                        AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                        UserHandle.USER_ALL);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

該方法有兩個調(diào)用場景
第一個:AMS#systemReady, mUserController.sendUserSwitchBroadcasts(-1, currentUserId);蚪腋,因為oldUserId為-1丰歌,因此只會發(fā)送ACTION_USER_FOREGROUND廣播
第二個:在startUser的時候,這個場景,只有foreground為true的時候才會發(fā)送屉凯。兩個廣播都會發(fā)送立帖,因為一個user切換到前臺,必然伴隨另一個user切換到后臺悠砚;兩個廣播攜帶的userid不一樣(不是廢話嗎)

if (foreground) {
    moveUserToForeground(uss, oldUserId, userId);-->sendUserSwitchBroadcasts
} else {
    finishUserBoot(uss);
}
  1. Intent#ACTION_USER_SWITCHED} sent to registered receivers of the new user.Sent only if {@code foreground} parameter is true
    也是在sendUserSwitchBroadcasts方法中晓勇,場景同上面兩個廣播
    //Intent#ACTION_USER_STARTING} 注釋找那個標注,這個廣播應(yīng)該在這個位置哩簿,實際代碼中實在startUser的最后階段發(fā)送的

  2. Intent#ACTION_LOCKED_BOOT_COMPLETED} - ordered broadcast sent to receivers of the new user

  3. Intent#ACTION_USER_UNLOCKED} - sent to registered receivers of the new user

  4. Intent#ACTION_PRE_BOOT_COMPLETED} - ordered broadcast sent to receivers of the new user. Sent only when the user is booting after a system update.

  5. Intent#ACTION_USER_INITIALIZE} - ordered broadcast sent to receivers of thenew user. Sent only the first time a user is starting.

  6. Intent#ACTION_BOOT_COMPLETED} - ordered broadcast sent to receivers of the newuser. Indicates that the user has finished booting.

  7. Intent#ACTION_USER_STARTING} - ordered broadcast sent to registered receivers of the new fg user

擴展知識

有序廣播
有序廣播宵蕉,即從優(yōu)先級別最高的廣播接收器開始接收,接收完了如果沒有丟棄节榜,就下傳給下一個次高優(yōu)先級別的廣播接收器進行處理羡玛,依次類推,直到最后宗苍。如果多個應(yīng)用程序設(shè)置的優(yōu)先級別相同稼稿,則誰先注冊的廣播薄榛,誰就可以優(yōu)先接收到廣播。
通過Context.sendorderBroadCast()方法來發(fā)送

sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)

其中的參數(shù)resultReceiver让歼,可以自己重寫一個類敞恋,作為一個最終的receive 最后都能夠接收到廣播,最終的receiver 不需要再清單文件里面配置谋右,initialData可以作為傳輸?shù)臄?shù)據(jù)
廣播可以被終止硬猫,數(shù)據(jù)傳輸過程中可以被修改。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末改执,一起剝皮案震驚了整個濱河市啸蜜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辈挂,老刑警劉巖衬横,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異终蒂,居然都是意外死亡蜂林,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門拇泣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來噪叙,“玉大人,你說我怎么就攤上這事挫酿」姑校” “怎么了愕难?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵早龟,是天一觀的道長。 經(jīng)常有香客問我猫缭,道長葱弟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任猜丹,我火速辦了婚禮芝加,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘射窒。我一直安慰自己藏杖,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布脉顿。 她就那樣靜靜地躺著蝌麸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艾疟。 梳的紋絲不亂的頭發(fā)上来吩,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天敢辩,我揣著相機與錄音,去河邊找鬼弟疆。 笑死戚长,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的怠苔。 我是一名探鬼主播同廉,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柑司!你這毒婦竟也來了恤溶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤帜羊,失蹤者是張志新(化名)和其女友劉穎咒程,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讼育,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡帐姻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奶段。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饥瓷。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖痹籍,靈堂內(nèi)的尸體忽然破棺而出呢铆,到底是詐尸還是另有隱情,我是刑警寧澤蹲缠,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布棺克,位于F島的核電站,受9級特大地震影響线定,放射性物質(zhì)發(fā)生泄漏娜谊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一斤讥、第九天 我趴在偏房一處隱蔽的房頂上張望纱皆。 院中可真熱鬧,春花似錦芭商、人聲如沸派草。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽近迁。三九已至,卻和暖如春蛉艾,著一層夾襖步出監(jiān)牢的瞬間钳踊,已是汗流浹背衷敌。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拓瞪,地道東北人缴罗。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像祭埂,于是被迫代替她去往敵國和親面氓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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