工作资料

Android 平台允许设备使用工作资料(有时称为受管理资料)。工作资料由 IT 管理员控制,其可用的功能与用户主要资料的功能分开设置。这种方法使组织能够控制在用户设备上运行公司专用应用和数据的环境,同时仍允许用户使用其个人应用和个人资料。

本课将向您介绍如何修改应用,使其在具有工作资料的设备上正常运行。除了常规的应用开发最佳实践之外,您无需执行任何其他操作。不过,其中一些最佳实践在具有工作资料的设备上尤其重要。本文档重点介绍了您需要注意的问题。

概览

用户通常希望在企业环境中使用个人设备。这种情况可能会给组织带来困境。如果用户可以使用自己的设备,组织必须担心机密信息(例如员工电子邮件地址和联系人)存储在不受组织控制的设备上。

为了解决这个问题,Android 5.0(API 级别 21)允许组织设置工作资料。如果设备具有工作资料,资料的设置将由 IT 管理员控制。IT 管理员可以选择允许哪些应用使用该配置文件,并且可以仅控制该配置文件可以使用哪些设备功能。

如果设备具有工作资料,无论在哪个资料下运行该应用,都会对设备上运行的应用产生影响:

  • 默认情况下,大多数 intent 不会从一个配置文件跨越到另一个配置文件。如果在配置文件上运行的应用触发了一个 intent,该资料上没有适用于该 intent 的处理程序,并且由于配置文件限制,不允许该 intent 传递到另一个资料,请求失败,应用可能会意外关闭。
  • 资料 IT 管理员可以限制可在工作资料中使用的系统应用。此限制还可能导致工作资料中没有针对某些常见 intent 的处理程序。
  • 由于个人资料和工作资料具有单独的存储区域,因此在一份资料上有效的文件 URI 在另一份资料上无效。在一个个人资料中触发的任何 intent 都可以在另一个个人资料中处理(具体取决于个人资料设置),因此将文件 URI 附加到 intent 是不安全的。

防止出现失败的 intent

在具有工作资料的设备上,对 intent 能否从一个资料跨越到另一个资料存在限制。在大多数情况下,当触发 intent 时,系统会在触发该 intent 的同一配置文件中对其进行处理。如果该配置文件上没有适用于该 intent 的处理程序,则系统不会处理该 intent,且触发该 intent 的应用可能会意外关闭,即使另一个资料有针对该 intent 的处理程序也是如此。

配置文件管理员可以选择允许哪些 intent 从一个配置文件跨越到另一个配置文件。由于 IT 管理员做出了这一决定,因此您无法提前知道允许哪些 intent 跨越此边界。IT 管理员会设置此政策,并可以随时对其进行更改。

在应用启动 activity 之前,您应验证是否存在合适的分辨率。您可以通过调用 Intent.resolveActivity() 来验证是否存在可接受的分辨率。如果无法解析 intent,该方法会返回 null。如果该方法返回非 null 值,则至少有一种方式可以解析该 intent,并且可以安全触发该 intent。在这种情况下,intent 可以得到解析,原因可能是当前个人资料上有一个处理程序,或者 intent 允许传递到另一个人资料上的处理程序。(如需详细了解如何解析 intent,请参阅常见 intent。)

例如,如果您的应用需要设置计时器,则需要检查是否存在 ACTION_SET_TIMER intent 的有效处理程序。如果应用无法解析该 intent,则应采取适当的操作(例如显示错误消息)。

Kotlin

fun startTimer(message: String, seconds: Int) {

    // Build the "set timer" intent
    val timerIntent = Intent(AlarmClock.ACTION_SET_TIMER).apply {
        putExtra(AlarmClock.EXTRA_MESSAGE, message)
        putExtra(AlarmClock.EXTRA_LENGTH, seconds)
        putExtra(AlarmClock.EXTRA_SKIP_UI, true)
    }

    // Check if there's a handler for the intent
    if (timerIntent.resolveActivity(packageManager) == null) {

        // Can't resolve the intent! Fail this operation cleanly
        // (perhaps by showing an error message)

    } else {
        // Intent resolves, it's safe to fire it off
        startActivity(timerIntent)

    }
}

Java

public void startTimer(String message, int seconds) {

    // Build the "set timer" intent
    Intent timerIntent = new Intent(AlarmClock.ACTION_SET_TIMER)
            .putExtra(AlarmClock.EXTRA_MESSAGE, message)
            .putExtra(AlarmClock.EXTRA_LENGTH, seconds)
            .putExtra(AlarmClock.EXTRA_SKIP_UI, true);

    // Check if there's a handler for the intent
    if (timerIntent.resolveActivity(getPackageManager()) == null) {

        // Can't resolve the intent! Fail this operation cleanly
        // (perhaps by showing an error message)

    } else {
        // Intent resolves, it's safe to fire it off
        startActivity(timerIntent);

    }
}

跨资料共享文件

有时,应用需要向其他应用提供对其文件的访问权限。例如,图库应用可能希望与图片编辑器共享其图片。通常情况下,您可以通过以下两种方法共享文件:使用文件 URI 或内容 URI。

文件 URI 以 file: 前缀开头,后跟设备存储空间中该文件的绝对路径。但是,由于工作资料和个人资料使用单独的存储区域,因此在一个资料上有效的文件 URI 在另一个资料上是无效的。这意味着,如果您将文件 URI 附加到某个 intent,并且该 intent 会在另一个资料上处理,则该处理程序将无法访问该文件。

您应改为使用内容 URI 共享文件。内容 URI 以更安全、可共享的方式标识文件。内容 URI 包含文件路径、提供文件的授权以及标识文件的 ID 编号。您可以使用 FileProvider 为任何文件生成内容 ID。然后,您可以与其他应用共享该内容 ID(即使是在其他资料中也是如此)。接收者可以使用内容 ID 获取对实际文件的访问权限。

例如,以下代码段展示了如何获取特定文件 URI 的内容 URI:

Kotlin

// Open File object from its file URI
val fileToShare = File(fileUriToShare)

val contentUriToShare: Uri = FileProvider.getUriForFile(
        context,
        "com.example.myapp.fileprovider",
        fileToShare
)

Java

// Open File object from its file URI
File fileToShare = new File(fileUriToShare);

Uri contentUriToShare = FileProvider.getUriForFile(getContext(),
        "com.example.myapp.fileprovider", fileToShare);

调用 getUriForFile() 方法时,您必须添加文件提供程序的授权(在此示例中为 "com.example.myapp.fileprovider"),该授权在应用清单的 <provider> 元素中指定。如需详细了解如何共享具有内容 URI 的文件,请参阅共享文件

监听通知

应用通常会提供一个 NotificationListenerService 子类,用于从系统接收关于通知更改的回调。装有工作资料的设备可能会影响 NotificationListenerService 与您的应用的协作方式。

在工作资料中

您无法通过工作资料中运行的应用使用 NotificationListenerService。当应用在工作资料中运行时,系统会忽略应用的 NotificationListenerService。不过,在个人资料中运行的应用可以监听通知。

在个人资料中

当您的应用在个人资料中运行时,您可能不会收到在工作资料中运行的应用的通知。默认情况下,所有个人资料应用都会收到回调,但 IT 管理员可以将一个或多个个人资料应用列入许可名单,这些应用允许监听通知更改。然后,系统会屏蔽未列入许可名单的应用。在 Android 8.0(API 级别 26)或更高版本中,管理工作资料的设备政策控制器 (DPC) 可能会阻止您的应用使用 DevicePolicyManager 方法 setPermittedCrossProfileNotificationListeners() 监听工作资料的通知。您的应用仍会收到有关个人资料中发布的通知的回调。

测试应用与工作资料的兼容性

您应该在工作资料环境中测试应用,以发现会导致应用在具有工作资料的设备上失败的问题。特别是,在工作资料设备上进行测试是确保应用正确处理 intent 的好方法:不触发无法处理的 intent,不附加不能跨资料使用的 URI 等。

我们提供了示例应用 TestDPC,您可以使用该应用在搭载 Android 5.0(API 级别 21)及更高版本的 Android 设备上设置工作资料。此应用提供了一种在工作资料环境中测试应用的简单方法。您也可以使用此应用按如下方式配置工作资料:

  • 指定在受管理资料中提供的默认应用
  • 配置允许哪些 intent 从一个配置文件传递到另一个配置文件

如果您通过 USB 线将某个应用手动安装到具有工作资料的设备上,则该应用会同时安装到个人资料和工作资料中。安装应用后,您可以在以下条件下测试应用:

  • 如果 intent 通常由默认应用(例如相机应用)处理,请尝试在工作资料上停用该默认应用,并验证该应用是否会妥善处理。
  • 如果您触发的 intent 希望由其他应用处理,请尝试启用和停用该 intent 从一个配置文件传递到另一个配置文件的权限。验证应用在这两种情况下是否都能正常运行。如果 intent 不允许跨资料交叉,请在应用资料中有合适的处理程序时和没有合适的处理程序时验证应用的行为。例如,如果您的应用触发了与地图相关的 intent,请尝试以下各种场景:
    • 设备允许映射 intent 从一个配置文件跨越到另一个配置文件,并且另一个配置文件(未运行应用的配置文件)上有合适的处理程序
    • 设备不允许映射 intent 在资料之间交叉,但应用配置文件上有合适的处理程序
    • 设备不允许映射 intent 在资料之间交叉传递,并且设备配置文件中没有适合映射 intent 的处理程序
  • 如果您将内容附加到 intent,请验证 intent 在应用配置文件中处理时和跨资料切换时的行为是否正常。

使用工作资料进行测试:提示和技巧

有一些技巧可以帮助您在工作资料设备上进行测试。

  • 如上所述,当您在工作资料设备上旁加载应用时,该应用会同时安装在两个资料上。如果需要,您可以从一个资料中删除该应用,并将其保留在另一个资料中。
  • Android 调试桥 (adb) shell 中提供的大多数 activity 管理器命令都支持 --user 标记,借助该标记,您可以指定以哪个用户身份运行。通过指定用户,您可以选择是以非受管主要用户还是工作资料身份运行。如需了解详情,请参阅 ADB Shell 命令
  • 如需查找设备上的活跃用户,请使用 adb 软件包管理器的 list users 命令。输出字符串中的第一个数字是用户 ID,您可以将其与 --user 标志一起使用。如需了解详情,请参阅 ADB Shell 命令

例如,如需查找某个设备上的用户,您需要运行以下命令:

$ adb shell pm list users
UserInfo{0:Drew:13} running
UserInfo{10:Work profile:30} running

在本例中,主要用户(“Drew”)的用户 ID 为 0,工作资料的用户 ID 为 10。如需在工作资料中运行应用,您应使用如下命令:

$ adb shell am start --user 10 \
-n "com.example.myapp/com.example.myapp.testactivity" \
-a android.intent.action.MAIN -c android.intent.category.LAUNCHER