设置受管理的配置

如果您针对企业市场开发应用,可能需要满足组织政策设置的特定要求。 托管配置(以前称为“应用限制”)允许组织的 IT 管理员远程为应用指定设置。此功能对于部署至工作资料的获得组织批准的应用特别有用。

例如,组织可能会要求获得批准的应用允许 IT 管理员执行以下操作:

  • 允许或屏蔽网络浏览器的网址
  • 配置是允许应用通过移动网络还是仅使用 WLAN 同步内容
  • 配置应用的电子邮件设置

本指南介绍了如何在您的应用中实现托管配置设置。如需查看采用托管配置的示例应用,请参阅 ManagedConfigurations。 如果您是企业移动管理 (EMM) 开发者,请参阅 Android Management API 指南

注意:由于历史原因,这些配置设置称为“限制”,是通过使用此术语的文件和类(例如 RestrictionsManager)实现的。但是,这些限制实际上可以实现各种配置选项,而不仅仅是对应用功能的限制。

远程配置概览

应用定义了可由 IT 管理员远程设置的托管配置选项。这些是任意设置,可由托管配置提供商更改。如果您的应用在工作资料中运行,IT 管理员可以更改应用的托管配置。

托管配置提供程序是在同一设备上运行的另一个应用。 此应用通常由 IT 管理员控制。IT 管理员将配置更改传达给受管理配置提供程序应用。该应用反过来会更改您应用的配置。

如需提供由外部管理的配置,请执行以下操作:

  • 在应用清单中声明托管配置。这样一来,IT 管理员便可以通过 Google Play API 读取应用的配置。
  • 每当应用恢复时,都请使用 RestrictionsManager 对象检查当前的受管理配置,并更改应用的界面和行为以符合这些配置。
  • 监听 ACTION_APPLICATION_RESTRICTIONS_CHANGED intent。收到此广播后,请检查 RestrictionsManager 以了解当前的托管配置是什么,并对应用的行为进行必要的更改。

定义托管配置

您的应用可以支持您想定义的任何托管配置。您可以在托管配置文件中声明应用的托管配置,并在清单中声明配置文件。通过创建配置文件,其他应用可以检查您的应用提供的托管配置。EMM 合作伙伴可以使用 Google Play API 读取您的应用配置。

如需定义应用的远程配置选项,请将以下元素放入清单的 <application> 元素中:

<meta-data android:name="android.content.APP_RESTRICTIONS"
    android:resource="@xml/app_restrictions" />

在应用的 res/xml 目录中创建一个名为 app_restrictions.xml 的文件。RestrictionsManager 的参考文档中介绍了该文件的结构。该文件包含一个顶级 <restrictions> 元素,该元素针对应用的每个配置选项包含一个 <restriction> 子元素。

注意:请勿创建托管配置文件的本地化版本。您的应用只能有一个代管式配置文件,因此应用在所有语言区域的配置将保持一致。

在企业环境中,EMM 通常使用托管配置架构为 IT 管理员生成远程控制台,以便管理员能够远程配置您的应用。

托管配置提供程序可以查询应用,以查找有关应用可用配置的详细信息,包括其说明文本。配置提供方和 IT 管理员可以随时更改应用的托管配置,即使应用未运行也可以。

例如,假设您可以远程配置您的应用,以允许或禁止其通过移动网络连接下载数据。您的应用可以包含如下所示的 <restriction> 元素:

<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">

  <restriction
    android:key="downloadOnCellular"
    android:title="@string/download_on_cell_title"
    android:restrictionType="bool"
    android:description="@string/download_on_cell_description"
    android:defaultValue="true" />

</restrictions>

您可以使用每个配置的 android:key 属性从托管配置 bundle 中读取其值。因此,每个配置必须具有唯一的键字符串,并且该字符串无法本地化。必须使用字符串字面量指定。

注意:在正式版应用中,应从本地化的资源文件中提取 android:titleandroid:description,如使用资源进行本地化中所述。

应用使用 bundle_array 中的捆绑包定义限制。例如,具有多个 VPN 连接选项的应用可以在 bundle 中定义每个 VPN 服务器配置,并将多个软件包归入一个软件包数组:

<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android" >

  <restriction
    android:key="vpn_configuration_list"
    android:restrictionType="bundle_array">
    <restriction
      android:key="vpn_configuration"
      android:restrictionType="bundle">
      <restriction
        android:key="vpn_server"
        android:restrictionType="string"/>
      <restriction
        android:key="vpn_username"
        android:restrictionType="string"/>
      <restriction
        android:key="vpn_password"
        android:restrictionType="string"/>
    </restriction>
  </restriction>

</restrictions>

android:restrictionType 元素支持的类型列在表 1 中,并记录在 RestrictionsManagerRestrictionEntry 的参考文档中。

表 1. 限制条目类型和用法。

类型 android:restrictionType 典型用法
TYPE_BOOLEAN "bool" 布尔值(true 或 false)。
TYPE_STRING "string" 字符串值,例如名称。
TYPE_INTEGER "integer" 一个整数,值介于 MIN_VALUEMAX_VALUE 之间。
TYPE_CHOICE "choice" android:entryValues 中选择的字符串值,通常以单选列表的形式呈现。
TYPE_MULTI_SELECT "multi-select" 一个包含从 android:entryValues 中选择的值的字符串数组。 使用此字段显示可选择多个条目的多选列表,例如用于选择要列入许可名单的特定影视内容。
TYPE_NULL "hidden" 隐藏的限制类型。此类型适用于需要传输、但不应该在界面中呈现的信息。存储单个字符串值。
TYPE_BUNDLE_ARRAY "bundle_array" 使用此方法来存储限制 bundles 的数组。适用于 Android 6.0(API 级别 23)。

注意android:entryValues 是机器可读的,无法本地化。使用 android:entries 显示人类可读的可本地化值。每个条目必须在 android:entryValues 中具有相应的索引。

检查托管配置

当其他应用更改其配置设置时,系统不会自动通知您的应用。相反,您需要检查在应用启动或恢复时托管配置是什么,并监听系统 intent,以了解配置是否在应用运行时发生更改。

为找出当前配置设置,您的应用会使用 RestrictionsManager 对象。您的应用应在以下时间检查当前的托管配置:

如需获取 RestrictionsManager 对象,请使用 getActivity() 获取当前的 activity,然后调用该 activity 的 Activity.getSystemService() 方法:

Kotlin

var myRestrictionsMgr =
        activity?.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager

Java

RestrictionsManager myRestrictionsMgr =
    (RestrictionsManager) getActivity()
        .getSystemService(Context.RESTRICTIONS_SERVICE);

拥有 RestrictionsManager 后,您可以通过调用其 getApplicationRestrictions() 方法来获取当前配置设置:

Kotlin

var appRestrictions: Bundle = myRestrictionsMgr.applicationRestrictions

Java

Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();

注意:为方便起见,您还可以通过调用 UserManager.getApplicationRestrictions() 使用 UserManager 提取当前配置。此方法的行为与 RestrictionsManager.getApplicationRestrictions() 完全相同。

getApplicationRestrictions() 方法需要从数据存储中读取数据,因此应谨慎完成。请不要在每次需要知道当前配置时调用此方法。而是应在应用启动或恢复时调用一次,并缓存提取的托管配置包。然后监听 ACTION_APPLICATION_RESTRICTIONS_CHANGED intent,以确定配置在应用处于活动状态时是否发生更改,如监听托管配置更改中所述。

读取和应用托管配置

getApplicationRestrictions() 方法会返回一个 Bundle,其中包含与已设置的每个配置的键值对。这些值均为 BooleanintStringString[] 类型。创建托管配置 Bundle 后,您可以使用适用于这些数据类型的标准 Bundle 方法(例如 getBoolean()getString())检查当前配置设置。

注意:托管配置提供方已明确设置的每个配置在托管配置 Bundle 中都有一项对应的内容。但是,您不能仅仅因为在托管配置 XML 文件中定义了默认值就假定配置会在软件包中。

您的应用需根据当前的托管配置设置采取适当的措施。例如,如果您的应用具有指定是否可以通过移动网络连接下载数据的配置,但您发现该配置设为 false,那么您必须停用数据下载(设备连接到 WLAN 时除外),如以下示例代码所示:

Kotlin

val appCanUseCellular: Boolean =
        if (appRestrictions.containsKey("downloadOnCellular")) {
            appRestrictions.getBoolean("downloadOnCellular")
        } else {
            // cellularDefault is a boolean using the restriction's default value
            cellularDefault
        }

if (!appCanUseCellular) {
    // ...turn off app's cellular-download functionality
    // ...show appropriate notices to user
}

Java

boolean appCanUseCellular;

if (appRestrictions.containsKey("downloadOnCellular")) {
    appCanUseCellular = appRestrictions.getBoolean("downloadOnCellular");
} else {
    // cellularDefault is a boolean using the restriction's default value
    appCanUseCellular = cellularDefault;
}

if (!appCanUseCellular) {
    // ...turn off app's cellular-download functionality
    // ...show appropriate notices to user
}

如需应用多个嵌套限制,请将 bundle_array 限制条目作为 Parcelable 对象的集合读取,并转换为 Bundle。在此示例中,系统会解析每个 VPN 的配置数据,并使用这些数据来构建服务器连接选项列表:

Kotlin

// VpnConfig is a sample class used store config data, not defined
val vpnConfigs = mutableListOf<VpnConfig>()

val parcelables: Array<out Parcelable>? =
        appRestrictions.getParcelableArray("vpn_configuration_list")

if (parcelables?.isNotEmpty() == true) {
    // iterate parcelables and cast as bundle
    parcelables.map { it as Bundle }.forEach { vpnConfigBundle ->
        // parse bundle data and store in VpnConfig array
        vpnConfigs.add(VpnConfig()
                .setServer(vpnConfigBundle.getString("vpn_server"))
                .setUsername(vpnConfigBundle.getString("vpn_username"))
                .setPassword(vpnConfigBundle.getString("vpn_password")))
    }
}

if (vpnConfigs.isNotEmpty()) {
    // ...choose a VPN configuration or prompt user to select from list
}

Java

// VpnConfig is a sample class used store config data, not defined
List<VpnConfig> vpnConfigs = new ArrayList<>();

Parcelable[] parcelables =
    appRestrictions.getParcelableArray("vpn_configuration_list");

if (parcelables != null && parcelables.length > 0) {
    // iterate parcelables and cast as bundle
    for (int i = 0; i < parcelables.length; i++) {
        Bundle vpnConfigBundle = (Bundle) parcelables[i];
        // parse bundle data and store in VpnConfig array
        vpnConfigs.add(new VpnConfig()
            .setServer(vpnConfigBundle.getString("vpn_server"))
            .setUsername(vpnConfigBundle.getString("vpn_username"))
            .setPassword(vpnConfigBundle.getString("vpn_password")));
    }
}

if (!vpnConfigs.isEmpty()) {
    // ...choose a VPN configuration or prompt user to select from list
}

监听托管配置更改

每当应用的托管配置发生更改时,系统都会触发 ACTION_APPLICATION_RESTRICTIONS_CHANGED intent。您的应用必须监听此 intent,以便在配置设置更改时更改应用的行为。

注意ACTION_APPLICATION_RESTRICTIONS_CHANGED intent 只会发送给动态注册的监听器,而不是应用清单中声明的监听器。

以下代码展示了如何为此 intent 动态注册广播接收器:

Kotlin

val restrictionsFilter = IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED)

val restrictionsReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        // Get the current configuration bundle
        val appRestrictions = myRestrictionsMgr.applicationRestrictions

        // Check current configuration settings, change your app's UI and
        // functionality as necessary.
    }
}

registerReceiver(restrictionsReceiver, restrictionsFilter)

Java

IntentFilter restrictionsFilter =
    new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);

BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
  @Override public void onReceive(Context context, Intent intent) {

    // Get the current configuration bundle
    Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();

    // Check current configuration settings, change your app's UI and
    // functionality as necessary.
  }
};

registerReceiver(restrictionsReceiver, restrictionsFilter);

注意:通常,当应用暂停时,不需要收到配置更改通知。相反,您应在应用暂停时取消注册广播接收器。当应用恢复时,请先检查当前的托管配置(如检查托管配置中所述),然后注册广播接收器,以确保应用处于活动状态时,如果发生配置更改,您会收到通知。

向 EMM 发送托管配置反馈

将托管配置更改应用到应用后,最佳实践是将更改状态告知 EMM。Android 支持一项名为“键控应用状态”的功能,您可以使用该功能在应用每次尝试应用托管配置更改时发送反馈。此反馈可以用于确认您的应用已成功设置了托管配置,也可以在应用未能应用指定的更改时包含错误消息。

EMM 提供商能够检索此反馈,并将其显示在控制台中,以供 IT 管理员查看。如需详细了解该主题,包括有关如何向应用添加反馈支持的详细指南,请参阅向 EMM 发送应用反馈

更多代码示例

ManagedConfigurations 示例进一步演示了如何使用本页介绍的 API。