管理多个用户

本开发者指南介绍了设备政策控制器 (DPC) 可以如何 管理专用设备上的多个 Android 用户。

概览

设备政策控制器 (DPC) 可帮助多人共用一台专用设备。您的设备政策控制器 (DPC) 在全托管式设备上运行时,可以创建和管理两类用户:

  • 次要用户是指分别保存了不同应用和数据的 Android 用户 。您可以使用管理组件管理用户。这些用户是 在轮班开始时接起设备时很有用,例如 送货司机或安全工作者
  • 临时用户是当用户被系统删除的次要用户 停止、切换离开或设备重新启动。这类用户对于支持请求非常有用 可在会话结束后删除数据,例如可公开访问的数据 互联网自助服务终端。

您可以使用现有 DPC 管理专用设备和辅助设备 用户。DPC 中的管理员组件将自己设为新的辅助资源的管理员 创建用户

主要用户和两个次要用户。
图 1. 由管理员管理的主要和次要用户 同一设备政策控制器 (DPC)

次要用户的管理员必须与 全代管式设备为了简化开发,我们建议与 Google 员工共用一个管理员 设备和次要用户之间

在专用设备上管理多个用户通常需要 Android 9.0, 不过,本开发者指南中提及的一些方法 Android 的早期版本

附属用户

次要用户可以连接到 Wi-Fi 并配置新网络。不过, 无法修改或删除网络,即使是他们创建的网络也不可以。

创建用户

设备政策控制器 (DPC) 可以在后台创建其他用户,然后可进行切换 。无论是辅助资料还是资料 临时用户在拥有完整权限的 受管设备和次要用户:

  1. 调用 DevicePolicyManager.createAndManageUser()。 如需创建临时用户,请添加 标志参数中的 MAKE_USER_EPHEMERAL
  2. 致电 DevicePolicyManager.startUserInBackground()至 在后台启动用户用户开始跑步,但您需要 完成设置,然后再将用户转到前台并向 与使用设备的人联系。
  3. 在次要用户的管理员中,调用 DevicePolicyManager.setAffiliationIds()至 将新用户与主要用户关联起来。请参阅 DPC 协调
  4. 返回全托管式设备的管理员界面, 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_FOREGROUNDACTION_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()

调用 RPC 的主要用户和两个关联的次要用户。
图 2. 已关联的主要和次要用户的管理员 调用服务方法

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);

其他资源

如需详细了解专用设备,请参阅以下文档: