当前版本(Android 5.1)下,多用户管理适用于平板模式(手机版本可能受限于版本等原因)。
多用户模式下,不同用户运行在不同的用户空间,共享具体的应用实例(即不同的用户下,其应用版本是一致的),但拥有各自不同的配置。
本文不会对具体的代码进行解读,只会对相关概念、关联性较强的部分加以说明,目的在于对Android下多用户管理进行整体描述。
主要相关的代码路径如下:
frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
frameworks/base/core/java/android/os/UserManager.java
frameworks/base/core/java/android/content/pm/UserInfo.java
frameworks/base/core/java/android/os/UserHandle.java
frameworks/base/core/java/android/os/Process.java
frameworks/base/core/java/com/android/server/pm/PackageManagerService.java
在 /data/system/users 目录下,可以看到如下片段:
drwx------ system system 2017-12-22 09:15 0
-rw------- system system 193 2017-12-16 15:20 0.xml
-rw------- system system 128 2017-12-16 15:20 userlist.xml
其中 0.xml 记录了 user-0(即root用户,在Process.java中定义了ROOT_UID=0)的相关信息(相关属性字段在UserInfo.java中可以找到):
<user id="0" serialNumber="0" flags="19" created="0" lastLoggedIn="1513408822281">
<name>机主name>
<restrictions />
user>
其中的serialNumber会递增存储,即不会重复使用(在UserManagerService.java的private UserInfo createUserInternal(String name, int flags, int parentId) 方法中,有如下字样的代码:
userInfo.serialNumber = mNextSerialNumber++;
而对应的用户id,每次新建用户时,会通过如下方法获得可用的id:
/**
* Returns the next available user id, filling in any holes in the ids.
* TODO: May not be a good idea to recycle ids, in case it results in confusion
* for data and battery stats collection, or unexpected cross-talk.
* @return
*/
private int getNextAvailableIdLocked() {
synchronized (mPackagesLock) {
int i = MIN_USER_ID;
while (i < Integer.MAX_VALUE) {
if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
break;
}
i++;
}
return i;
}
}
而在 0 目录下,对应存放了一些系统配置信息:
accounts.db
accounts.db-journal
appwidgets.xml
package-restrictions.xml
usb_device_manager.xml
wallpaper
wallpaper_info.xml
而在userlist.xml中,对应存放了所有用户信息:
<users nextSerialNumber="10" version="4">
<user id="0" />
users>
在上述配置文件中,可以看出,系统在/data/system/users目录下管理不同用户的配置信息,android版本的多用户管理是在Linux版本之上的简化、封装,譬如Linux系统下,除了user-id以外,还有group-id、文件权限等(不是本文重点,不做赘述)。
而系统是否支持多用户,在UserManager.java中有明确的函数实现:
public static boolean supportsMultipleUsers() {
return getMaxSupportedUsers() > 1
&& SystemProperties.getBoolean("fw.show_multiuserui",
Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI));
}
UserManager中定义了相关的用户操作权限,
DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
DISALLOW_CONFIG_WIFI = "no_config_wifi";
DISALLOW_INSTALL_APPS = "no_install_apps";
DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
DISALLOW_SHARE_LOCATION = "no_share_location";
DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
DISALLOW_REMOVE_USER = "no_remove_user";
DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
DISALLOW_CONFIG_VPN = "no_config_";
DISALLOW_CONFIG_TETHERING = "no_config_tethering";
DISALLOW_FACTORY_RESET = "no_factory_reset";
DISALLOW_ADD_USER = "no_add_user";
ENSURE_VERIFY_APPS = "ensure_verify_apps";
DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
DISALLOW_APPS_CONTROL = "no_control_apps";
DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
DISALLOW_SMS = "no_sms";
DISALLOW_CREATE_WINDOWS = "no_create_windows";
DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
此类权限主要用于一些系统功能的控制,也包含了用户的添加、删除等。
UserHandle.java中,其内部值通过 final int mHandle 变量保存,用于保存对应的userId,UserHandle中定义了几个特殊的userId:
userId | 值 | 注释 |
---|---|---|
USER_OWNER | 0 | the “owner” user of the device (机主用户) |
USER_ALL | -1 | all users on the device (设备上所有用户) |
USER_CURRENT | -2 | the currently active user (当前用户) |
USER_CURRENT_OR_SELF | -3 | A user handle to indicate that we would like to send to the current user, but if this is calling from a user process then we will send it to the caller’s user instead of failing with a security exception (1、当前用户 2、调用者所在用户非当前用户时返回对应用户) |
USER_NULL | -1000 | An undefined user id (未定义用户) |
userId: 用户id,即上文中所说的多用户id,UserHandle中定义了几个特殊值(0、-1、2、-3、-1000),其中0值作为默认用户id,无论是否存在多用户状态,均为此值,其余userId根据添加时缓存情况变化,上文稍有提及
appId(base uid): 与用户id无关的应用程序id,取值范围为 [0, 100000),UserHandle中定义了PER_USER_RANGE = 100000,即每个user下允许的uid个数,根据对应的方法和说明可以知道其对应范围。所有的系统应用、用户应用均在此范畴,下文会对Process中的对应uid进行说明
uid: userId与appId结合下的id,其对应的获取可以从UserHandle.getUid方法中找到全貌:
public static final int getUid(int userId, int appId) {
if (MU_ENABLED) {
return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
} else {
return appId;
}
}
userGid: 获取某个user下的group id。通过getUid(userId, Process.SHARED_USER_GID)获取,即上述getUid方法中的参数appId使用Process.SHARED_USER_GID值获取,从变量描述可知,同一用户下的所有应用共享一个userGid,且此值是唯一的,可见android中的用户组管理直接与user关联,简化了对应管理。
sharedAppId:共享的appId。可以根据对应的uid或appId获取,从下述方法可知,该值其实也是唯一的,只有输入参数id一个变量,其值为 40000+id%100000,应该也只会在某些特殊场景使用到,但是是与userId无关的,粗略看过代码后发现,应该是和某个apk被拆分成多个包的实现有关,譬如abc.apk被拆分成abc-1.apk与abc-2.apk,那么二者需要共享一个appId。
public static final int getSharedAppGid(int id) {
return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE)
- Process.FIRST_APPLICATION_UID;
}
UserHandle中其余静态方法解释:
getAppId(int uid):获取uid的appId,直接通过 uid % PER_USER_RANGE 公式获得,可以从 getUid方法的实现中得知,此处其实是其对应的逆解。
getUserGid(int userId):获取userId对应的gid,上文有提及,内部直接调用的getUid(userId, Process.SHARED_USER_GID) 。
getUserId(int uid):获取uid对应的userId,代码中有对MU_ENABLED进行特殊处理,其实 uid / PER_USER_RANGE 公式已经可以满足,因为单用户情况下,uid通常为0. 同样,也是对getUid方法的逆解。
isApp(int uid):是否是应用进程。 uid <=0 时必然为false,对应UserHandle中定义的几种特殊uid;uid>0时,获得对应的appId,然后进行范围判断 return appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID。
isIsolated(int uid):是否是完全隔绝的沙箱进程,完全隔绝的沙箱进程每次启动都是独立的,不能复用已有的进程信息。这个进程与系统其他进程分开且没有自己的权限。获得对应的appId后,对其范围进行判断 return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID。
isSameUser(int uid1, int uid2):比较两个uid的userId是否相同,即它们是否属于同一个用户 return getUserId(uid1) == getUserId(uid2)。
isSameApp(int uid1, int uid2):比较两个uid的appId是否相同 getAppId(uid1) == getAppId(uid2)。
myUserId():获取当前进程的userId => getUserId(Process.myUid())
formatUid(StringBuilder sb, int uid) / formatUid(PrintWriter pw, int uid) 用于将uid进程显示成对应的格式化字符串,即通过ps指令查询到的进程USER值,将对应用户应用拆分成user/app/isolated等组合,形如u0_a20,而系统uid如1000则直接显示为system等,以StringBuilder中的实现为例:
if (uid < Process.FIRST_APPLICATION_UID) {
sb.append(uid);
} else {
sb.append('u');
sb.append(getUserId(uid));
final int appId = getAppId(uid);
if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
sb.append('i');
sb.append(appId - Process.FIRST_ISOLATED_UID);
} else if (appId >= Process.FIRST_APPLICATION_UID) {
sb.append('a');
sb.append(appId - Process.FIRST_APPLICATION_UID);
} else {
sb.append('s');
sb.append(appId);
}
}
上述方法本质上都是在相关概念基础上的封装,理解清除各个id的内在含义就会很清楚了。
Process.java中定义了系统应用UID、应用程序应用UID范围、沙盒应用UID范围、共享应用UID范围等:
UID/GID | 值 | 注释 |
---|---|---|
ROOT_UID | 0 | rootUid,默认根用户id |
SYSTEM_UID | 1000 | systemUid,系统代码运行的 uid/gid |
PHONE_UID | 1001 | the UID/GID under which the telephony code runs |
SHELL_UID | 2000 | the UID/GID for the user shell |
LOG_UID | 1007 | the UID/GID for the log group |
WIFI_UID | 1010 | the UID/GID for the WIFI supplicant process |
MEDIA_UID | 1013 | the UID/GID for the mediaserver process |
DRM_UID | 1019 | the UID/GID for the DRM process(Digital Rights Management数字版权管理) |
VPN_UID | 1016 | the UID/GID for the group that controls VPN services |
NFC_UID | 1027 | the UID/GID for the NFC service process |
BLUETOOTH_UID | 1002 | the UID/GID for the Bluetooth service process |
MEDIA_RW_GID | 1023 | the GID for the group that allows write access to the internal media storage |
PACKAGE_INFO_GID | 1032 | Access to installed package details |
SHARED_RELRO_UID | 1037 | the UID/GID for the shared RELRO file updater process |
SHARED_USER_GID | 9997 | Defines the gid shared by all applications running under the same profile |
FIRST_APPLICATION_UID | 10000 | Defines the start of a range of UIDs (and GIDs), going from this number to {@link #LAST_APPLICATION_UID} that are reserved for assigning to applications (首个应用程序UID,非系统应用,每个用户的首个安装应用id一般均由此值开始) |
LAST_APPLICATION_UID | 19999 | Last of application-specific UIDs starting at {@link #FIRST_APPLICATION_UID} |
FIRST_ISOLATED_UID | 99000 | First uid used for fully isolated sandboxed processes (with no permissions of their own) (首个完全沙盒进程uid,禁止应用访问) |
LAST_ISOLATED_UID | 99999 | Last uid used for fully isolated sandboxed processes (with no permissions of their own) |
FIRST_SHARED_APPLICATION_GID | 50000 | First gid for applications to share resources. Used when forward-locking is enabled but all UserHandles need to be able to read the resources (首个共享gid,用于应用程序共享资源) |
LAST_SHARED_APPLICATION_GID | 59999 | Last gid for applications to share resources. Used when forward-locking is enabled but all UserHandles need to be able to read the resources |
关于UserInfo、UserState以及对应的用户创建、删除、启动逻辑等,可参考如下两个链接中描述,非本文重点,不加赘述:
多用户管理UserManager
Android UserManager相关源码分析