为您的应用创建自定义“快捷设置”图块

“快捷设置”是显示在“快捷设置”面板中的功能块,表示用户可点按它们以快速完成周期性任务。应用可以通过 TileService 类向用户提供自定义功能块,并使用 Tile 对象跟踪功能块的状态。例如,您可以创建一个功能块,让用户能够开启或关闭应用提供的 VPN。

显示开启和关闭 VPN 图块的“快捷设置”面板
图 1. “快捷设置”面板,其中 VPN 功能块处于打开和关闭状态。

决定何时创建功能块

我们建议您针对您希望用户经常访问和/或需要快速使用的特定功能创建功能块。最有效的功能块是同时符合这两种特性的功能块,可让您快速访问经常执行的操作。

例如,您可以为健身应用创建功能块,让用户能够快速开始锻炼课程。但是,我们不建议为同一应用创建可让用户查看其整个锻炼历史记录的功能块。

健身应用功能块用例
图 2. 健身应用的推荐功能块与不推荐功能块示例。

为了帮助提高功能块的可检测性和易用性,我们建议您避免以下做法:

  • 避免使用功能块启动应用。请改用应用快捷方式或标准启动器。

  • 避免将功能块用于一次性用户操作。请改用应用快捷方式或通知

  • 避免创建过多功能块。我们建议每个应用最多设置两个。请改用应用快捷方式。

  • 避免使用显示信息但对用户无互动功能的功能块。请改用通知或 widget

创建功能块

如需创建功能块,您需要先创建适当的功能块图标,然后在应用的清单文件中创建并声明 TileService

“快捷设置”示例提供了有关如何创建和管理功能块的示例。

创建自定义图标

您需要提供一个自定义图标,该图标会显示在“快捷设置”面板的图块中。(您将在声明 TileService 时添加此图标,如下一部分所述。)该图标必须为纯白色、透明背景,尺寸为 24 x 24dp,并且采用 VectorDrawable 形式。

矢量可绘制对象示例
图 3. 矢量可绘制对象示例。

创建一个在视觉上暗示功能块用途的图标。这有助于用户轻松确定您的功能块是否符合他们的需求。例如,您可以为健身应用的功能块创建秒表图标,以允许用户开始锻炼时段。

创建并声明 TileService

为功能块创建一项扩展 TileService 类的服务。

Kotlin

class MyQSTileService: TileService() {

  // Called when the user adds your tile.
  override fun onTileAdded() {
    super.onTileAdded()
  }
  // Called when your app can update your tile.
  override fun onStartListening() {
    super.onStartListening()
  }

  // Called when your app can no longer update your tile.
  override fun onStopListening() {
    super.onStopListening()
  }

  // Called when the user taps on your tile in an active or inactive state.
  override fun onClick() {
    super.onClick()
  }
  // Called when the user removes your tile.
  override fun onTileRemoved() {
    super.onTileRemoved()
  }
}

Java

public class MyQSTileService extends TileService {

  // Called when the user adds your tile.
  @Override
  public void onTileAdded() {
    super.onTileAdded();
  }

  // Called when your app can update your tile.
  @Override
  public void onStartListening() {
    super.onStartListening();
  }

  // Called when your app can no longer update your tile.
  @Override
  public void onStopListening() {
    super.onStopListening();
  }

  // Called when the user taps on your tile in an active or inactive state.
  @Override
  public void onClick() {
    super.onClick();
  }

  // Called when the user removes your tile.
  @Override
  public void onTileRemoved() {
    super.onTileRemoved();
  }
}

在应用的清单文件中声明 TileService。添加 TileService 的名称和标签、您在上一部分中创建的自定义图标以及相应的权限。

 <service
     android:name=".MyQSTileService"
     android:exported="true"
     android:label="@string/my_default_tile_label"  // 18-character limit.
     android:icon="@drawable/my_default_icon_label"
     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
     <intent-filter>
         <action android:name="android.service.quicksettings.action.QS_TILE" />
     </intent-filter>
 </service>

管理您的 TileService

在应用清单中创建并声明 TileService 后,您必须管理其状态。

TileService 是一项绑定服务。当应用发出请求或系统需要与 TileService 通信时,系统会绑定 TileService。典型的绑定服务生命周期包含以下四种回调方法:onCreate()onBind()onUnbind()onDestroy()。每当服务进入新的生命周期阶段时,系统都会调用这些方法。

TileService 生命周期概览

除了控制绑定服务生命周期的回调之外,您还必须实现特定于 TileService 生命周期的其他方法。这些方法可以在 onCreate()onDestroy() 之外调用,因为 Service 生命周期方法和 TileService 生命周期方法是在两个单独的异步线程中调用的。

TileService 生命周期包含以下方法,每当 TileService 进入新的生命周期阶段时,系统都会调用这些方法:

  • onTileAdded():仅当用户首次添加功能块,并且用户移除并重新添加功能块时,系统才会调用此方法。此时最适合执行任何一次性初始化。不过,这可能无法满足所需的所有初始化设置。

  • onStartListening()onStopListening():每当您的应用更新功能块时,系统都会调用这些方法,并且会经常调用这些方法。TileServiceonStartListening()onStopListening() 之间保持绑定状态,允许您的应用修改功能块并推送更新。

  • onTileRemoved():仅当用户移除功能块时,系统才会调用此方法。

选择聆听模式

您的 TileService 会在活动模式或非活动模式下收听。我们建议您使用活动模式,您需要在应用清单中声明该模式。否则,TileService 是标准模式,不需要声明。

不要假设 TileService 将位于 onStartListening()onStopListening() 对方法之外。

TileService 使用活动模式,让其在自己的进程中监听和监控其状态。处于活动模式的 TileService 绑定到 onTileAdded()onTileRemoved()、点按事件,以及当应用进程发出请求时。

如果 TileService 在应该由自己的进程更新功能块状态时收到通知,我们建议您启用活动模式。活跃功能块可限制系统上的负担,因为无需在每次“快捷设置”面板对用户可见时进行绑定。

可以调用静态 TileService.requestListeningState() 方法来请求开始监听状态并接收对 onStartListening() 的回调。

您可以通过向应用的清单文件中添加 META_DATA_ACTIVE_TILE 来声明活动模式。

<service ...>
    <meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
         android:value="true" />
    ...
</service>

非活动模式

非活动模式是标准模式。如果在功能块对用户可见时绑定 TileService,则处于非活动模式。这意味着,您的 TileService 可能会在其控制范围之外的时间被创建和绑定。此外,当用户未查看功能块时,它也可能会解除绑定和销毁。

用户打开“快捷设置”面板后,您的应用会收到对 onStartListening() 的回调。在 onStartListening()onStopListening() 之间,您可以根据需要多次更新 Tile 对象。

您无需声明非活动模式,只需不将 META_DATA_ACTIVE_TILE 添加到应用的清单文件中即可。

图块状态概览

用户添加功能块后,功能块会始终处于以下状态之一。

  • STATE_ACTIVE:表示开启或启用状态。在此状态下,用户可以与您的功能块互动。

    例如,对于允许用户发起定时锻炼时段的健身应用功能块,STATE_ACTIVE 表示用户已发起锻炼时段,并且计时器正在运行。

  • STATE_INACTIVE:表示关闭或暂停状态。在此状态下,用户可以与您的功能块互动。

    再次使用健身应用功能块示例,STATE_INACTIVE 中的功能块表示用户尚未启动锻炼时段,但如果他们愿意,也可以启动。

  • STATE_UNAVAILABLE:表示暂时不可用的状态。在此状态下,用户无法与您的功能块互动。

    例如,STATE_UNAVAILABLE 中的功能块表示由于某种原因,用户当前无法使用该功能。

系统仅设置 Tile 对象的初始状态。您可以在 Tile 对象的整个生命周期内设置其状态。

系统可能会调节功能块图标和背景的色调,以反映 Tile 对象的状态。设置为 STATE_ACTIVETile 对象最暗,STATE_INACTIVESTATE_UNAVAILABLE 的颜色逐渐变浅。确切的色调因制造商和版本而异。

已调节 VPN 图块的色调,以反映对象状态
图 4. 着色以反映功能块状态(分别是有效、无效和不可用状态)的功能块示例。

更新您的功能块

收到 onStartListening() 的回调后,您可以更新功能块。在收到 onStopListening() 的回调之前,您的功能块可以至少更新一次,具体取决于功能块的模式。

在活动模式下,您可以先更新功能块一次,然后再收到对 onStopListening() 的回调。在非活动模式下,您可以在 onStartListening()onStopListening() 之间根据需要多次更新功能块。

您可以通过调用 getQsTile() 来检索 Tile 对象。如需更新 Tile 对象的特定字段,请调用以下方法:

Tile 对象的字段设置为正确的值后,您必须调用 updateTile() 来更新功能块。这将使系统解析更新后的功能块数据并更新界面。

Kotlin

data class StateModel(val enabled: Boolean, val label: String, val icon: Icon)

override fun onStartListening() {
  super.onStartListening()
  val state = getStateFromService()
  qsTile.label = state.label
  qsTile.contentDescription = tile.label
  qsTile.state = if (state.enabled) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
  qsTile.icon = state.icon
  qsTile.updateTile()
}

Java

public class StateModel {
  final boolean enabled;
  final String label;
  final Icon icon;

  public StateModel(boolean e, String l, Icon i) {
    enabled = e;
    label = l;
    icon = i;
  }
}

@Override
public void onStartListening() {
  super.onStartListening();
  StateModel state = getStateFromService();
  Tile tile = getQsTile();
  tile.setLabel(state.label);
  tile.setContentDescription(state.label);
  tile.setState(state.enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
  tile.setIcon(state.icon);
  tile.updateTile();
}

处理点按操作

如果您的功能块位于 STATE_ACTIVESTATE_INACTIVE 中,则用户可以点按功能块以触发操作。然后,系统会调用应用的 onClick() 回调。

一旦您的应用收到对 onClick() 的回调,即可启动对话框或 activity、触发后台工作或更改功能块的状态。

Kotlin

var clicks = 0
override fun onClick() {
  super.onClick()
  counter++
  qsTile.state = if (counter % 2 == 0) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
  qsTile.label = "Clicked $counter times"
  qsTile.contentDescription = qsTile.label
  qsTile.updateTile()
}

Java

int clicks = 0;

@Override
public void onClick() {
  super.onClick();
  counter++;
  Tile tile = getQsTile();
  tile.setState((counter % 2 == 0) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
  tile.setLabel("Clicked " + counter + " times");
  tile.setContentDescription(tile.getLabel());
  tile.updateTile();
}

启动对话框

showDialog() 可收起“快捷设置”面板并显示一个对话框。 如果对话框需要额外输入或征得用户同意,可以使用对话框为操作添加上下文。

启动 activity

startActivityAndCollapse() 在收起面板时启动 activity。如果有比对话框显示的详细信息要更详细,或者您的操作具有高度互动性,那么 activity 非常有用。

如果您的应用需要大量的用户互动,则只有在万不得已时才启动 activity。请考虑改用对话框或切换开关。

长按功能块会提示用户进入应用信息界面。如需替换此行为并改为启动一个 activity 来设置偏好设置,请使用 ACTION_QS_TILE_PREFERENCES<intent-filter> 添加到您的其中一个 activity。

从 Android API 28 开始,PendingIntent 必须具有 Intent.FLAG_ACTIVITY_NEW_TASK

if (Build.VERSION.SDK_INT >= 28) {
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}

或者,您也可以在特定 Activity 部分的 AndroidManifest.xml 中添加该标志。

将功能块标记为可切换

如果功能块主要用作两种状态开关(这是功能块最常见的行为),我们建议您将其标记为可切换。这有助于向操作系统提供有关功能块行为的信息,并改进整体无障碍功能。

TOGGLEABLE_TILE 元数据设置为 true,将功能块标记为可切换。

<service ...>
  <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
    android:value="true" />
</service>

仅在被安全锁定的设备上执行安全操作

在锁定的设备上,您的图块可能会显示在锁定屏幕上。如果该功能块包含敏感信息,请检查 isSecure() 的值以确定设备是否处于安全状态,您的 TileService 应相应地更改其行为。

如果在锁定时可以安全执行功能块操作,请使用 startActivity() 在锁定的屏幕上启动 activity。

如果功能块操作不安全,请使用 unlockAndRun() 提示用户解锁设备。如果成功,系统会执行您传入此方法的 Runnable 对象。

提示用户添加功能块

如要手动添加功能块,用户必须按以下步骤操作:

  1. 向下滑动以打开“快捷设置”面板。
  2. 点按“修改”按钮。
  3. 滚动浏览孩子设备上的所有板块,直到他们找到您的板块。
  4. 按住您的功能块,然后将其拖动到有效功能块列表中。

用户还可以随时移动或移除您的功能块。

从 Android 13 开始,您可以使用 requestAddTileService() 方法,让用户更轻松地将功能块添加到设备。此方法会提示用户直接将您的功能块直接添加到他们的“快捷设置”面板。提示包括应用名称、提供的标签和图标。

Quick Settings Placement API 提示
图 5.Quick Settings Placement API 提示。
public void requestAddTileService (
  ComponentName tileServiceComponentName,
  CharSequence tileLabel,
  Icon icon,
  Executor resultExecutor,
  Consumer<Integer> resultCallback
)

该回调包含以下方面的信息:是否添加功能块、未添加功能块、功能块是否已存在或是否发生了任何错误。

您可以自行决定何时以及以何种频率提示用户。我们建议仅在上下文中调用 requestAddTileService(),例如当用户首次与您的功能块提供的功能互动时。

如果先前已多次被用户拒绝,系统可以选择停止处理给定 ComponentName 的请求。用户由用于检索此服务的 Context 确定,必须与当前用户匹配。