为你的应用创建自定义快捷设置图块

快捷设置是显示在快捷设置面板中的图块,代表操作,用户可以点击这些图块来快速完成重复性任务。你的应用可以通过 TileService 类为用户提供自定义图块,并使用 Tile 对象来跟踪图块的状态。例如,你可以创建一个图块,让用户开启或关闭你的应用提供的 VPN。

Quick Settings panel with the VPN tile turned
  on and off
图 1. 显示 VPN 图块已开启和已关闭的快捷设置面板。

决定何时创建图块

我们建议为用户期望经常访问或需要快速访问(或两者兼而有之)的特定功能创建图块。最有效的图块是同时满足这两种特性的图块,提供对频繁执行操作的快速访问。

例如,你可以为健身应用创建一个图块,让用户快速开始锻炼会话。但是,我们不建议为同一个应用创建一个允许用户查看其完整锻炼历史记录的图块。

Fitness app tile use cases
图 2. 健身应用图块的推荐示例与不推荐示例。

为了帮助提高图块的可发现性和易用性,我们建议避免某些做法

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

  • 避免使用图块执行一次性用户操作。请改用应用快捷方式或通知

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

  • 避免使用显示信息但不与用户交互的图块。请改用通知或小部件

创建图块

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

通过快捷设置示例可以查看如何创建和管理图块的示例。

创建自定义图标

你需要提供一个自定义图标,该图标会显示在快捷设置面板中的图块上。(你将在声明 TileService 时添加此图标,这将在下一节中介绍。)该图标必须是纯白色且背景透明,尺寸为 24 x 24dp,并且采用 VectorDrawable 的形式。

Example of a vector drawable
图 3. 矢量 Drawable 示例。

创建一个在视觉上暗示图块用途的图标。这有助于用户轻松识别你的图块是否符合其需求。例如,你可以为健身应用的图块创建一个秒表图标,让用户可以开始锻炼会话。

创建并声明你的 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 会被绑定。典型的绑定服务生命周期包含以下四个回调方法: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 tile tinted to reflect object states
图 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,请在你的一个 Activity 中添加 <intent-filter>,并使用 ACTION_QS_TILE_PREFERENCES

从 Android API 28 开始,PendingIntent 必须包含 Intent.FLAG_ACTIVITY_NEW_TASK

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

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

将图块标记为可切换

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

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 prompt
图 5. 快捷设置放置 API 提示。
public void requestAddTileService (
  ComponentName tileServiceComponentName,
  CharSequence tileLabel,
  Icon icon,
  Executor resultExecutor,
  Consumer<Integer> resultCallback
)

回调包含有关图块是否已添加、未添加、是否已存在或是否发生任何错误的信息。

在决定何时以及多久提示用户时,请自行酌情处理。我们建议仅在特定情境下调用 requestAddTileService()——例如,当用户首次与你的图块所支持的功能进行交互时。

如果某个给定的 ComponentName 之前已被用户拒绝足够多次,系统可以选择停止处理对其的请求。用户是根据用于检索此服务的 Context 确定的——它必须匹配当前用户。