工作资料

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

本课程展示了如何修改你的应用,使其在具有工作资料的设备上可靠运行。你只需遵循常规应用开发最佳实践即可。然而,其中一些最佳实践在具有工作资料的设备上变得尤为重要。本文档重点介绍了你需要注意的问题。

概览

用户通常希望在企业环境中使用其个人设备。这种情况可能会让组织陷入两难。如果用户可以使用自己的设备,组织就必须担心机密信息(如员工电子邮件和联系人)会出现在组织无法控制的设备上。

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

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

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

阻止失败的 intent

在具有工作资料的设备上,intent 是否可以从一个资料跨越到另一个资料存在限制。在大多数情况下,当 intent 触发时,它会在触发它的同一资料上处理。如果该资料上没有处理程序来处理该 intent,则 intent 不会被处理,并且触发它的应用可能会意外关闭,即使另一个资料上有处理程序来处理该 intent 也是如此。

资料 IT 管理员可以选择允许哪些 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 中可用的大多数活动管理器命令都支持 --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