Android多用户
Context.startActivityAsUser(Intent, UserHandle)
Context.bindServiceAsUser(Intent, …, UserHandle)
Context.sendBroadcastAsUser(Intent, … , UserHandle)
Context.startServiceAsUser(Intent, …, UserHandle)
\android\os\UserHandle.java
/** @hide A user id to indicate all users on the device */ @UnsupportedAppUsage @TestApi public static final @UserIdInt int USER_ALL = -1; /** @hide A user handle to indicate all users on the device */ @SystemApi public static final @NonNull UserHandle ALL = new UserHandle(USER_ALL); /** @hide A user id to indicate the currently active user */ @UnsupportedAppUsage @TestApi public static final @UserIdInt int USER_CURRENT = -2; /** @hide A user handle to indicate the current user of the device */ @SystemApi public static final @NonNull UserHandle CURRENT = new UserHandle(USER_CURRENT); /** @hide A user id 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 */ @UnsupportedAppUsage public static final @UserIdInt int USER_CURRENT_OR_SELF = -3; /** @hide An undefined user id */ @UnsupportedAppUsage @TestApi public static final @UserIdInt int USER_NULL = -10000; private static final @NonNull UserHandle NULL = new UserHandle(USER_NULL); /** * @hide A user id constant to indicate the "owner" user of the device * @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}. */ @UnsupportedAppUsage @Deprecated public static final @UserIdInt int USER_OWNER = 0; /** * @hide A user handle to indicate the primary/owner user of the device * @deprecated Consider using either {@link UserHandle#SYSTEM} constant or * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}. */ @UnsupportedAppUsage @Deprecated public static final @NonNull UserHandle OWNER = new UserHandle(USER_OWNER); /** @hide A user id constant to indicate the "system" user of the device */ @UnsupportedAppUsage @TestApi public static final @UserIdInt int USER_SYSTEM = 0; /** @hide A user serial constant to indicate the "system" user of the device */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final int USER_SERIAL_SYSTEM = 0; /** @hide A user handle to indicate the "system" user of the device */ @SystemApi public static final @NonNull UserHandle SYSTEM = new UserHandle(USER_SYSTEM);
构建可感知多用户的应用
对于支持多用户的设备,其中的应用必须能够感知不同的用户。
某些应用需要将一些组件作为单例运行,并且可接受来自任意用户的请求。目前只有系统应用可以使用此功能。
这种便利功能有以下益处:
- 节约资源
- 可在用户间裁决一项或多项共享资源
- 由于使用单一服务器连接,可减少网络开销
下图描绘了涉及多用户时的权限流程。
启用单例组件
如需将应用标识为单例,请将 android:singleUser=”true”
添加至 Android 清单中的服务、接收器或提供程序。
系统会在仅以用户 0 身份运行的进程中将该组件实例化。任何用户若提出任何连接到上述提供程序或服务或者向上述接收器发送广播的请求,都会传送到以用户 0 身份运行的进程中。如果该组件是应用中的唯一组件,则只有一个应用实例会运行。
软件包中的活动仍将在每个用户各自的进程中启动,并且 UID 处于相应用户的 UID 范围内(例如 1010034)。
与用户互动
设置权限
需要下列权限:
INTERACT_ACROSS_USERS (signature|system) INTERACT_ACROSS_USERS_FULL (signature)
使用 API
使用下列 API 可使应用能够感知多个用户。
- 从传入的 Binder 调用中提取用户句柄:
int userHandle = UserHandle.getCallingUserId()
- 使用受保护的新 API 为特定用户启动服务、Activity 和广播:
Context.startActivityAsUser(Intent, UserHandle)
Context.bindServiceAsUser(Intent, …, UserHandle)
Context.sendBroadcastAsUser(Intent, … , UserHandle)
Context.startServiceAsUser(Intent, …, UserHandle)
UserHandle
可以是显式用户,也可以是以下特殊句柄之一:UserHandle.CURRENT
或UserHandle.ALL
。CURRENT
表示当前位于前台的用户。如果您想向所有用户发送广播,可以使用ALL
。 - 如需与您自己应用中的组件通信,请使用
(INTERACT_ACROSS_USERS)
;如需与其他应用中的组件通信,请使用(INTERACT_ACROSS_USERS_FULL)
- 您可能需要创建代理组件,这些代理组件先在用户进程中运行,之后会访问以用户 0 身份运行的
singleUser
组件。 - 使用新的
UserManager
系统服务查询用户及其句柄:UserManager.getUsers()
UserManager.getUserInfo()
UserManager.supportsMultipleUsers()
UserManager.getUserSerialNumber(int userHandle)
- 与用户句柄对应的不可再循环数字。UserManager.getUserHandle(int serialNumber)
UserManager.getUserProfiles()
- 返回用户本人个人资料和托管个人资料的集合(如有)。
- 注册即可借助 ContentObserver、PackageMonitor 和 BroadcastReceiver 上的新 API 监听特定或所有用户以及回调(可提供与回调发起用户相关的其他信息)。
多个用户或资料中的服务
并非所有服务都需要在其他用户或工作资料中运行实例。如果您的系统服务只需要以用户 0 的身份运行,则在以其他用户的身份运行时应停用该服务的组件,以帮助节省资源。下例显示了如何在服务的入口点执行此操作:
// Add on all entry points such as boot_completed or other manifest-listed receivers and providers if (!UserManager.isSystemUser()) { // Disable the service ComponentName targetServiceName = new ComponentName(this, TargetService.class); context.getPackageManager().setComponentEnabledSetting( targetServiceName, COMPONENT_ENABLED_STATE_DISABLED, 0); }