27、Slice:Slice架构、SliceProvider、SliceBuilder、SliceAction、实战:快捷功能开发
Slice 这个东西,说实话刚出来的时候我也有点懵。它到底是什么?简单说,Slice 就是让你的 App 功能“长”到系统搜索框或者 Google App 里的一种方式。用户不用打开你的 App,就能直接操作核心功能。
我最早接触 Slice 是在做一款音乐播放器的时候。产品经理说:“能不能让用户在搜索框里直接搜歌、直接播放?” 我当时第一反应是“这得写多少代码?” 后来发现,Slice 就是干这个的。
Slice 架构:到底长什么样?
Slice 的架构其实不复杂。它本质上是一个轻量级的 UI 模板,由系统渲染,数据由你的 App 提供。
核心角色有三个:
- SliceProvider:数据提供者,负责响应系统的 Slice 请求
- SliceBuilder:UI 构建器,用代码搭界面
- SliceAction:交互动作,处理点击、滑动等事件
我习惯把 Slice 理解成“App 的快捷窗口”。系统通过 URI 来请求某个 Slice,你的 SliceProvider 返回一个 Slice,系统把它渲染到搜索列表里。整个过程是异步的,不会阻塞主线程。
SliceProvider:你的数据出口
SliceProvider 是 Slice 系统的入口。你需要继承它,然后重写 onBindSlice 方法。系统会传一个 Slice URI 给你,你根据这个 URI 返回对应的 Slice。
我记得第一次写的时候,踩了一个坑:SliceProvider 必须在 Manifest 里注册,而且权限声明不能少。不然系统根本找不到你的 Provider。
// AndroidManifest.xml
<provider
android:name=".MySliceProvider"
android:authorities="com.example.app.slices"
android:exported="true"
android:grantUriPermissions="true" />
注意:exported 必须为 true,否则系统无法访问你的 SliceProvider。我曾经因为这个排查了半天,最后发现是 Manifest 配置漏了。
再来看看 Provider 的实现:
public class MySliceProvider extends SliceProvider {
@Override
public Slice onBindSlice(Uri sliceUri) {
// 根据 URI 的 path 判断返回哪个 Slice
String path = sliceUri.getPath();
if ("/quick_actions".equals(path)) {
return createQuickActionSlice(sliceUri);
}
return null;
}
private Slice createQuickActionSlice(Uri sliceUri) {
// 使用 SliceBuilder 构建
...
}
}
你想想看,每个 URI 对应一个功能模块。比如 /quick_actions 是快捷操作,/search 是搜索。这样设计很清晰,也好维护。
SliceBuilder:搭界面就像拼积木
SliceBuilder 是真正干活的东西。它提供了一系列方法,让你用代码搭出一个 UI 模板。说白了,就是 Row、Column、Text、Icon 这些元素的组合。
我个人习惯把 SliceBuilder 的构建过程拆成三步:
- 创建主列表:用
ListBuilder作为容器 - 添加行:用
RowBuilder放标题、副标题、图标 - 绑定动作:用
SliceAction处理点击
private Slice createQuickActionSlice(Uri sliceUri) {
// 1. 创建 ListBuilder
ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
// 2. 构建第一行:打开 App
SliceAction openAppAction = SliceAction.create(
PendingIntent.getActivity(getContext(), 0,
new Intent(getContext(), MainActivity.class), 0),
null, "打开 App"
);
RowBuilder row1 = new RowBuilder(listBuilder, sliceUri.buildUpon().appendPath("open").build())
.setTitle("打开音乐 App")
.setSubtitle("一键进入主界面")
.setPrimaryAction(openAppAction);
listBuilder.addRow(row1);
// 3. 构建第二行:播放最近歌曲
SliceAction playAction = SliceAction.create(
PendingIntent.getService(getContext(), 1,
new Intent(getContext(), MusicService.class)
.setAction("PLAY_RECENT"), 0),
null, "播放最近歌曲"
);
RowBuilder row2 = new RowBuilder(listBuilder, sliceUri.buildUpon().appendPath("play").build())
.setTitle("播放最近歌曲")
.setSubtitle("继续上次的播放列表")
.setPrimaryAction(playAction);
listBuilder.addRow(row2);
return listBuilder.build();
}
小技巧:RowBuilder 的 URI 最好带上不同的 path,这样系统可以缓存不同的 Slice 状态。我之前把所有行都用同一个 URI,结果点击后状态更新出了问题。
SliceAction:让 Slice 活起来
SliceAction 就是 Slice 的“灵魂”。没有 Action 的 Slice 只是一个静态卡片,有了 Action 才能响应用户操作。
SliceAction 支持三种类型:
| 类型 | 用途 | 示例 |
|---|---|---|
| Activity 跳转 | 打开 App 的某个页面 | 点击后进入播放列表 |
| Service 启动 | 后台执行任务 | 点击后开始下载 |
| Broadcast 发送 | 通知 App 更新状态 | 点击后切换开关 |
嗯,这里要注意:SliceAction 的 PendingIntent 必须唯一。如果两个 Action 用了同一个 requestCode,系统可能会混淆。我遇到过点击“播放”却打开了“设置”页面的情况,就是因为 requestCode 重复了。
// 正确的做法:每个 Action 使用不同的 requestCode
SliceAction action1 = SliceAction.create(
PendingIntent.getActivity(context, 100, intent1, PendingIntent.FLAG_IMMUTABLE),
null, "动作一"
);
SliceAction action2 = SliceAction.create(
PendingIntent.getActivity(context, 200, intent2, PendingIntent.FLAG_IMMUTABLE),
null, "动作二"
);
实战:快捷功能开发
光说不练假把式。我们做一个完整的快捷功能:用户可以在系统搜索框里直接“打开手电筒”、“打开设置”、“播放音乐”。
先定义 URI 结构:
// 基础 URI
public static final Uri BASE_URI = Uri.parse("content://com.example.app.slices/quick_actions");
// 各个功能的 URI
public static final Uri FLASHLIGHT_URI = BASE_URI.buildUpon().appendPath("flashlight").build();
public static final Uri SETTINGS_URI = BASE_URI.buildUpon().appendPath("settings").build();
public static final Uri MUSIC_URI = BASE_URI.buildUpon().appendPath("music").build();
然后在 Provider 里分发:
@Override
public Slice onBindSlice(Uri sliceUri) {
switch (sliceUri.getPath()) {
case "/quick_actions/flashlight":
return buildFlashlightSlice(sliceUri);
case "/quick_actions/settings":
return buildSettingsSlice(sliceUri);
case "/quick_actions/music":
return buildMusicSlice(sliceUri);
default:
return buildMainSlice(sliceUri); // 返回主菜单
}
}
构建手电筒 Slice:
private Slice buildFlashlightSlice(Uri sliceUri) {
ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
// 开关动作
SliceAction toggleAction = SliceAction.create(
PendingIntent.getBroadcast(getContext(), 300,
new Intent("com.example.action.TOGGLE_FLASHLIGHT"), PendingIntent.FLAG_IMMUTABLE),
null, "切换手电筒"
);
RowBuilder row = new RowBuilder(listBuilder, sliceUri)
.setTitle("手电筒")
.setSubtitle("点击开关")
.setPrimaryAction(toggleAction);
listBuilder.addRow(row);
return listBuilder.build();
}
关键点:这里用了 Broadcast 而不是 Activity,因为开关手电筒不需要打开界面。后台处理就行,用户体验更好。
最后别忘了在 Manifest 里声明 Slice 的 URI 权限:
<provider
android:name=".QuickActionSliceProvider"
android:authorities="com.example.app.slices"
android:exported="true"
android:grantUriPermissions="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.app.slice.category.SLICE" />
</intent-filter>
</provider>
做完这些,你的快捷功能就上线了。用户在系统搜索框里搜你的 App 名,就能看到这些 Slice,点击直接操作。
说实话,Slice 的坑不少。比如不同厂商的系统对 Slice 的支持程度不一样,有的直接不显示。我的建议是:把 Slice 当作锦上添花的功能,不要依赖它做核心交互。但一旦用好了,用户体验的提升是实打实的。
公众号:蓝海资料掘金营,微信deep3321