概述
Android从4.2开始支持多用户模式,不同的用户运行在独立的用户空间,它们的的锁屏设置,PIN码和壁纸等系统设置是各不相同的,而且不同用户安装的应用和应用数据都是不一样的,但是系统中和硬件相关的设置则是共用的,比如网络设置等。通常第一个在系统中注册的用户将成为系统管理员,可以管理手机上的其他用户。但由于专利的原因,目前手机上并没有开启多用户模式,这种模式只能用在平板上面。
手机上经常见到的一个功能就是访客模式,它是多用户模式的一种应用。访客模式下用户的所有数据(通讯录,短信,应用等)会被隐藏,访客只能查看和使用手机的基本功能,另外你还可以设置访客是否有接听电话、发送短信等权限。
与其它系统服务的实现类似,用户管理也采用了经由Binder
调用的远程服务机制。UserManagerService
(以下简称 UMS )继承自IUserManager.Stub
为接口的底层实现,作为Binder的服务端。UserManager
为暴露给用户的接口,它的成员变量mService
是IUserManager.Stub.Proxy
的实例,作为Binder的客户端,UserManager
的大部分工作都是通过mService
与服务端进行通信调用UMS的对应方法来完成的。
本文的代码是基于Android6.0来进行介绍。UserManager
相关代码位置:
1 | frameworks/base/core/java/android/os/UserManager.java |
- PackageManagerService:在多用户环境中,所有安装的应用还是位于 /data/app/ 目录中,应用的数据还是保存在 /data/data 下面,这些数据只对 Id 为 0 的用户即管理员用户有效,但是这些应用的数据在 /data/user/<用户id>/ 目录下面都会有单独的一份。
UserManager
UserManager
可以称为 UMS 的代理对象,它通过IUserManager mService
来与 UMS 进行进程间的通信。UserManager
是暴露出来的应用程序接口。对于普通应用程序,提供用户数查询,用户状态判断和用户序列号查询等基本功能。普通应用没有用户操作权限。
对于系统应用,UserManager
提供了创建/删除/擦除用户、用户信息获取、用户句柄获取等用户操作的接口。
这些操作均由远程调用 UMS 服务的对应方法实现。
几个常用方法介绍
判断是否支持多用户模式:
1 | public static boolean supportsMultipleUsers() { |
首先会读取系统配置fw.show_multiuserui
和fw.max_users
,如果系统没有这个配置项则从配置文件中读取默认值,配置文件在frameworks/base/core/res/res/values/config.xml
中:
1 | <!-- Maximum number of supported users --> |
也可以看到,手机上默认是不支持多用户模式的。
查询是否是访客模式
查询当前进程的用户是否是访客。
1 | UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); |
查询用户权限
查询当前进程的用户是否拥有某个权限,
1 | UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); |
还有个方法是查询指定用户的权限:
1 | public boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) |
如何创建Guest用户
比如要创建一个访客用户,就可以调用UserManager.createGuest(Context context, String name)
方法来完成:
1 | public UserInfo createGuest(Context context, String name) { |
从代码我们可以看到createGuest
最终也会调用到UserManagerService.createUser
方法,然后通过setUserRestrictions
方法进一步对Guest用户的权限进行限制。因此,Guset用户和普通用户的区别也就在于权限的不同。
可以通过下面的方式创建一个访客用户:
1 | private void initGuestUser() { |
如何切换用户
一般通过 ActivityManagerNative.getDefault().switchUser(int userId)
进行调用,这个在后面会有详细介绍。
1 | private void enterGuestMode() { |
用户权限
不同的用户UserManager
通过一些限制赋予了不同的权限,下面列举一些限制权限:
- DISALLOW_MODIFY_ACCOUNTS
- DISALLOW_CONFIG_WIFI
- DISALLOW_INSTALL_APPS
- DISALLOW_UNINSTALL_APPS
- DISALLOW_SHARE_LOCATION
- DISALLOW_INSTALL_UNKNOWN_SOURCES
- DISALLOW_CONFIG_BLUETOOTH
- DISALLOW_USB_FILE_TRANSFER
- DISALLOW_CONFIG_CREDENTIALS
- DISALLOW_REMOVE_USER
- DISALLOW_DEBUGGING_FEATURES
- DISALLOW_CONFIG_VPN
- DISALLOW_CONFIG_TETHERING
- DISALLOW_NETWORK_RESET
- DISALLOW_FACTORY_RESET
- DISALLOW_ADD_USER
- DISALLOW_CONFIG_CELL_BROADCASTS
- DISALLOW_CONFIG_MOBILE_NETWORKS
- DISALLOW_APPS_CONTROL
- DISALLOW_MOUNT_PHYSICAL_MEDIA
- DISALLOW_UNMUTE_MICROPHONE
- DISALLOW_ADJUST_VOLUME
- DISALLOW_OUTGOING_CALLS
- DISALLOW_SMS
- DISALLOW_FUN
- DISALLOW_CREATE_WINDOWS
- DISALLOW_CROSS_PROFILE_COPY_PASTE
- DISALLOW_OUTGOING_BEAM
- DISALLOW_WALLPAPER
- DISALLOW_SAFE_BOOT
- DISALLOW_RECORD_AUDIO
相关adb命令
获取用户信息
用adb shell dumpsys user
可以查看当前的用户情况:
1 | Users: |
上面的打印是在UserManagerService.dump()
中打印的。
UserInfo{0:机主:13} 所代表的含义可以参考代码UserInfo.toString()
:
1 |
|
- 0:id
- 机主:name
- 13:flags
UserManagerService
UMS 是用来管理用户的系统服务,是创建、删除以及查询用户的执行者。
下面先看一下 UMS 的初始化过程。
初始化
UMS 的创建是在PackageManagerService
的构造函数中进行的。
1 | sUserManager = new UserManagerService(context, this, |
接下来再看一下 UMS 的构造函数:
UMS 构造函数有两个参数:分别是/data
目录和/data/user/
目录。
1 | private UserManagerService(Context context, PackageManagerService pm, |
然后再看一下systemReady()
函数,这里面也有一些初始化的工作,它的调用是在PackageManagerService.systemReady()
中进行的。
1 | void systemReady() { |
初始化过程中,会创建/data/system/users/
目录,以及在/data/system/users/
下面创建0/
目录,然后调用readUserListLocked()
分析/data/system/users/userlist.xml
文件,这个文件保存了系统中所有用户的Id信息。/data/system/users
目录下面的文件有:
1 | 0 |
0/目录下面的文件:
1 | accounts.db |
里面存放了针对该用户的各种设置数据。
userlist.xml示例:
1 |
|
nextSerialNumber
指的是创建下一个用户时它的serialNumber
,version
指的是当前多用户的版本,使 userlist.xml 文件像数据库一样是可以升级的。guestRestrictions
标签指的是为访客用户设置的权限,可以通过UserManager.setDefaultGuestRestrictions()
来设置。
这里面有两个用户的信息,分别为 id 为 0 和 10 的用户。
得到 Id 信息后还要读取保存了用户注册信息的 xml 文件,这个文件也位于/data/system/users
目录下,文件名用用户 Id 数字表示。
0.xml 示例:
1 |
|
10.xml:
1 |
|
标签的属性值对应了UserInfo
里面的成员变量,读取用户的xml文件后,会根据文件的内容来创建和初始化一个UserInfo
来保存,并把该对象加入到mUsers
列表中去。restrictions
表示针对该用户做的权限限制,可以通过UserManager.setUserRestriction()
或UserManager.setUserRestrictions()
进行设置。
如果有未创建完成的用户,即partial=true
的用户,则把它们从用户列表中移除出去。
因此,UMS 的初始化工作主要就是分析userlist.xml
文件、创建用户列表mUsers
以及设置用户权限。
UserInfo
1 | public class UserInfo implements Parcelable { |
需要注意的是,用户的Id用来表示用户,如果用户被删除了它的Id会分配给下一个新建的用户,用来保持Id的连续性;
但是serialNumber是一个不会重复的数字,是不会被重复利用的,用来惟一标识一个用户。
UserHandle
UserHandle
就代表了设备中的一个用户,提供了一系列针对用户操作的API。
现在先来分析几个用户相关的概念:
- userId:用户id
- appId:是指跟用户无关的应用程序id,取值范围
0 <= appId < 100000
- uid:是指跟用户紧密相关的应用程序id
他们之间有一下的换算关系:
1 | uid = userId * 100000 + appId |
在UserHandle
中有常量PER_USER_RANGE
,它的值为100000
,也就是说每个 user 最大可以有 100000 个 appid 。
下面介绍几个UserHandle
的API:
- isApp(int uid):是否是应用进程
- isIsolated(int uid):是否是完全隔绝的沙箱进程,完全隔绝的沙箱进程每次启动都是独立的,不能复用已有的进程信息。这个进程与系统其他进程分开且没有自己的权限。
- isSameUser(int uid1, int uid2):比较两个uid的userId是否相同,即它们是否属于同一个用户
- isSameApp(int uid1, int uid2):比较两个uid的appId是否相同
- getUserId(int uid):根据uid获取userId
- getAppId(int uid):根据uid获取appId
userId的几种类型:
userId | 值 | 含义 |
---|---|---|
USER_OWNER | 0 | 机主用户 |
USER_ALL | -1 | 所有用户 |
USER_CURRENT | -2 | 当前用户 |
USER_CURRENT_OR_SELF | -3 | 当前用户或者调用者所在用户 |
USER_NULL | -1000 | 未定义用户 |
1 | public static final UserHandle OWNER = new UserHandle(USER_OWNER); |
CURRENT
就代表了当前用户的UserHandle
。
UID
终端输入命令adb shell ps
,可以得到设备的进程信息,下面仅列举部分具有代表性的进程信息:
1 | USER PID PPID VSIZE RSS WCHAN PC NAME |
可以看到 UID 有root,system,shell等都属于系统 uid,它们定义在在Process.java
文件,如下:
UID | 值 | 含义 |
---|---|---|
ROOT_UID | 0 | root UID |
SYSTEM_UID | 1000 | |
PHONE_UID | 1001 | |
SHELL_UID | 2000 | |
LOG_UID | 1007 | |
WIFI_UID | 1010 | |
MEDIA_UID | 1013 | |
DRM_UID | 1019 | |
VPN_UID | 1016 | |
NFC_UID | 1017 | |
BLUETOOTH_UID | 1002 | |
MEDIA_RW_GID | 1023 | |
PACKAGE_INFO_GID | 1032 | |
SHARED_RELRO_UID | 1037 | |
SHARED_USER_GID | 9997 |
在Process.java
中还有下面常量:
- FIRST_APPLICATION_UID = 10000
- LAST_APPLICATION_UID = 19999
- FIRST_SHARED_APPLICATION_GID = 50000
- LAST_SHARED_APPLICATION_GID = 59999
- FIRST_ISOLATED_UID = 99000 //沙箱进程起始值
- LAST_ISOLATED_UID = 99999
还有像u0_a24
这种uid代表的uid为10024,代表user 0 的一个应用程序uid,这个转换方法在UserHandle.formatUid()
:
1 | public static void formatUid(StringBuilder sb, int uid) { |
如果我们切换到访客模式,还会发现一个UID为u10_a24
的com.android.systemui
进程,因为该应用的appid为10024,不同的是它们是在不同的用户下面创建的进程。
但是在访客模式下面,root,system,shell等这些系统进程是和主用户是共用的,不会再创建新的进程。
UserState
先了解一下用户的几个运行状态:
1 | public final class UserState { |
UMS 有下列变量来存储用户的相关信息:
mCurrentUserId
表示当前正在允许的用户的Id;mTargetUserId
记录当前正在切换到该用户;mStartedUsers
保存了当前已经运行过的用户的列表,这个列表中的用户会有上面的四种状态mUserLru
用最近最少使用算法保存的用户列表,最近的前台用户保存在列表的最后位置mStartedUserArray
表示mStartedUsers中当前正在运行的用户列表的index,即mStartedUsers中除去正在关闭和已经被关闭状态外的用户
创建用户
UserManagerService
UMS 中创建用户是在createUser()
方法中实现的:
1 |
|
首先检查调用者的权限,只有 UID 是 system 或者具有android.Manifest.permission.MANAGE_USERS
的应用才有权限,否则抛出异常。然后就调用createUserInternal
来执行真正的创建工作。
1 | private UserInfo createUserInternal(String name, int flags, int parentId) { |
由上面的代码可知,createUser
主要做了一下工作:
- 检查调用进程所属用户是否被限制添加用户、当前设备是否是低内存设备、当前用户数是否已达上限,如是则停止创建新用户
- 为新用户设置用户信息,比如Id,序列号创建时间等
- 将用户信息写入userlist.xml,注意,此时的userInfo.partial = true,表示正在创建
- 创建用户目录/data/user/
/或者/mnt/expand/ /user/ / - 调用PackageManagerService的createNewUserLILPw方法,这个函数会在新建用户的目录下面为所有应用创建数据目录
- 创建用户完成,将userInfo.partial设置为false,创建用户的信息文件,比如0.xml
- 发送用户创建完成的广播
PackageManagerService
创建用户数据createNewUserLILPw()
1 | void createNewUserLILPw(int userHandle) { |
下面来看一下Settings.createNewUserLILP()
方法的代码:
1 | void createNewUserLILPw(PackageManagerService service, Installer installer, int userHandle) { |
对每一个用户,系统都会以 PackageuserState
类来保护其安装的每一个包状态。它以SparseArray
的形式由 PackageSetting
类来保护,PackageSetting
存储了每一个安装包的数据。也就是说对于每个安装包,里面都有个每个用户对应的列表 userState
来存储该安装包对于不同用户的不同状态,比如针对该用户是否隐藏以及是否标识为已安装状态。具体详细介绍参考我的介绍 PackageManagerService
的博客。
切换用户
ActivityManagerService
startUser()
下面介绍一下切换用户的流程,比如我们从机主用户切换到访客用户是就是走的这个流程。
用户切换是通过调用ActivityManager
的public boolean switchUser(int userId)
方法进行。一般通过 ActivityManagerNative.getDefault().switchUser(int userId)
进行调用。
最终会调用ActivityManagerService.switchUser
方法:
1 |
|
Handler
收到START_USER_SWITCH_MSG
消息后,会调用showUserSwitchDialog()
来弹出一个确认的对话框。
1 | private void showUserSwitchDialog(int userId, String userName) { |
点击确定后最终会调用到startUser()
来执行切换用户的动作。
1 | private boolean startUser(final int userId, final boolean foreground) { |
下面来分析一下moveUserToForeground()
函数
1 | void moveUserToForeground(UserState uss, int oldUserId, int newUserId) { |
消息处理
下面介绍一下startUser()
中发出的几条消息的处理。
SYSTEM_USER_CURRENT_MSG:
1 | case SYSTEM_USER_CURRENT_MSG: { |
这里主要是通知BatteryStatsService
用户切换的消息以及让SystemServiceManager
调用各个SystemService
的onSwitchUser(userHandle)
方法。
REPORT_USER_SWITCH_MSG、USER_SWITCH_TIMEOUT_MSG、CONTINUE_USER_SWITCH_MSG和REPORT_USER_SWITCH_COMPLETE_MSG
1 | case REPORT_USER_SWITCH_MSG: { |
REPORT_USER_SWITCH_MSG
消息的处理方法是dispatchUserSwitch()
:
1 | void dispatchUserSwitch(final UserState uss, final int oldUserId, |
如果系统中有对切换用户感兴趣的模块,可以调用AMS的registerUserSwitchObserver()
方法来注册观察对象,这个对象会保存在mUserSwitchObservers
中,当有用户切换时AMS会通过调用onUserSwitching
来通知这些模块,模块处理完成后会调用参数中传递的callback
来通知AMS。最后都调用完成后会调用sendContinueUserSwitchLocked
来继续进行切换用户的工作。sendContinueUserSwitchLocked()
方法会先清除前面延迟发送的USER_SWITCH_TIMEOUT_MSG
消息,然后再发送一条CONTINUE_USER_SWITCH_MSG
消息。CONTINUE_USER_SWITCH_MSG
消息的执行函数是completeSwitchAndInitialize()
方法,
1 | void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) { |
REPORT_USER_SWITCH_COMPLETE_MSG
消息的处理函数是dispatchUserSwitchComplete
,此方法的主要工作就是调用观察者的onUserSwitchComplete()
方法,进行用户切换的收尾工作。
1 | void dispatchUserSwitchComplete(int userId) { |
再介绍一下USER_SWITCH_TIMEOUT_MSG
消息,发送REPORT_USER_SWITCH_MSG
消息的同时发送USER_SWITCH_TIMEOUT_MSG
消息是为了防止用户切换时间过长,毕竟只有所有的观察者都处理完了才能继续进行切换用户的操作。发送完USER_SWITCH_TIMEOUT_MSG
消息以后,如果后面没有进行清除该消息,那么时间一到就表示处理超时,就会调用timeoutUserSwitch()
方法进行超时处理,timeoutUserSwitch()
执行sendContinueUserSwitchLocked()
来结束切换工作,不再等待各个观察者处理任务的结束。
1 | void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) { |
上面startUser()
方法介绍完了,但是现在用户的状态还是在UserState.STATE_BOOTING
的状态,那么什么时候切换到UserState.STATE_RUNNING
状态呢?
更新用户状态,停止旧用户
在Activity
进入Idle状态后会调用AMS的activityIdle()
方法,此方法会调用mStackSupervisor.activityIdleInternalLocked(token, false, config);
,在activityIdleInternalLocked()
方法内有下面的处理:
1 | if (!booting) { |
当前有正在切换的用户的话就会调用AMS的finishUserSwitch()
和finishUserBoot()
方法,来更新用户状态,发送广播以及处理需要停止的用户工作。
下面来看一下 AMS 的finishUserSwitch()
和finishUserBoot()
方法:
1 | void finishUserSwitch(UserState uss) { |
通过finishUserSwitch
->finishUserBoot
来将用户的状态置为UserState.STATE_RUNNING
,并发出广播Intent.ACTION_BOOT_COMPLETED
广播。
至此,用户切换工作全部结束。
删除用户
UserManagerService
UserManagerService
中删除用户是在removeUser()
方法中实现的:
1 | public boolean removeUser(int userHandle) { |
ActivityManagerNative.getDefault().stopUser
执行完后 UMS 会继续执行删除工作。
1 | void finishRemoveUser(final int userHandle) { |
根据代码可以看到finishRemoveUser
方法只是发送了一个有序广播ACTION_USER_REMOVED,同时注册了一个广播接收器,这个广播接收器是最后一个接收到该广播的接收器,这样做的目的是让关心该广播的其他接收器处理完之后, UMS 才会进行删除用户的收尾工作,即调用removeUserStateLocked
来删除用户的相关文件。
1 | private void removeUserStateLocked(final int userHandle) { |
至此,删除用户的工作已经全部完成。
ActivityManagerService
UMS的removeUser()
会调用AMS的stopUser()
来处理停止用户的一些工作,在AMS内部也会调用stopUser()
。该方法在进行了权限检查之后,主要的工作都是由stopUserLocked()
来完成的。
1 | private int stopUserLocked(final int userId, final IStopUserCallback callback) { |
stopUserLocked()
方法首先检查当前的用户是否需要执行停止的工作,如果不需要直接调用参数的回调函数结束停止工作,如果需要,则先后发送Intent.ACTION_USER_STOPPING
和Intent.ACTION_SHUTDOWN
广播,方法中也注册了两个广播的接收器,在Intent.ACTION_SHUTDOWN
广播接收器中执行finishUserStop(uss)
方法。
1 | void finishUserStop(UserState uss) { |
finishUserStop()
方法从mStartedUsers
和mUserLru
列表中删除该用户,更新mStartedUserArray
列表,清理和该用户有关的进程,发送Intent.ACTION_USER_STOPPED
广播来通知该用户已经停止,接下来清除用户相关的Recent Task列表以及从mStackSupervisor
中删除用户的信息。
1 | private void forceStopUserLocked(int userId, String reason) { |
至此,AMS相关的删除用户的相关工作全部完成。
PackageManagerService
删除用户数据cleanUpUserLILPw()
1 | void cleanUpUserLILPw(UserManagerService userManager, int userHandle) { |
删除用户的工作比较简单,删除用户的数据。同时调用mSettings.removeUserLPw(userHandle)
来删除和 PMS 中和用户相关的信息。
1 | void removeUserLPw(int userId) { |