专用设备实战宝典

本实战宝典可帮助开发者和系统集成商增强其 解决方案请按照我们的方法指南查找适用于专用设备的解决方案 行为本实战宝典最适合已拥有专门 设备应用 - 如果您刚刚开始使用,请阅读专用设备 概览

自定义主屏幕应用

如果您正在开发一款应用来取代 Android Home,这些食谱会非常有用 屏幕和启动器。

成为主屏幕应用

您可以将应用设为设备的主屏幕应用,以便其启动 自动同步。您还可以启用首页 按钮,可将已列入许可名单的应用锁定在前台 任务模式。

所有主屏幕应用都会处理 CATEGORY_HOME intent 类别,这样做 是系统识别主屏幕应用的方式。若要成为默认主屏幕应用,请设置一个 作为首选的“主屏幕 intent”处理程序 DevicePolicyManager.addPersistentPreferredActivity() 如以下示例中所示:

Kotlin

// Create an intent filter to specify the Home category.
val filter = IntentFilter(Intent.ACTION_MAIN)
filter.addCategory(Intent.CATEGORY_HOME)
filter.addCategory(Intent.CATEGORY_DEFAULT)

// Set the activity as the preferred option for the device.
val activity = ComponentName(context, KioskModeActivity::class.java)
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
dpm.addPersistentPreferredActivity(adminName, filter, activity)

Java

// Create an intent filter to specify the Home category.
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);

// Set the activity as the preferred option for the device.
ComponentName activity = new ComponentName(context, KioskModeActivity.class);
DevicePolicyManager dpm =
    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.addPersistentPreferredActivity(adminName, filter, activity);

您仍然需要声明 intent 过滤器 应用清单文件中,如以下 XML 代码段所示:

<activity
        android:name=".KioskModeActivity"
        android:label="@string/kiosk_mode"
        android:launchMode="singleInstance"
        android:excludeFromRecents="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

通常情况下,您不希望启动器应用显示在“概览”屏幕中。 不过,您无需将 excludeFromRecents 添加到 activity 声明,因为 Android 启动器会隐藏最初启动的 (当系统在锁定任务模式下运行时)。

显示单独的任务

对于以下情况,可以将 FLAG_ACTIVITY_NEW_TASK 作为一个实用标志 因为每个新任务在 概览屏幕。如需详细了解“概览”屏幕中的任务,请阅读最近使用的应用 屏幕

公用自助服务终端

这些食谱非常适合公共场所中的无人值守设备 可帮助许多专用设备用户专注于自己的任务。

锁定设备

为了确保设备用于其预期用途,您可以添加 表 1 中列出的用户限制。

表 1.自助服务终端设备的用户限制
用户限制 说明
DISALLOW_FACTORY_RESET 阻止设备用户将设备重置为出厂默认设置。 完全受管设备的管理员和主要用户可以设置此项 限制。
DISALLOW_SAFE_BOOT 阻止设备用户启动设备 安全模式 系统将不会自动启动您的应用拥有完全访问权限的管理员 且主要用户可以设置此限制。
DISALLOW_MOUNT_PHYSICAL_MEDIA 阻止设备用户装载他们可能使用的任何存储卷 连接到设备。完全受管设备的管理员和主要用户 可以设置此限制
DISALLOW_ADJUST_VOLUME 将设备静音,并阻止设备用户更改声音 音量和振动设置检查您的自助服务终端不需要音频功能 。全托管式管理员 主要用户、次要用户和工作资料均可设置此项 限制。
DISALLOW_ADD_USER 阻止设备用户添加新用户(如次要用户或 权限受限制的用户系统会自动将此用户限制添加到 但可能已被清除拥有完全访问权限的管理员 且主要用户可以设置此限制。

以下代码段展示了如何设置限制:

Kotlin

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
arrayOf(
        UserManager.DISALLOW_FACTORY_RESET,
        UserManager.DISALLOW_SAFE_BOOT,
        UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
        UserManager.DISALLOW_ADJUST_VOLUME,
        UserManager.DISALLOW_ADD_USER).forEach { dpm.addUserRestriction(adminName, it) }

Java

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
String[] restrictions = {
    UserManager.DISALLOW_FACTORY_RESET,
    UserManager.DISALLOW_SAFE_BOOT,
    UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
    UserManager.DISALLOW_ADJUST_VOLUME,
    UserManager.DISALLOW_ADD_USER};

for (String restriction: restrictions) dpm.addUserRestriction(adminName, restriction);

当您的应用处于管理员模式时,您不妨取消这些限制, 确保 IT 管理员仍可使用这些功能进行设备维护清除 限制、调用 DevicePolicyManager.clearUserRestriction()

不显示错误对话框

在某些环境中,例如零售演示或公共信息 您可能不想向用户显示错误对话框。在 Android 9.0 (API 级别 28)或更高版本,您可以禁止在 Chrome 操作系统中针对 添加 DISALLOW_SYSTEM_ERROR_DIALOGS 位用户 限制。系统会重启无响应的应用,就像设备用户关闭了应用一样 从对话框中打开应用。以下示例展示如何执行此操作:

Kotlin

override fun onEnabled(context: Context, intent: Intent) {
    val dpm = getManager(context)
    val adminName = getWho(context)

    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
}

Java

public void onEnabled(Context context, Intent intent) {
  DevicePolicyManager dpm = getManager(context);
  ComponentName adminName = getWho(context);

  dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS);
}

如果主要或次要用户的管理员设置了此限制,系统会 隐藏该用户的错误对话框。如果一个全托管式 设备设置此限制,则系统会禁止所有用户显示对话框。

使屏幕保持开启状态

如果您要构建自助服务终端,可以停止访问 休眠。将 将 FLAG_KEEP_SCREEN_ON 布局标志添加到应用的 窗口,如以下示例所示:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Keep the screen on and bright while this kiosk activity is running.
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Keep the screen on and bright while this kiosk activity is running.
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

您可能需要检查设备是否已插入交流电源、USB 或无线电源 充电器。注册接收电池变化广播并使用 BatteryManager 以了解充电状态。您甚至可以向 IT 人员发送远程提醒 管理员。如需分步说明,请参阅 监控电池电量和充电 状态

您还可以将 STAY_ON_WHILE_PLUGGED_IN 全局设置,以使设备在连接到电源时保持唤醒状态。 Android 6.0(API 级别 23)或更高版本中的完全托管设备的管理员可以 调用 DevicePolicyManager.setGlobalSetting(),如下所示 如下例中所示:

Kotlin

val pluggedInto = BatteryManager.BATTERY_PLUGGED_AC or
        BatteryManager.BATTERY_PLUGGED_USB or
        BatteryManager.BATTERY_PLUGGED_WIRELESS
dpm.setGlobalSetting(adminName,
        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, pluggedInto.toString())

Java

int pluggedInto = BatteryManager.BATTERY_PLUGGED_AC |
    BatteryManager.BATTERY_PLUGGED_USB |
    BatteryManager.BATTERY_PLUGGED_WIRELESS;
dpm.setGlobalSetting( adminName,
    Settings.Global.STAY_ON_WHILE_PLUGGED_IN, String.valueOf(pluggedInto));

应用软件包

本部分包含可帮助您高效将应用安装到专用设备的方案。

缓存应用软件包

如果共用设备的用户共用一组通用应用, 尽量避免下载应用简化用户 在具有一组固定用户的共享设备上进行配置,例如 轮班员工,在 Android 9.0(API 级别 28)或更高版本中,您可以缓存应用 多用户会话所需的软件包 (APK)。

安装缓存的 APK(已安装在设备上)的整个过程如下: 两个阶段:

  1. 完全受管设备(或受托人)的管理员组件,请参阅 下文)用于设置要保留在设备上的 APK 列表。
  2. 关联的次要用户(或其受托人)的管理员组件可以 代表用户安装缓存的 APK。全托管式服务的管理员 主要用户或关联的工作资料(或其资料) 受托人)也可以根据需要安装缓存的应用。

要设置在设备上保留的 APK 列表,管理员调用 DevicePolicyManager.setKeepUninstalledPackages()。 此方法不会检查 APK 是否已安装在设备上。 您希望在用户需要某个应用程序之前就安装这个应用程序。要获取 您可以调用 DevicePolicyManager.getKeepUninstalledPackages()。 在您调用 setKeepUninstalledPackages() 并进行更改后,或次要 当用户被删除时,系统会删除不再需要的所有缓存 APK。

要安装缓存的 APK,请调用 DevicePolicyManager.installExistingPackage()。 此方法只能安装系统已经缓存的应用,即您的 必须首先在 Chrome 或 Chrome 中 然后才能调用此方法。

以下示例展示了如何在 全托管式设备和次要用户:

Kotlin

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
val cachedAppPackageName = "com.example.android.myapp"
dpm.setKeepUninstalledPackages(adminName, listOf(cachedAppPackageName))

// ...

// The admin of a secondary user installs the app.
val success = dpm.installExistingPackage(adminName, cachedAppPackageName)

Java

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
String cachedAppPackageName = "com.example.android.myapp";
List<String> packages = new ArrayList<String>();
packages.add(cachedAppPackageName);
dpm.setKeepUninstalledPackages(adminName, packages);

// ...

// The admin of a secondary user installs the app.
boolean success = dpm.installExistingPackage(adminName, cachedAppPackageName);

将应用委托给他人

您可以委托其他应用来管理应用缓存。为此,您可能需要 分离您解决方案的功能,或者让 IT 管理员能够 自己的应用。受委托应用将获得与管理员相同的权限 组件。例如,次要用户管理员的应用委托可以调用 installExistingPackage(),但无法呼叫 setKeepUninstalledPackages()

进行委托通话 DevicePolicyManager.setDelegatedScopes() 并包含 DELEGATION_KEEP_UNINSTALLED_PACKAGES 。以下示例展示了如何创建另一个应用 委托人:

Kotlin

var delegatePackageName = "com.example.tools.kept_app_assist"

// Check that the package is installed before delegating.
try {
    context.packageManager.getPackageInfo(delegatePackageName, 0)
    dpm.setDelegatedScopes(
            adminName,
            delegatePackageName,
            listOf(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES))
} catch (e: PackageManager.NameNotFoundException) {
    // The delegate app isn't installed. Send a report to the IT admin ...
}

Java

String delegatePackageName = "com.example.tools.kept_app_assist";

// Check that the package is installed before delegating.
try {
  context.getPackageManager().getPackageInfo(delegatePackageName, 0);
  dpm.setDelegatedScopes(
      adminName,
      delegatePackageName,
      Arrays.asList(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES));
} catch (PackageManager.NameNotFoundException e) {
  // The delegate app isn't installed. Send a report to the IT admin ...
}

如果一切顺利,受委托应用会收到 ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED 广播并成为代理。应用可以调用本指南中的方法 就像是设备所有者或资料所有者一样。调用时 DevicePolicyManager 方法,委托可以为管理员传递 null 组件参数。

安装应用软件包

有时,将本地缓存的自定义应用安装到 设备。例如,专用设备经常部署到 带宽受限的环境或无互联网连接的区域您的 专用设备解决方案时,应考虑客户的带宽。您的 使用 PackageInstaller 类。

虽然任何应用都可以安装 APK,但完全受管设备上的管理员可以安装 APK 无需用户互动即可安装(或卸载)软件包。管理员可能负责管理 关联的次要用户或关联的工作资料。更新后 完成安装时,系统会发布一条通知,告诉所有设备用户 看到的内容。通知会告知设备用户该应用已安装(或者 由其管理员更新)。

表 2.支持软件包安装的 Android 版本 无需用户互动
Android 版本 用于安装和卸载的管理组件
Android 9.0(API 级别 28)或更高版本 关联的次要用户和工作资料(均为全代管式服务) 设备
Android 6.0(API 级别 23)或更高版本 全托管式设备

如何将一个或多个 APK 副本分发到专用设备, 取决于设备之间的距离以及设备之间的距离 都是来自另一个。您的解决方案需要遵循安全性最佳实践 然后再将 APK 安装到专用设备上。

您可以使用 PackageInstaller.Session 创建一个会话,将一个会话加入队列 一个或多个 APK 供安装。在以下示例中,我们收到的状态是 反馈,但您可以使用 服务或广播接收器:

Kotlin

// First, create a package installer session.
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
        PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
// The I/O streams can't be open when installation begins.
session.openWrite("apk", 0, -1).use { output ->
    getContext().resources.openRawResource(R.raw.app).use { input ->
        input.copyTo(output, 2048)
    }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
val intent = Intent(context, activity.javaClass)
intent.action = "com.android.example.APK_INSTALLATION_ACTION"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val statusReceiver = pendingIntent.intentSender

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver)

Java

// First, create a package installer session.
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
try (
    // These I/O streams can't be open when installation begins.
    OutputStream output = session.openWrite("apk", 0, -1);
    InputStream input = getContext().getResources().openRawResource(R.raw.app);
) {
  byte[] buffer = new byte[2048];
  int n;
  while ((n = input.read(buffer)) >= 0) {
    output.write(buffer, 0, n);
  }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
Intent intent = new Intent(context, getActivity().getClass());
intent.setAction("com.android.example.APK_INSTALLATION_ACTION");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver);

会话使用 intent 发送有关安装的状态反馈。查看 每个 intent 的 EXTRA_STATUS 字段来获取 状态。请注意,管理员不会收到 STATUS_PENDING_USER_ACTION状态更新 因为无需设备用户批准安装。

如需卸载应用,您可以调用 PackageInstaller.uninstall。 完全受管设备、用户和工作资料的管理员可以卸载软件包 无需用户互动即可运行支持的 Android 版本(请参阅 表 2)。

冻结系统更新

Android 设备接收系统和应用的无线下载 (OTA) 更新 软件开发应用。在重要时段(例如节假日或 其他繁忙时段,专用设备最多可在 90 秒内暂停 OTA 系统更新 天。如需了解详情,请参阅管理系统更新

远程配置

借助 Android 的受管配置,IT 管理员可以 远程配置您的应用。您可能需要公开设置 许可名单、网络主机或内容网址,让应用对 IT 更加有用 管理员。

如果您的应用公开了其配置,请务必在您的 文档。详细了解如何公开应用的配置以及对 请参阅设置受管配置

开发设置

在为专用设备开发解决方案时,有时需要 将应用设置为不带出厂设置的全代管式设备的管理员非常有用 重置。如需设置完全受管设备的管理员,请按以下步骤操作:

  1. 构建您的设备政策控制器 (DPC) 应用并将其安装在设备上。
  2. 检查确认设备上未配置任何账号。
  3. Android 调试桥 (adb) shell 中运行以下命令。您 需要将示例中的 com.example.dpc/.MyDeviceAdminReceiver 替换为 应用的管理组件名称:

    adb shell dpm set-device-owner com.example.dpc/.MyDeviceAdminReceiver

为帮助客户部署您的解决方案,您需要查看其他注册情况 方法。我们建议您:注册二维码 专用设备

其他资源

如需详细了解专用设备,请参阅以下文档: