android从4.2开始便添加了多用户功能,其具体的管理者为UserManager.

多用户模式的启用

系统判断当前设备是否支持多用户模式的依据是配置文件config.xml 中的

config_multiuserMaximumUsers 和config_enableMultiUserUI 配置项。

[html]  view plain  copy
  1.   
  2. <integer name="config_multiuserMaximumUsers">4integer>  
  3.   
  4. <bool name="config_enableMultiUserUI">truebool>  

前者取值为整型,决定着当前设备支持的最大用户上限。默认值为1,即不支持多用户。

后者取值为布尔型,默认值为false。

如需启用多用户,则设置前者为大于1 的值,后者为true。

从上面的配置可知,当前设备已经打开多用户功能,且支持最大用户数为4,该值也可以用pm命令获取。

当然,在代码中的判断则是在UserManager.java中:

[java]  view plain  copy
  1. public static int getMaxSupportedUsers() {  
  2.     // Don't allow multiple users on certain builds  
  3.     if (android.os.Build.ID.startsWith("JVP")) return 1;  
  4.     // Svelte devices don't get multi-user.  
  5.     if (ActivityManager.isLowRamDeviceStatic()) return 1;  
  6.     return SystemProperties.getInt("fw.max_users",  
  7.             Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));  
  8. }  
[java]  view plain  copy
  1. public static boolean supportsMultipleUsers() {  
  2.     return getMaxSupportedUsers() > 1  
  3.             && SystemProperties.getBoolean("fw.show_multiuserui",  
  4.             Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI));  
  5. }  

多用户的操作目前未对普通应用开放,其相关API 都有hide 注解,并需要system权限。

此外,用户的添加和移除还需要android.Manifest.permission.MANAGE_USERS权限。

添加/切换/删除用户

为每个人创建一个用户,你可以很容易地与家人和朋友分享你的设备(手机/平板等),每个人可以拥有一块独立的设备空间,自定义屏幕、设定账户、应用、设置等其他更多的操作。这里我们只介绍主要的用户操作,添加、切换和删除,其他用户操作可以自行尝试。

添加用户

要创建一个新用户/访客,你必须处于机主模式下,能创建的用户数取决于设备的max users。

上面提到的pm命令可以用于查询当前设备支持的最大用户数。

打开settings-->users-->add user

Android多用户原理_第1张图片

点击“添加用户”,点击“确定”。

Android多用户原理_第2张图片

点击“立即设置”,切换到新用户,设置账户等其他细节。点击“以后再说”,下次切换到新用户时,再进行账户等其他细节设置。

Android多用户原理_第3张图片

切换用户

(1)第一种方式:
1.从设备顶端向下滑动屏幕,打开快速设置菜单,点击右上角的用户切换按钮。

2.在用户列表中,点击要切换的用户。

Android多用户原理_第4张图片

2)第二中方式:
1.打开设置。

2.点击“设备”列表下的“用户”,在用户列表中,点击要切换的用户。

删除同理

添加原理

用户添加是通过调用UserManager 的public UserInfo createUser(String name, int flags)方法进行的。

其具体实现在UserManagerService 的同名方法中。

[java]  view plain  copy
  1. public UserInfo createUser(String name, int flags) {  
  2.     try {  
  3.         return mService.createUser(name, flags);  
  4.     } catch (RemoteException re) {  
  5.         Log.w(TAG, "Could not create a user", re);  
  6.         return ;  
  7.     }  
  8. }  
[java]  view plain  copy
  1. // UserManagerService.java  
  2. @Override  
  3. public UserInfo createUser(String name, int flags) {  
  4.     checkManageUsersPermission("Only the system can create users");  
  5.     return createUserInternal(name, flags, UserHandle.USER_NULL);  
  6. }  

调用内部的createUserInternal方法:

[java]  view plain  copy
  1. private UserInfo createUserInternal(String name, int flags, int parentId) {  
  2.         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(  
  3.                 UserManager.DISALLOW_ADD_USER, false)) {  
  4.             Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");  
  5.             return ;  
  6.         }  
  7.         if (ActivityManager.isLowRamDeviceStatic()) {  
  8.             return ;  
  9.         }  
  10.         final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;  
  11.         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;  
  12.         final long ident = Binder.clearCallingIdentity();  
  13.         UserInfo userInfo = ;  
  14.         final int userId;  
  15.         try {  
  16.             synchronized (mInstallLock) {  
  17.                 synchronized (mPackagesLock) {  
  18.                     UserInfo parent = ;  
  19.                     if (parentId != UserHandle.USER_NULL) {  
  20.                         parent = getUserInfoLocked(parentId);  
  21.                         if (parent == return ;  
  22.                     }  
  23.                     if (isManagedProfile && !canAddMoreManagedProfiles()) {  
  24.                         return ;  
  25.                     }  
  26.                     if (!isGuest && !isManagedProfile && isUserLimitReachedLocked()) {  
  27.                         // If we're not adding a guest user or a managed profile and the limit has  
  28.                         // been reached, cannot add a user.  
  29.                         return ;  
  30.                     }  
  31.                     // If we're adding a guest and there already exists one, bail.  
  32.                     if (isGuest && findCurrentGuestUserLocked() != ) {  
  33.                         return ;  
  34.                     }  
  35.                     userId = getNextAvailableIdLocked();  
  36.                     userInfo = new UserInfo(userId, name, , flags);  
  37.                     userInfo.serialNumber = mNextSerialNumber++;  
  38.                     long now = System.currentTimeMillis();  
  39.                     userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;  
  40.                     userInfo.partial = true;  
  41.                     Environment.getUserSystemDirectory(userInfo.id).mkdirs();  
  42.                     mUsers.put(userId, userInfo);  
  43.                     writeUserListLocked();  
  44.                     if (parent != ) {  
  45.                         if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {  
  46.                             parent.profileGroupId = parent.id;  
  47.                             scheduleWriteUserLocked(parent);  
  48.                         }  
  49.                         userInfo.profileGroupId = parent.profileGroupId;  
  50.                     }  
  51.                     final StorageManager storage = mContext.getSystemService(StorageManager.class);  
  52.                     for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {  
  53.                         final String volumeUuid = vol.getFsUuid();  
  54.                         try {  
  55.                             final File userDir = Environment.getDataUserDirectory(volumeUuid,  
  56.                                     userId);  
  57.                             prepareUserDirectory(mContext, volumeUuid, userId);  
  58.                             enforceSerialNumber(userDir, userInfo.serialNumber);  
  59.                         } catch (IOException e) {  
  60.                             Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);  
  61.                         }  
  62.                     }  
  63.                     mPm.createNewUserLILPw(userId);  
  64.                     userInfo.partial = false;  
  65.                     scheduleWriteUserLocked(userInfo);  
  66.                     updateUserIdsLocked();  
  67.                     Bundle restrictions = new Bundle();  
  68.                     mUserRestrictions.append(userId, restrictions);  
  69.                 }  
  70.             }  
  71.             mPm.newUserCreated(userId);  
  72.             if (userInfo != ) {  
  73.                 Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);  
  74.                 addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);  
  75.                 mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,  
  76.                         android.Manifest.permission.MANAGE_USERS);  
  77.             }  
  78.         } finally {  
  79.             Binder.restoreCallingIdentity(ident);  
  80.         }  
  81.         return userInfo;  
  82.     }  
在调用时,系统进行如下操作:
1.检查调用者是否具有所需权限。
2.对安装和软件包流程加锁,保证线程安全。
3.检查多用户环境是否到达用户数量限制。如果没有,创建用户实例。
4.为新用户创建相关目录。
5.序列化用户列表。

6. 发送用户建立广播, MountService 在收到此广播后, 调用createEmulatedVolumeForUserLocked 方法为用户建立相应的数据目录。

用户保存

用户创建后,会首先在/data/system/users/userlist.xml 文件中保存新增加用户的id,创建/data/system/users/ 用户id 目录,并将用户信息保存至其下的用户id.xml 文件中。

其内容包括一些基本的用户信息。

/data/system/users/userlist.xml:

[html]  view plain  copy
  1. <?xml version='1.0' encoding='utf-8' standalone='yes' ?>  
  2. <users nextSerialNumber="11" version="5">  
  3.     <guestRestrictions>  
  4.         <restrictions no_outgoing_calls="true" no_sms="true" />  
  5.     guestRestrictions>  
  6.     <user id="0" />  
  7.     <user id="10" />  
  8. users>  

用户切换流程

用户切换是通过调用ActivityManager 的public boolean switchUser(int userId)方法进行。一般通过 ActivityManagerNative.getDefault().switchUser(int userId)进行调用。
在调用时,系统进行以下操作:
1.检查调用者是否具有所需权限。
2.获取切换目标用户信息,并设定当前用户为目标用户
3.WindowsManagerService 设置当前用户,锁定屏幕
4.切换目标用户状态至启动
5.广播REPORT_USER_SWITCH_MSG 和USER_SWITCH_TIMEOUT_MSG 消息,设定用户切换和切换超时时间(2 秒),此超时时间用于限定REPORT_USER_SWITCH_MSG 广播全程时间。
6.切换Activity 堆栈至当前用户
7.广播ACTION_USER_SWITCHED 消息,各接收组件进行相应操作。 大部分具体操作,

比如存储目录切换、安全设置切换等,都在此广播后进行。

用户移除流程

用户移除是通过调用UserManager 的 public boolean removeUser(int userHandle)方法进行的。其具体实现同样是在UserManagerService 的同名方法中。
在调用时,系统进行如下操作:
1.检查调用者是否具有所需权限。
2.对软件包变化加锁
3.将用户id 加入待移除用户列表,将用户状态设为partial,这样,在下次系统启
动时,会清除此用户。
4.停止用户,杀掉用户相关进程。
5.发送用户移除的广播。广播成功后,删除用户描述文件和数据文件。

6.序列化用户列表

多用户的API接口

与其它系统服务的实现类似,用户管理也采用了经由Binder 调用的远程服务机制。

UserManager 为暴露给用户的接 口,UserManagerService 为接口的底层实现。

UserManager

UserManager 是暴露出来的应用程序接口。对于普通应用程序,提供用户数查询,用户状态判断和用户序列号查询等基本功能。普通应用没有用户操作权限。

对于系统应用,UserManager 提供了创建/删除/擦除用户、用户信息获取、用户句柄获取等用户操作的接口。均由远程调用UserManagerService 服务的对应方法实现。

UserManagerService

与其它大部分Service 一样,UserManagerService 的实现采用了单例模式。在服务中,由组成为UserInfo 类的散列表mUsers 维护所有的用户状态。

mUsers 在系统启动时由/data/system/users/userlist.xml 读取生成,并在运行期间动态改变。所有用户的添加、删除操作,都最终序列化回此文件中。

ActivityManagerService

ActivityManagerService 加入了多用户支持。负责维护设备中存在的所有用户状态。

服务以下述变量来记录当前处于“启动”状态的用户。

用户的启动状态对象为com.android.server.am.UserStartedState。其中指定的用

户状态有四种:

[java]  view plain  copy
  1. // User is first coming up.  
  2. public final static int STATE_BOOTING = 0;//用户启动  
  3. // User is in the normal running state.  
  4. public final static int STATE_RUNNING = 1;//运行中  
  5. // User is in the initial process of being stopped.  
  6. public final static int STATE_STOPPING = 2;//停止中  
  7. // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN.  
  8. public final static int STATE_SHUTDOWN = 3;//用户关闭状态  

完整的用户生命周期为:BOOTING->RUNNING->STOPPING->SHUTDOWN

用户必须处于RUNNING 状态时,才能作为切换的目标用户。所以在用户切换流程中,

首先要判断当前用户的状态, 并启动STOPPING/SHUTDOWN 状态的用户。

多用户模式的牵涉面

1锁屏界面

用户最先体验到多用户的入口位置即为锁屏界面。锁屏界面中加入了用户切换组件:

KeyguardMultiUserSelectorView 类。

该类在设备允许多用户存在的情况下,显示当前所有用户的列表。并在用户进行选择后,调用 ActivityManagerNative.getDefault().switchUser(int userId)方法进行用户切换。

2外部存储

对于每个用户,Android 都为其分配了单独的存储空间。标准的支持多用户的外部存储空间是由init.rc 定义的环境变量所指定。

[java]  view plain  copy
  1. # See storage config details at 
  2. href="http://source.android.com/tech/storage/">http://source.android.com/tech/  
  3. storage/  
  4. mkdir /mnt/shell/emulated 0700 shell shell  
  5. mkdir /storage/emulated 0555 root root  
  6. export EXTERNAL_STORAGE /storage/emulated/legacy  
  7. export EMULATED_STORAGE_SOURCE /mnt/shell/emulated  
  8. export EMULATED_STORAGE_TARGET /storage/emulated  
  9. # Support legacy paths  
  10. symlink /storage/emulated/legacy /sdcard  
  11. symlink /storage/emulated/legacy /mnt/sdcard  
  12. symlink /storage/emulated/legacy /storage/sdcard0  
  13. 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 为主用户的外部存储路径。

3包管理器PackageManagerService

在多用户环境下,所有用户安装的应用仍然同以前一样,放置于/data/app 目录下。
但原先/data/data 的数据存储位置目前仅对主用户有效,其余用户的数据存储目录则位于/data/user/用户id/目录下。此目录的创建是在创建用户时由前述的MountService 完成的。


对于每个用户,系统都会以PackageuserState 类来维护其安装的软件状态。此列表以散列表的形式存在,由 PackageSettingBase 类维护。所有的包--用户关系和状态最终仍然序列化至/data/system/package.xml 中, 并保留 /data/system/packagebackup.xml 作为备份。

UserHandle

1.所有用户都可以接收到

[java]  view plain  copy
  1. /** @hide A user handle to indicate all users on the device */ public static  
  2. final UserHandle ALL = new UserHandle(USER_ALL);  

2.当前用户可以接收到

[java]  view plain  copy
  1. /** @hide A user handle to indicate the current user of the device */  
  2. public static final UserHandle CURRENT = new UserHandle(USER_CURRENT);  

3.当前用户或者该应用所属用户可以接收到

[java]  view plain  copy
  1. /** @hide A user handle to indicate that we would like to send to the current 
  2. * user, but if this is calling from a user process then we will send it * to 
  3. the caller's user instead of failing with a security exception */  
  4. public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF);  

3.设备所有者(机主)可以接收到

[java]  view plain  copy
  1. /** @hide A user handle to indicate the primary/owner user of the device */  
  2. public static final UserHandle OWNER = new UserHandle(USER_OWNER); 

更多相关文章

  1. Android判断App前台运行还是后台运行(运行状态)
  2. Android Studio如何实现隐藏标题栏和状态栏:
  3. Android非主用户无线相关设置的显示
  4. Android 设备接入小票打印机 爱宝小票打印机
  5. Android开发——本地存储、用户权限获取
  6. 创建灵活的用户界面-Android Fragment
  7. Android 设备屏幕信息(dip,sp,px的转换及其运用)
  8. Android中如何实现高亮显示即选中状态

随机推荐

  1. Android+Eclipse环境
  2. Android的源代码结构
  3. Android(安卓)编译时注解
  4. android 纯c/c++开发
  5. android布局学习利器-Hierarchy Viewer
  6. Android摄像头--通过Intent启动
  7. AndroidManifest.xml中 andorid 版本号 v
  8. Windows上搭建android开发环境
  9. 系出名门Android(8) - 控件(View)之TextS
  10. android 电容屏(二):驱动调试之基本概念篇