设置托管配置

如果您正在为企业市场开发应用,则可能需要满足组织策略设定的特定要求。托管配置(以前称为应用限制)允许组织的 IT 管理员远程指定应用的设置。此功能对于部署到工作配置文件的组织批准的应用特别有用。

例如,组织可能要求批准的应用允许 IT 管理员:

  • 允许或阻止网页浏览器的网址
  • 配置应用是否允许通过蜂窝网络同步内容,或者仅通过 Wi-Fi 同步内容
  • 配置应用的电子邮件设置

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

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

远程配置概述

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

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

要提供外部托管配置:

  • 在您的应用清单中声明托管配置。这样做允许 IT 管理员通过 Google Play API 读取应用的配置。
  • 每当应用恢复时,请使用RestrictionsManager对象检查当前托管配置,并更改您的应用的 UI 和行为以符合这些配置。
  • 侦听ACTION_APPLICATION_RESTRICTIONS_CHANGED意图。当您收到此广播时,请检查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属性从托管配置包中读取其值。因此,每个配置必须具有唯一的键字符串,并且该字符串不能进行本地化。它必须使用字符串文字指定。

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

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

<?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" 隐藏的限制类型。将此类型用于需要传输但无需在UI中向用户显示的信息。存储单个字符串值。
TYPE_BUNDLE_ARRAY "bundle_array" 用于存储限制bundles数组。Android 6.0(API 级别 23)可用。

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

检查托管配置

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

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

要获取RestrictionsManager对象,请使用getActivity()获取当前活动,然后调用该活动的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通过调用UserManager.getApplicationRestrictions()来获取当前配置。此方法的行为与RestrictionsManager.getApplicationRestrictions()完全相同。

getApplicationRestrictions()方法需要从数据存储区读取数据,因此应谨慎使用。不要每次需要知道当前配置时都调用此方法。相反,您应该在应用启动或恢复时调用它一次,并缓存获取的托管配置捆绑包。然后侦听ACTION_APPLICATION_RESTRICTIONS_CHANGED意图以了解应用处于活动状态时配置是否发生更改,如侦听托管配置更改中所述。

读取和应用托管配置

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

注意:托管配置Bundle包含托管配置提供程序显式设置的每个配置的一个项目。但是,您不能假设仅因为您在托管配置XML文件中定义了默认值,捆绑包中就会存在该配置。

您的应用需要根据当前的托管配置设置采取适当的操作。例如,如果您的应用有一个配置指定它是否可以通过蜂窝连接下载数据,并且您发现该配置设置为false,则您必须禁用数据下载,除非设备具有Wi-Fi连接,如下面的示例代码所示。

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意图。您的应用必须侦听此意图,以便您可以在配置设置更改时更改应用的行为。

注意:ACTION_APPLICATION_RESTRICTIONS_CHANGED意图仅发送给动态注册的侦听器,发送给在应用清单中声明的侦听器。

以下代码显示如何为该意图动态注册广播接收器。

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的用法。