本开发者指南介绍了设备政策控制器 (DPC) 可以如何 管理专用设备上的多个 Android 用户。
概览
设备政策控制器 (DPC) 可帮助多人共用一台专用设备。您的设备政策控制器 (DPC) 在全托管式设备上运行时,可以创建和管理两类用户:
- 次要用户是指分别保存了不同应用和数据的 Android 用户 。您可以使用管理组件管理用户。这些用户是 在轮班开始时接起设备时很有用,例如 送货司机或安全工作者
- 临时用户是当用户被系统删除的次要用户 停止、切换离开或设备重新启动。这类用户对于支持请求非常有用 可在会话结束后删除数据,例如可公开访问的数据 互联网自助服务终端。
您可以使用现有 DPC 管理专用设备和辅助设备 用户。DPC 中的管理员组件将自己设为新的辅助资源的管理员 创建用户
次要用户的管理员必须与 全代管式设备为了简化开发,我们建议与 Google 员工共用一个管理员 设备和次要用户之间
在专用设备上管理多个用户通常需要 Android 9.0, 不过,本开发者指南中提及的一些方法 Android 的早期版本
附属用户
次要用户可以连接到 Wi-Fi 并配置新网络。不过, 无法修改或删除网络,即使是他们创建的网络也不可以。
创建用户
设备政策控制器 (DPC) 可以在后台创建其他用户,然后可进行切换 。无论是辅助资料还是资料 临时用户在拥有完整权限的 受管设备和次要用户:
- 调用
DevicePolicyManager.createAndManageUser()
。 如需创建临时用户,请添加 标志参数中的MAKE_USER_EPHEMERAL
。 - 致电
DevicePolicyManager.startUserInBackground()
至 在后台启动用户用户开始跑步,但您需要 完成设置,然后再将用户转到前台并向 与使用设备的人联系。 - 在次要用户的管理员中,调用
DevicePolicyManager.setAffiliationIds()
至 将新用户与主要用户关联起来。请参阅 DPC 协调。 - 返回全托管式设备的管理员界面,
DevicePolicyManager.switchUser()
,将用户切换到 前景。
以下示例展示了如何向 DPC 添加第 1 步:
Kotlin
val dpm = getContext().getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager // If possible, reuse an existing affiliation ID across the // primary user and (later) the ephemeral user. val identifiers = dpm.getAffiliationIds(adminName) if (identifiers.isEmpty()) { identifiers.add(UUID.randomUUID().toString()) dpm.setAffiliationIds(adminName, identifiers) } // Pass an affiliation ID to the ephemeral user in the admin extras. val adminExtras = PersistableBundle() adminExtras.putString(AFFILIATION_ID_KEY, identifiers.first()) // Include any other config for the new user here ... // Create the ephemeral user, using this component as the admin. try { val ephemeralUser = dpm.createAndManageUser( adminName, "tmp_user", adminName, adminExtras, DevicePolicyManager.MAKE_USER_EPHEMERAL or DevicePolicyManager.SKIP_SETUP_WIZARD) } catch (e: UserManager.UserOperationException) { if (e.userOperationResult == UserManager.USER_OPERATION_ERROR_MAX_USERS) { // Find a way to free up users... } }
Java
DevicePolicyManager dpm = (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); // If possible, reuse an existing affiliation ID across the // primary user and (later) the ephemeral user. Set<String> identifiers = dpm.getAffiliationIds(adminName); if (identifiers.isEmpty()) { identifiers.add(UUID.randomUUID().toString()); dpm.setAffiliationIds(adminName, identifiers); } // Pass an affiliation ID to the ephemeral user in the admin extras. PersistableBundle adminExtras = new PersistableBundle(); adminExtras.putString(AFFILIATION_ID_KEY, identifiers.iterator().next()); // Include any other config for the new user here ... // Create the ephemeral user, using this component as the admin. try { UserHandle ephemeralUser = dpm.createAndManageUser( adminName, "tmp_user", adminName, adminExtras, DevicePolicyManager.MAKE_USER_EPHEMERAL | DevicePolicyManager.SKIP_SETUP_WIZARD); } catch (UserManager.UserOperationException e) { if (e.getUserOperationResult() == UserManager.USER_OPERATION_ERROR_MAX_USERS) { // Find a way to free up users... } }
创建或启动新用户时,您可以检查失败的原因
方法是捕获 UserOperationException
异常并调用
getUserOperationResult()
。超出用户数量
是一些常见的失败原因:
创建用户可能需要一些时间。如果您经常创建用户,则可以 通过在后台准备好随时可用的用户来改善用户体验。 您可能需要在即时可用的用户的优势与最高 一台设备上允许的用户数。
身份认同
创建新用户后,您应使用永久性序列号来引用该用户。
数字。不要保留 UserHandle
,因为系统会在您进行回收时回收它们
创建和删除用户。通过调用
UserManager.getSerialNumberForUser()
:
Kotlin
// After calling createAndManageUser() use a device-unique serial number // (that isn’t recycled) to identify the new user. secondaryUser?.let { val userManager = getContext().getSystemService(UserManager::class.java) val ephemeralUserId = userManager!!.getSerialNumberForUser(it) // Save the serial number to storage ... }
Java
// After calling createAndManageUser() use a device-unique serial number // (that isn’t recycled) to identify the new user. if (secondaryUser != null) { UserManager userManager = getContext().getSystemService(UserManager.class); long ephemeralUserId = userManager.getSerialNumberForUser(secondaryUser); // Save the serial number to storage ... }
用户配置
您可以根据用户的需求自定义辅助实例的设置
用户。您可以在调用 createAndManageUser()
时添加以下标志:
SKIP_SETUP_WIZARD
- 不运行用于检查并安装更新的新用户设置向导, 提示用户添加 Google 账号和 Google 服务,并设置 屏幕锁定。此过程可能需要一些时间,可能并不适用于所有情况 用户,例如公共互联网自助服务终端。
LEAVE_ALL_SYSTEM_APPS_ENABLED
- 使新用户的所有系统应用保持启用状态。如果您未设置此标志 新用户只包含手机运行所需的最少应用集 通常是文件浏览器、电话拨号器、通讯录和短信。
遵循用户生命周期
设备政策控制器 (DPC)(如果是完全受管设备的管理员)可能会发现它对
次要用户何时发生变化如需在更改后运行后续任务,请替换
DPC 的 DeviceAdminReceiver
子类中的以下回调方法:
onUserStarted()
- 在系统启动用户后调用。此用户可能仍在设置或
它们都在后台运行您可以从
startedUser
获取该用户 参数。 onUserSwitched()
- 在系统切换到其他用户后调用。您可以获取新用户
现在从
switchedUser
参数在前台运行。 onUserStopped()
- 系统因用户已退出登录、切换到了
新用户(如果是临时用户),或者您的 DPC 停止了用户。您可以获得
从
stoppedUser
参数访问用户。 onUserAdded()
- 当系统添加新用户时调用。通常,次要用户
并完成完整设置。您可以从
newUser
参数。 onUserRemoved()
- 在系统删除用户后调用。由于该用户已被删除,
则无法访问由
removedUser
参数表示的用户。
为了了解系统何时将用户带到前台,或何时将用户送到
应用可以注册一个接收器来接收
ACTION_USER_FOREGROUND
和
ACTION_USER_BACKGROUND
广播。
发现用户
如要获取所有次要用户,完全受管设备的管理员可以调用
DevicePolicyManager.getSecondaryUsers()
。取得的成效
包括管理员创建的所有次要或临时用户。结果还
添加任何次要用户(或访客用户),包括使用设备的人可能
资源。结果不包含工作资料,因为它们
次要用户以下示例展示了如何使用此方法:
Kotlin
// The device is stored for the night. Stop all running secondary users. dpm.getSecondaryUsers(adminName).forEach { dpm.stopUser(adminName, it) }
Java
// The device is stored for the night. Stop all running secondary users. for (UserHandle user : dpm.getSecondaryUsers(adminName)) { dpm.stopUser(adminName, user); }
您还可以调用以下方法来查找次要用户的状态:
DevicePolicyManager.isEphemeralUser()
- 向次要用户的管理员调用此方法,以了解这是否属于 临时用户。
DevicePolicyManager.isAffiliatedUser()
- 向次要用户的管理员调用此方法,以了解用户是否 与主要用户相关联的各个 ID。要详细了解联属关系,请参阅设备政策控制器 (DPC) 协调性。
用户管理
如果您想完全管理用户生命周期,可以调用 API 来 对设备更改用户的时间和方式进行精细控制。例如,您 可以在设备一段时间未使用时删除用户,或者 在轮班结束之前将任何未发送的订单发送到服务器。
退出
Android 9.0 在锁定的屏幕上添加了一个退出按钮, 设备可以结束会话。轻击按钮后,系统会停止 次要用户,如果是临时用户,则删除用户,然后主要用户返回 。当主要用户处于 处于前台,因为主要用户无法退出。
默认情况下,Android 不会显示结束会话按钮,但您的管理员(或
全托管式设备)可通过调用
DevicePolicyManager.setLogoutEnabled()
。如果您需要
确认按钮的当前状态,调用
DevicePolicyManager.isLogoutEnabled()
。
次要用户的管理员可以以编程方式退出该用户,然后再返回
提供给主要用户首先,确认次要用户和主要用户
关联,然后调用 DevicePolicyManager.logoutUser()
。如果
已退出的用户是临时用户,系统会停止,然后删除
用户。
切换用户
如需切换到其他次要用户,完全受管设备的管理员可以
调用 DevicePolicyManager.switchUser()
。为方便起见,
可以传递 null
以切换到主要用户。
停止用户
如需停止次要用户,拥有全托管式设备的 DPC 可以调用
DevicePolicyManager.stopUser()
。如果已停止的用户是
临时用户,系统会停止该用户,然后将其删除。
我们建议您尽可能禁止用户,以免用户超出设备 运行用户数量上限。
删除用户
如需永久删除次要用户,DPC 可以调用以下其中一项:
DevicePolicyManager
方法:
- 完全受管设备的管理员可以调用
removeUser()
。 - 次要用户的管理员可以调用
wipeData()
。
系统会在临时用户退出、停止或切换后将其删除 距离。
停用默认界面
如果您的 DPC 提供了用于管理用户的界面,您可以停用 Android 的内置功能
多用户界面为此,您可以调用
DevicePolicyManager.setLogoutEnabled()
并添加
DISALLOW_USER_SWITCH
限制,如
示例:
Kotlin
// Explicitly disallow logging out using Android UI (disabled by default). dpm.setLogoutEnabled(adminName, false) // Disallow switching users in Android's UI. This DPC can still // call switchUser() to manage users. dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH)
Java
// Explicitly disallow logging out using Android UI (disabled by default). dpm.setLogoutEnabled(adminName, false); // Disallow switching users in Android's UI. This DPC can still // call switchUser() to manage users. dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH);
设备使用者无法通过 Android 内置界面添加次要用户
因为全托管式设备的管理员会自动将
DISALLOW_ADD_USER
用户限制。
会话消息
当使用设备的用户切换到新用户时,Android 会显示一个面板 选中该开关Android 会显示以下消息:
- 当设备切换到辅助服务器时显示的启动用户会话消息 与主要用户区分开来
- 当设备返回给主要用户时显示的最终用户会话消息 来自次要用户
在两个次要用户之间切换时,系统不会显示消息。
由于这些消息可能并非适合所有情况,因此您可以更改 这些消息的文本。例如,如果您的解决方案使用临时用户 您可以在消息中反映这一点,例如:Stopping browser(正在关闭浏览器) 会话和正在删除个人数据...
该消息只会显示几秒钟,因此每条消息
就是一个简短明确的词组如需自定义消息,您的管理员可以致电
DevicePolicyManager
方法
setStartUserSessionMessage()
和
setEndUserSessionMessage()
,如
示例:
Kotlin
// Short, easy-to-read messages shown at the start and end of a session. // In your app, store these strings in a localizable resource. internal val START_USER_SESSION_MESSAGE = "Starting guest session…" internal val END_USER_SESSION_MESSAGE = "Stopping & clearing data…" // ... dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE) dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE)
Java
// Short, easy-to-read messages shown at the start and end of a session. // In your app, store these strings in a localizable resource. private static final String START_USER_SESSION_MESSAGE = "Starting guest session…"; private static final String END_USER_SESSION_MESSAGE = "Stopping & clearing data…"; // ... dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE); dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE);
传递 null
即可删除您的自定义消息并返回 Android 的默认设置
消息。如果您需要查看当前短信内容,请致电
getStartUserSessionMessage()
或
getEndUserSessionMessage()
。
您的 DPC 应设置本地化消息 用户当前的语言区域。您还需要在 用户的语言区域更改:
Kotlin
override fun onReceive(context: Context?, intent: Intent?) { // Added the <action android:name="android.intent.action.LOCALE_CHANGED" /> // intent filter for our DeviceAdminReceiver subclass in the app manifest file. if (intent?.action === ACTION_LOCALE_CHANGED) { // Android's resources return a string suitable for the new locale. getManager(context).setStartUserSessionMessage( getWho(context), context?.getString(R.string.start_user_session_message)) getManager(context).setEndUserSessionMessage( getWho(context), context?.getString(R.string.end_user_session_message)) } super.onReceive(context, intent) }
Java
public void onReceive(Context context, Intent intent) { // Added the <action android:name="android.intent.action.LOCALE_CHANGED" /> // intent filter for our DeviceAdminReceiver subclass in the app manifest file. if (intent.getAction().equals(ACTION_LOCALE_CHANGED)) { // Android's resources return a string suitable for the new locale. getManager(context).setStartUserSessionMessage( getWho(context), context.getString(R.string.start_user_session_message)); getManager(context).setEndUserSessionMessage( getWho(context), context.getString(R.string.end_user_session_message)); } super.onReceive(context, intent); }
DPC 协调
管理次要用户通常需要两个 DPC 实例:一个拥有 完全受管设备,而另一个用户拥有次要用户。创建 新用户时,完全受管设备的管理员会将另一个实例 作为新用户的管理员。
关联用户
本开发者指南中的某些 API 只有在次要用户 具有关联性。由于 Android 会停用某些功能 (例如网络日志记录)将新的非关联次要用户添加到 您应尽快关联用户。有关示例,请参见 设置。
设置
先通过拥有次要用户的 DPC 设置新的次要用户,然后再进行设置
供用户使用您可以在以下位置进行此设置:
DeviceAdminReceiver.onEnabled()
回调。如果您以前
在对 createAndManageUser()
的调用中设置任何管理员 extra,即可获取
intent
参数的值。以下示例展示了 DPC 关联
在回调函数中添加新的次要用户:
Kotlin
override fun onEnabled(context: Context?, intent: Intent?) { super.onEnabled(context, intent) // Get the affiliation ID (our DPC previously put in the extras) and // set the ID for this new secondary user. intent?.getStringExtra(AFFILIATION_ID_KEY)?.let { val dpm = getManager(context) dpm.setAffiliationIds(getWho(context), setOf(it)) } // Continue setup of the new secondary user ... }
Java
public void onEnabled(Context context, Intent intent) { // Get the affiliation ID (our DPC previously put in the extras) and // set the ID for this new secondary user. String affiliationId = intent.getStringExtra(AFFILIATION_ID_KEY); if (affiliationId != null) { DevicePolicyManager dpm = getManager(context); dpm.setAffiliationIds(getWho(context), new HashSet<String>(Arrays.asList(affiliationId))); } // Continue setup of the new secondary user ... }
DPC 之间的 RPC
即使这两个 DPC 实例在不同的用户下运行,DPC 也
拥有设备的次要用户可以相互通信。
由于调用其他 DPC 服务会跨越用户边界,因此您的 DPC 无法
调用 bindService()
,通常在
Android 版应用。要绑定到
其他用户, 致电
DevicePolicyManager.bindDeviceAdminServiceAsUser()
。
DPC 只能绑定到
DevicePolicyManager.getBindDeviceAdminTargetUsers()
。
以下示例显示了与管理员绑定的次要用户的管理员
:
Kotlin
// From a secondary user, the list contains just the primary user. dpm.getBindDeviceAdminTargetUsers(adminName).forEach { // Set up the callbacks for the service connection. val intent = Intent(mContext, FullyManagedDeviceService::class.java) val serviceconnection = object : ServiceConnection { override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) { // Call methods on service ... } override fun onServiceDisconnected(componentName: ComponentName) { // Clean up or reconnect if needed ... } } // Bind to the service as the primary user [it]. val bindSuccessful = dpm.bindDeviceAdminServiceAsUser(adminName, intent, serviceconnection, Context.BIND_AUTO_CREATE, it) }
Java
// From a secondary user, the list contains just the primary user. List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(adminName); if (targetUsers.isEmpty()) { // If the users aren't affiliated, the list doesn't contain any users. return; } // Set up the callbacks for the service connection. Intent intent = new Intent(mContext, FullyManagedDeviceService.class); ServiceConnection serviceconnection = new ServiceConnection() { @Override public void onServiceConnected( ComponentName componentName, IBinder iBinder) { // Call methods on service ... } @Override public void onServiceDisconnected(ComponentName componentName) { // Clean up or reconnect if needed ... } }; // Bind to the service as the primary user. UserHandle primaryUser = targetUsers.get(0); boolean bindSuccessful = dpm.bindDeviceAdminServiceAsUser( adminName, intent, serviceconnection, Context.BIND_AUTO_CREATE, primaryUser);
其他资源
如需详细了解专用设备,请参阅以下文档: