构建在工作资料上运行的应用

什么是工作资料?

工作资料是指公司允许员工使用其个人设备办公时,员工可在其个人设备上启用的辅助资料。

工作资料可以由 IT 管理员控制,工作资料所提供的功能独立于用户的主要个人资料的功能。通过这种方式,单位可以控制在用户设备上运行公司专用应用和数据的环境,同时仍允许用户使用他们的个人应用和个人资料。

这对您的应用有何影响?任何应用都可以安装在工作资料下,这意味着应用可能会面临运行时限制以及行为变更。如果出于工作目的使用应用,还需确保应用的安全性。即使应用在个人资料中运行,工作资料仍可能会影响应用的行为方式。

前提条件

此 Codelab 专为具备中级技能基础知识的 Android 开发者而设计。

此 Codelab 假定您之前构建过应用、使用过 Android Studio,并且在设备或模拟器上测试过应用。

实践内容

在此 Codelab 中,您将修改应用,以便在装有工作资料的设备上使用时提供最佳用户体验。您将学习如何创建您的应用:

  • 同时处理个人联系人和工作联系人。
  • 在应用内切换工作资料和个人资料。

e69c26cfc305d675.png

所需条件

  • 不受管理的 Android 设备(不归单位所有或不受单位管理)。

设置测试设备

我们建议您在此 Codelab 中使用实体设备。但是,您仍可以在模拟器上执行下方相同的设置。

TestDPC

TestDPC 应用由 Google 构建,旨在帮助您在自己的设备上模拟和测试受管环境。此应用可用于设置工作资料,并为您提供用于启用/停用设备上的某些功能,让您如 IT 管理员一样执行相关操作。

安装 TestDPC 应用

在您的设备上,打开 Google Play 商店并下载 TestDPC 应用

设置工作资料

安装 TestDPC 应用后,设备上会显示 2 个图标:一个设置图标和一个 TestDPC 应用图标。点按设置图标并按照相关步骤操作。

现在,您有两份单独的资料,分别针对个人应用和工作应用。您可以通过应用列表顶部的标签页在它们之间切换。

请注意,每个资料都有自己的 Play 商店应用。您可以通过启动器图标顶部的小型公文包图片识别工作应用。

153e3b8dbfb4a86e.gif

您可以像往常一样通过 Play 商店安装应用,根据您启动的 Play 商店(是个人资料的 Play 商店还是工作资料的 Play 商店),该应用将仅安装在相应的资料中。通过两个 Play 商店安装应用后,应用也可以同时存在于两个资料中。在这种情况下,应用的每个版本都将具有完全独立的存储空间和配置空间。

通过从 Android Studio 运行应用来安装应用时,该应用会默认安装在两个资料中。

设置一些要在演示版应用中使用的测试联系人:

  1. 从个人资料中启动设备的“通讯录”应用。
  2. 添加一些您可确认为个人联系人的测试联系人。
  3. 从工作资料启动“通讯录”应用。(您不会看到自己刚添加的任何个人联系人)。
  4. 添加一些您可确认为工作联系人的测试联系人。

如果您对自己设置的联系人感到满意,请试用演示版应用的起始代码。

  1. 如需获取示例应用,请执行以下任一操作:
  • 从 GitHub 克隆代码库:
$  git clone https://github.com/a-samak/work-profile-codelab
  • 或者以 Zip 文件的形式下载代码库。

下载 Zip 文件

  1. 下载完成后,导航到项目文件夹并切换到 starter 分支。
$  git checkout starter
  1. 在 Android Studio 中打开并运行应用。

以下是您首次启动该应用时的情形:

f9779ab476511718.png

试试看

当您在设备或模拟器上从 Android Studio 运行应用时,应用会同时安装在两个资料中。如有需要,您可以从一个资料中删除该应用,并将其保留于另一资料中。

请尝试在个人资料中运行该应用。您将看到列出的所有个人联系人,但看不到任何工作联系人。现在,请尝试在您的工作资料中运行该应用。您将只能看到工作联系人,而看不到任何个人联系人。

在此 Codelab 结束时,您的应用在个人资料中运行时会同时显示工作联系人和个人联系人。您还可以通过在应用的其他资料中启动另一个应用实例来切换资料。

使用 ContactsContract.Contacts.CONTENT_URI 加载联系人时,应用将根据其在哪个资料中运行来决定要显示哪些联系人。但在很多情况下,您可能希望应用同时加载两个联系人列表。例如,用户可能希望与同事共享个人内容(照片、文档)。为此,您需要检索两个联系人列表。

打开 MainActivity.kt

onCreateLoader() 方法负责创建用于检索和加载联系人的光标加载器。目前,它仅返回使用默认 ContentURI 的 CursorLoader。您将调用此方法两次,一次用于个人联系人,另一次用于工作联系人。为区分这两个用例,在每个用例中,我们将向 onCreateLoader() 传递不同的 ID。您需要检查传递到方法中的 ID,以确定要使用哪个 ContentURI。

首先,根据传递给方法的 ID 的值更改 ContentURI 变量的值。对于 PERSONAL_CONTACTS_LOADER_ID,将其分配给默认的 ContactsContract.Contacts.CONTENT_URI,否则您将按照此处的说明构建 ENTERPRISE_CONTENT_FILTER_URI

ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI
                    .buildUpon()
                    .appendPath(nameFilter)
                    .appendQueryParameter(
                        ContactsContract.DIRECTORY_PARAM_KEY,
                        ContactsContract.Directory.ENTERPRISE_DEFAULT.toString()
                    )
                    .build()

您会发现,由于这是一个内容过滤 URI,构建器需要在搜索/加载联系人时使用搜索过滤器(搜索词组)。

现在,将搜索词组硬编码为以字母“a”开头的任何名称。

val nameFilter = Uri.encode("a") // names that start with a

您还需要指定要在其中执行搜索的联系人目录。您将使用 ENTERPRISE_DEFAULT 目录来搜索存储在设备本地的联系人。

您的 onCreateLoader() 方法应如下所示:

override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        val nameFilter = Uri.encode("a") // names that start with W
        val contentURI = when (id) {
            PERSONAL_CONTACTS_LOADER_ID -> ContactsContract.Contacts.CONTENT_URI
            else -> {
                ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI
                    .buildUpon()
                    .appendPath(nameFilter)
                    .appendQueryParameter(
                        ContactsContract.DIRECTORY_PARAM_KEY,
                        ContactsContract.Directory.ENTERPRISE_DEFAULT.toString()
                    )
                    .build()
            }
        }
        return CursorLoader(
            this, contentURI, arrayOf(
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
            ), null, null, null
        )
    }

现在,您需要使用新 ID 值初始化另一个 Loader 以触发上述方法。

首先,在 MainActivity 顶部为工作联系人创建新的常量 ID 值:

const val WORK_CONTACTS_LOADER_ID = 1

然后,在 initLoaders() 中,使用 LoaderManager 初始化包含上面创建的新 ID 的新 Loader

private fun initLoaders() {
        LoaderManager.getInstance(this).
            initLoader(PERSONAL_CONTACTS_LOADER_ID, null, this)
        LoaderManager.getInstance(this).
            initLoader(WORK_CONTACTS_LOADER_ID, null, this)
    }

其他所有方法的运行方式应该相同,因为来自两个加载器的数据光标具有相同的结构。

试用

在个人资料中运行应用,现在您可同时看到工作联系人和个人联系人了!

f9779ab476511718.png 7e4846e179664d66.png

如何处理工作资料?

如果您在工作资料中运行应用,您仍然只能看到工作联系人,而看不到任何个人联系人。这是因为工作资料的主要目标之一是保护用户的隐私,因此工作应用通常无法从个人资料中获取任何个人信息。

9b7ddeec64957963.png

Android 添加了一些 API,用于在另一种资料中启动应用的另一个实例,以帮助用户实现帐号切换。例如,电子邮件应用可以提供一个界面,让用户可以在个人资料与工作资料之间切换,以便访问两个电子邮件帐号。

所有应用都可以调用这些 API 来启动同一应用的主 activity,前提是在另一资料中已安装该应用。

如需为您的应用添加跨资料帐号切换功能,首先需在我们的主 activity 布局中添加一个按钮,以便用户切换资料。

打开 activity_main.xml 并在 recycler 视图微件下添加一个按钮微件:

<androidx.appcompat.widget.AppCompatButton
        android:id="@+id/button"
        app:layout_constraintTop_toBottomOf="@+id/contacts_rv"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

返回 MainActivity.kt,设置按钮的点击事件来切换资料。

为此,请先获取 CrossProfileApps 系统服务:

val crossProfileApps = getSystemService(CrossProfileApps::class.java)

此类提供了实现资料切换功能所需的所有 API。您可以通过调用 targetUserProfiles 来检索用户资料列表,该方法将返回已在其中安装此应用的所有其他资料。

val userHandles = crossProfileApps.targetUserProfiles

您现在可以使用返回的 userHandle 作为第一项,并在另一资料中启动应用。

crossProfileApps.startMainActivity(
                    componentName,
                    userHandles.first()
                )

您甚至可以获取提示用户切换资料的本地化文本,并使用该文本设置按钮的文本值。

val label = crossProfileApps.getProfileSwitchingLabel(userHandles.first())

现在,整合上述所有部分,就是您需要添加到 MainActivity.kt 中的内容:

val crossProfileApps = getSystemService(CrossProfileApps::class.java)
        val userHandles = crossProfileApps.targetUserProfiles
        val label = crossProfileApps.getProfileSwitchingLabel(userHandles.first())
        button = findViewById<AppCompatButton>(R.id.button).apply {
            text = label
            setOnClickListener {
                crossProfileApps.startMainActivity(
                    componentName,
                    userHandles.first()
                )
            }
        }

试用

如果您现在运行应用,在应用底部将能看到相应按钮,表示您可以在工作资料个人资料之间切换,切换方向取决于您从哪个位置启动应用。

点击相应按钮将在另一资料中启动该应用。

d904de4fdc0d091b.png 4835ce56fcf10ea1.png

您已成功修改应用,使之既可在个人资料中运行,也可在工作资料中运行。该应用能感知已安装的工作资料,甚至在个人模式下运行时,也能检索工作联系人。

此外,您还实现了一种方法,让用户可在运行应用时在同一应用的工作资料和个人资料之间切换,而无需关闭应用并从相应的资料重新启动该应用。这是一种很好的做法,可帮助用户在不同的资料中以不同方式使用您的应用。

了解详情