锁定任务模式

本开发者指南解释了如何将专用设备锁定到单个应用或一组应用。如果您是企业移动管理 (EMM) 开发者或解决方案集成商,请阅读本指南以将锁定任务模式添加到您的解决方案中。

概览

Android 可以以沉浸式、类似信息亭的方式运行任务,这称为锁定任务模式。如果您正在开发信息亭应用或启动器以呈现应用集合,则可能会使用锁定任务模式。当系统在锁定任务模式下运行时,设备用户通常无法看到通知、访问未列入许可名单的应用或返回主屏幕(除非主屏幕已列入许可名单)。

只有经设备政策控制器 (DPC) 列入许可名单的应用才能在系统处于锁定任务模式时运行。应用之所以列入许可名单,是因为使用设备的人员并非总是能够退出锁定任务模式。

如何组合用于锁定任务模式的应用许可名单和许可名单 DPC 将取决于您要解决的问题。以下是一些示例:

  • 一个单一的应用包,它结合了信息亭(用于呈现内容)和迷你 DPC(用于将自身列入锁定任务模式的许可名单)。
  • 作为企业移动管理解决方案一部分的 DPC,以锁定任务模式启动客户的移动应用。

可用性

系统可以在 Android 5.0 或更高版本中以锁定任务模式运行。表 1 显示了支持按用户将应用列入许可名单的 Android 版本。

表 1. Android 版本对 DPC 管理模式的支持
Android 版本 DPC 管理 备注
Android 5.0 (API level 21) 或更高版本 完全托管设备
Android 8.0 (API level 26) 或更高版本 关联的辅助用户 辅助用户必须与主要用户关联。请参阅多用户概览。
Android 9.0 (API level 28) 或更高版本 辅助用户

在 Android 9.0 或更高版本中,DPC 可以将任何应用活动启动到锁定任务模式。在早期版本中,应用必须已支持在其自身活动中启动锁定任务模式。

将应用列入许可名单

DPC 必须先将应用列入许可名单,然后才能在锁定任务模式下使用它们。调用 DevicePolicyManager.setLockTaskPackages(),将应用列入锁定任务模式的许可名单,如以下示例所示:

Kotlin

// Allowlist two apps.
private val KIOSK_PACKAGE = "com.example.kiosk"
private val PLAYER_PACKAGE = "com.example.player"
private val APP_PACKAGES = arrayOf(KIOSK_PACKAGE, PLAYER_PACKAGE)

// ...

val context = context
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
val adminName = getComponentName(context)
dpm.setLockTaskPackages(adminName, APP_PACKAGES)

Java

// Allowlist two apps.
private static final String KIOSK_PACKAGE = "com.example.kiosk";
private static final String PLAYER_PACKAGE = "com.example.player";
private static final String[] APP_PACKAGES = {KIOSK_PACKAGE, PLAYER_PACKAGE};

// ...

Context context = getContext();
DevicePolicyManager dpm =
    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminName = getComponentName(context);
dpm.setLockTaskPackages(adminName, APP_PACKAGES);

要找出之前已列入锁定任务模式许可名单的应用,DPC 可以调用 DevicePolicyManager.getLockTaskPackages()。其他应用可以调用 DevicePolicyManager.isLockTaskPermitted() 以确认应用包支持锁定任务模式。

启动锁定任务模式

在 Android 9.0 (API level 28) 或更高版本中,您可以在锁定任务模式下启动另一个应用的活动。如果某个活动已在前台或后台运行,则需要重新启动该活动。调用 ActivityOptions.setLockTaskEnabled() 并在启动活动时提供这些选项。以下代码段显示了一种实现方式:

Kotlin

// Set an option to turn on lock task mode when starting the activity.
val options = ActivityOptions.makeBasic()
options.setLockTaskEnabled(true)

// Start our kiosk app's main activity with our lock task mode option.
val packageManager = context.packageManager
val launchIntent = packageManager.getLaunchIntentForPackage(KIOSK_PACKAGE)
if (launchIntent != null) {
    context.startActivity(launchIntent, options.toBundle())
}

Java

// Set an option to turn on lock task mode when starting the activity.
ActivityOptions options = ActivityOptions.makeBasic();
options.setLockTaskEnabled(true);

// Start our kiosk app's main activity with our lock task mode option.
PackageManager packageManager = context.getPackageManager();
Intent launchIntent = packageManager.getLaunchIntentForPackage(KIOSK_PACKAGE);
if (launchIntent != null) {
  context.startActivity(launchIntent, options.toBundle());
}

在 Android 9.0 之前的版本中,应用通过调用 Activity.startLockTask() 在锁定任务模式下启动自己的活动。要调用此方法,活动必须在前台运行(请参阅活动生命周期概念),因此我们建议在 ActivityFragmentonResume() 方法中调用。以下是如何调用 startLockTask()

Kotlin

// In our Fragment subclass.
override fun onResume() {
    super.onResume()
    // First, confirm that this package is allowlisted to run in lock task mode.
    if (dpm.isLockTaskPermitted(context.packageName)) {
        activity.startLockTask()
    } else {
        // Because the package isn't allowlisted, calling startLockTask() here
        // would put the activity into screen pinning mode.
    }
}

Java

// In our Fragment subclass.
@Override
public void onResume() {
  super.onResume();

  // First, confirm that this package is allowlisted to run in lock task mode.
  if (dpm.isLockTaskPermitted(context.getPackageName())) {
    getActivity().startLockTask();
  } else {
    // Because the package isn't allowlisted, calling startLockTask() here
    // would put the activity into screen pinning mode.
  }
}

设备锁定时不要启动锁定任务模式,因为用户可能无法解锁设备。您可以调用 KeyguardManager 方法来判断设备是否锁定,并使用 Activity 生命周期回调(例如解锁后调用的 onResume())来启动锁定任务模式。

锁定任务模式中的应用可以启动新的活动,只要该活动不启动新的任务——除了启动列入许可名单的应用的任务。要了解任务与活动的关系,请阅读指南了解任务和返回栈

或者,您可以在应用清单文件中声明当系统在锁定任务模式下运行时活动的表现。要让系统自动在锁定任务模式下运行您的活动,请将 android:lockTaskMode 属性设置为 if_whitelisted,如以下示例所示:

<activity
    android:name=".MainActivity"
    android:lockTaskMode="if_whitelisted">
    <!-- ... -->
</activity>

您可以阅读 lockTaskMode 参考,了解有关在应用清单文件中声明选项的更多信息。

停止锁定任务模式

DPC 可以通过从许可名单中移除应用包来远程停止锁定任务模式。在 Android 6.0 (API level 23) 或更高版本中,调用 DevicePolicyManager.setLockTaskPackages(),并从许可名单数组中省略包名称。当您更新许可名单时,应用将返回堆栈中的上一个任务。

如果某个活动之前调用了 startLockTask(),则该活动可以调用 Activity.stopLockTask() 来停止锁定任务模式。此方法仅适用于启动锁定任务模式的活动。

生命周期回调

DPC 可能会发现了解应用(在同一用户中运行)何时进入和退出锁定任务模式很有用。要接收回调,请在 DPC 的 DeviceAdminReceiver 子类中覆盖以下回调方法:

onLockTaskModeEntering()
在应用进入锁定任务模式后调用。您可以从 pkg 参数中获取应用的包名。
onLockTaskModeExiting()
在应用退出锁定任务模式后调用。此回调不接收有关应用的信息。

如果您将另一个应用启动到锁定任务模式,则需要在自己的应用中跟踪运行状态。要检查当前应用是否在锁定任务模式下运行,请使用 ActivityManager 上的方法,如以下示例所示:

Kotlin

// Check if this app is in lock task mode. Screen pinning doesn't count.
var isLockTaskModeRunning = false

val activityManager = context
        .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    isLockTaskModeRunning =
            activityManager.lockTaskModeState ==
            ActivityManager.LOCK_TASK_MODE_LOCKED
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Deprecated in API level 23.
    isLockTaskModeRunning = activityManager.isInLockTaskMode
}

if (isLockTaskModeRunning) {
    // Show the exit button ...
}

Java

// Check if this app is in lock task mode. Screen pinning doesn't count.
boolean isLockTaskModeRunning = false;

ActivityManager activityManager = (ActivityManager)
    getContext().getSystemService(Context.ACTIVITY_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  isLockTaskModeRunning = activityManager.getLockTaskModeState()
      == ActivityManager.LOCK_TASK_MODE_LOCKED;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  // Deprecated in API level 23.
  isLockTaskModeRunning = activityManager.isInLockTaskMode();
}

if (isLockTaskModeRunning) {
  // Show the exit button ...
}

自定义用户界面

当应用在锁定任务模式下运行时,系统用户界面 (UI) 会发生以下变化:

  • 状态栏空白,通知和系统信息被隐藏。
  • 主页按钮和概览按钮被隐藏。
  • 其他应用无法启动新活动。
  • 锁定屏幕(如果设置)被禁用。

在 Android 9.0 或更高版本中,当锁定任务模式启用时,您的 DPC 可以在设备上启用某些系统 UI 功能——这对于创建自定义启动器的开发者很有用。调用 DevicePolicyManager.setLockTaskFeatures(),如以下代码段所示:

Kotlin

// Enable the Home and Overview buttons so that our custom launcher can respond
// using our custom activities. Implicitly disables all other features.
dpm.setLockTaskFeatures(
        adminName,
        DevicePolicyManager.LOCK_TASK_FEATURE_HOME or
              DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW)

Java

// Enable the Home and Overview buttons so that our custom launcher can respond
// using our custom activities. Implicitly disables all other features.
dpm.setLockTaskFeatures(adminName,
    DevicePolicyManager.LOCK_TASK_FEATURE_HOME |
          DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW);

系统将禁用您未包含在 flags 参数中的任何功能。启用的 UI 功能在锁定任务模式启动之间保持不变。如果设备已处于锁定任务模式,您对锁定任务功能所做的任何更改都会立即显示。表 2 描述了您可以自定义的 UI 功能。

表 2. 锁定任务模式下可自定义的系统 UI 功能
系统 UI 功能 说明
LOCK_TASK_FEATURE_HOME 显示主页按钮。为自定义启动器启用——点击启用的主页按钮不会有任何操作,除非您将默认的 Android 启动器列入许可名单。
LOCK_TASK_FEATURE_OVERVIEW 显示概览按钮(点击此按钮会打开最近应用屏幕)。如果启用此按钮,则还必须启用主页按钮。
LOCK_TASK_FEATURE_GLOBAL_ACTIONS 启用长按电源按钮时显示的全屏操作对话框。这是唯一在未调用 setLockTaskFeatures() 时启用的功能。如果禁用此对话框,用户通常无法关闭设备。
LOCK_TASK_FEATURE_NOTIFICATIONS 为所有应用启用通知。这会在状态栏中显示通知图标、浮动通知和可展开的通知抽屉。如果启用此按钮,则还必须启用主页按钮。在锁定任务模式下,点击通知操作和打开新面板的按钮不起作用。
LOCK_TASK_FEATURE_SYSTEM_INFO 启用状态栏的系统信息区域,其中包含连接、电池、声音和振动选项等指示器。
LOCK_TASK_FEATURE_KEYGUARD 启用设备上可能设置的任何锁定屏幕。通常不适用于信息亭或数字标牌等公共用户设备。
LOCK_TASK_FEATURE_NONE 禁用上述所有系统 UI 功能。

DPC 可以调用 DevicePolicyManager.getLockTaskFeatures() 来获取设备在启用锁定任务模式时可用的功能列表。当设备退出锁定任务模式时,用户界面将恢复到现有设备政策规定的状态。

阻止窗口和叠加层

当应用在锁定任务模式下运行时,其他应用和后台服务可以创建 Android 显示在锁定任务模式应用前面的新窗口。应用和服务创建这些窗口是为了向使用设备的人员显示吐司消息、对话框和叠加层。您的 DPC 可以通过添加 DISALLOW_CREATE_WINDOWS 用户限制来阻止这些。以下示例显示了您如何在 onLockTaskModeEntering() 回调中执行此操作:

Kotlin

// Called just after entering lock task mode.
override fun onLockTaskModeEntering(context: Context, intent: Intent) {
    val dpm = getManager(context)
    val admin = getWho(context)

    dpm.addUserRestriction(admin, UserManager.DISALLOW_CREATE_WINDOWS)
}

Java

// Called just after entering lock task mode.
public void onLockTaskModeEntering(Context context, Intent intent) {
  DevicePolicyManager dpm = getManager(context);
  ComponentName admin = getWho(context);

  dpm.addUserRestriction(admin, UserManager.DISALLOW_CREATE_WINDOWS);
}

当设备退出锁定任务模式时,您的 DPC 可以移除用户限制。

其他资源

要了解有关专用设备的更多信息,请阅读以下文档: