我們可以感受到,在Android 4.2中的一個比較顯著的改變就是加入了多用戶的支持。因多用戶手機專利早已被Symbian雇員注冊,故 android官方的多用戶切換目前僅支持平板設備。
多用戶模式的啟用
系統判斷當前設備是否支持多用戶模式的依據是配置文件config.xml中的config_multiuserMaximumUsers配置項。 其取值為整型,決定着當前設備支持的最大用戶上限。默認值為1,即不支持多用戶。如需啟用多用戶,則設置此值 為大於1的值。在Nexus 7中,此值為8。
具體代碼的判斷位置在UserManager.java:
- public static int getMaxSupportedUsers() {
- // Don't allow multiple users on certain builds
- if (android.os.Build.ID.startsWith("JVP")) return 1;
- return SystemProperties.getInt("fw.max_users",
- Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
- }
多用戶相關操作流程
對用戶的操作目前未對普通應用開放,其相關API都有hide注解,並需要system權限。此外,用戶的添加和移除還 要需android.Manifest.permission.MANAGE_USERS權限。
用戶添加流程
用戶添加是通過調用UserManager的public UserInfo createUser(String name, int flags)方法進行的。其具體實現在UserManagerService的同名方法中。
在調用時,系統進行如下操作:
- 檢查調用者是否具有所需權限。
- 對安裝和軟件包流程加鎖,保證線程安全
- 檢查多用戶環境是否到達用戶數量限制。如果沒有,創建用戶實例
- 為新用戶創建相關目錄
- 序列化用戶列表
- 發送用戶建立廣播,MountService在收到此廣播后,調用createEmulatedVolumeForUserLocked方法為用戶建 立相應的數據目錄
用戶的保存
用戶創建后,會首先在/data/system/users/userlist.xml文件中保存新增加用戶的id,創建/data/system/users/ 用戶id 目錄,並將用戶信息保存至其下的用戶id.xml 文件中。其內容包括一些基本的用戶信息。
用戶切換流程
用戶切換是通過調用ActivityManager的public boolean switchUser(int userId)方法進行。一般通過 ActivityManagerNative.getDefault().switchUser(int userId)進行調用。
在調用時,系統進行以下操作
- 檢查調用者是否具有所需權限。
- 獲取切換目標用戶信息,並設定當前用戶為目標用戶
- WindowsManagerService設置當前用戶,鎖定屏幕
- 切換目標用戶狀態至啟動
- 廣播REPORT_USER_SWITCH_MSG和USER_SWITCH_TIMEOUT_MSG消息,設定用戶切換和切換超時時間(2秒),此超 時時間用於限定REPORT_USER_SWITCH_MSG廣播全程時間。
- 切換Activity堆棧至當前用戶
- 廣播ACTION_USER_SWITCHED消息,各接收組件進行相應操作。 大部分具體操作,比如存儲目錄切換、安全設置 切換等,都在此廣播后進行。
用戶移除流程
用戶移除是通過調用UserManager的 public boolean removeUser(int userHandle) 方法進行的。其具體實現同樣 是在UserManagerService的同名方法中。
在調用時,系統進行如下操作:
- 檢查調用者是否具有所需權限。
- 對軟件包變化加鎖
- 將用戶id加入待移除用戶列表,將用戶狀態設為partial,這樣,在下次系統啟動時,會清除此用戶。
- 停止用戶,殺掉用戶相關進程。
- 發送用戶移除的廣播。廣播成功后,刪除用戶描述文件和數據文件。
- 序列化用戶列表
此外UserManager還提供了 public void wipeUser(int userHandle) 方法,用於刪除單個用戶的所有數據,但保留 用戶賬號。 此方法目前對應的底層實現尚未完成。
多用戶模式的API接口UserManager->UserManagerService
大致結構
與其它系統服務的實現類似,用戶管理也采用了經由Binder調用的遠程服務機制。UserManager為暴露給用戶的接 口,UserManagerService為接口的底層實現。其類圖如下所示:
android.os.UserManager
UserManager是暴露出來的應用程序接口。對於普通應用程序,提供用戶數查詢,用戶狀態判斷和用戶序列號查詢 等基本功能。 普通應用沒有用戶操作權限。
對於系統應用,UserManager提供了創建/刪除/擦除用戶、用戶信息獲取、用戶句柄獲取等用戶操作的接口。均由遠 程調用UserManagerService服務的對應方法實現。
isUserAGoat()
UserManager中提供了一個名為isUserAGoat()的方法。源碼中此方法直接返回了false。此方法的加入純粹是為了給 枯燥的編程生活帶來一絲樂趣,以便寫出:
- while(!isUserAGoat()){
- }
這樣的語句。
com.android.server.pm.UserManagerService
與其它大部分Service一樣,UserManagerService的實現采用了 單例模式。在服務中,由組成為UserInfo類的散列 表mUsers維護所有的用戶狀態。
mUsers在系統啟動時由/data/system/users/userlist.xml讀取生成,並在運行期間動態改變。所有用戶的添加、刪 除操作,都最終序列化回此文件中。
com.android.server.am.ActivityManagerService
ActivityManagerService目前加入了多用戶支持。負責維護設備中存在的所有用戶狀態。服務以下述變量來記錄當 前處於“啟動”狀態的用戶。
- /**
- * Which uses have been started, so are allowed to run code.
- */
- final SparseArray mStartedUsers = new SparseArray();
- /**
- * LRU list of history of current users. Most recently current is at the end.
- */
- final ArrayList mUserLru = new ArrayList();
- /**
- * Constant array of the users that are currently started.
- */
- int[] mStartedUserArray = new int[] { 0 };
用戶的啟動狀態對象為com.android.server.am.UserStartedState。其中指定的用戶狀態有四種:
- public final static int STATE_BOOTING = 0; //用戶啟動
- public final static int STATE_RUNNING = 1; //運行中
- public final static int STATE_STOPPING = 2; //停止中
- public final static int STATE_SHUTDOWN = 3; //用戶關閉狀態
完整的用戶生命周期為:
BOOTING->RUNNING->STOPPING->SHUTDOWN
用戶必須處於RUNNING狀態時,才能作為切換的目標用戶。所以在用戶切換流程中,首先要判斷當前用戶的狀態, 並啟動STOPPING/SHUTDOWN狀態的用戶。
多用戶模式的牽涉面
鎖屏界面
用戶最先體驗到多用戶的入口位置即為鎖屏界面。鎖屏界面中加入了用戶切換組件: KeyguardMultiUserSelectorView類。
該類在設備允許多用戶存在的情況下,顯示當前所有用戶的列表。並在用戶進行選擇后,調用 ActivityManagerNative.getDefault().switchUser(int userId)方法進行用戶切換。
外部存儲
對於每個用戶,Android都為其分配了單獨的存儲空間。標准的支持多用戶的外部存儲空間是由init.rc定義的環境 變量所指定:
- # See storage config details at <a href="http://source.android.com/tech/storage/">http://source.android.com/tech/storage/</a>
- mkdir /mnt/shell/emulated 0700 shell shell
- mkdir /storage/emulated 0555 root root
- export EXTERNAL_STORAGE /storage/emulated/legacy
- export EMULATED_STORAGE_SOURCE /mnt/shell/emulated
- export EMULATED_STORAGE_TARGET /storage/emulated
- # Support legacy paths
- symlink /storage/emulated/legacy /sdcard
- symlink /storage/emulated/legacy /mnt/sdcard
- symlink /storage/emulated/legacy /storage/sdcard0
- symlink /mnt/shell/emulated/0 /storage/emulated/legacy
在Dalvik虛擬機初始化的過程中,會以dalvik_system_Zygote.cpp中的mountEmulatedStorage函數,使用帶有 MS_BIND參數的mount命令, 將用戶對應的外部存儲卡目錄mount到上述定義的TARGET目錄下。其判斷應用userid的 方式為: 以當前應用的uid/100000,獲得對應的userid,這段邏輯位於system/core/libcutils/multiuser.c中。
而Environment類中相應的獲取外部存儲目錄的方法,也是由上述環境變量所獲得。對於每個用戶,其標准外部存 儲路徑為:
EMULATED_STORAGE_TARGET/userid/
比如:
/storage/emulated/0 為主用戶的外部存儲路徑。
包管理(PackageManagerService)
在多用戶環境下,所有用戶安裝的應用仍然同以前一樣,放置於/data/app目錄下。但原先/data/data的數據存儲位 置目前僅對主用戶有效,其余用戶的數據存儲目錄則位於/data/user/用戶id/目錄下。 此目錄的創建是在創建用戶 時由前述的MountService完成的。
對於每個用戶,系統都會以PackageuserState類來維護其安裝的軟件狀態。此列表以散列表的形式存在,由 PackageSettingBase類維護。所有的包——用戶關系和狀態最終仍然序列化至/data/system/package.xml中,並保留 /data/system/package-backup.xml作為備份。
可能的發展
Guest用戶的實現
目前代碼中已經存在諸如 isGuestEnabled() 之類的方法。但沒有對開發公開,可以預計今后會加入Guest用戶, 實現“隨手玩玩”模式