Android pie slice开发小记
slice创建和运行
- 首先新建一个项目并选择使用的api为API 28:Android 9(pie)或者在已有的项目调整其编译、最低和目标版本为28(最低)。
- 创建 slice provider:
当生成slice provider后,编译器会报如下错误:1
2
3Manifest merger failed : Attribute application@appComponentFactory value=(androidx.core.app.CoreComponentFactory) from [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86
is also present at [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91 value=(android.support.v4.app.CoreComponentFactory).
Suggestion: add 'tools:replace="android:appComponentFactory"' to <application> element at AndroidManifest.xml:5:5-35:19 to override.
以上错误主要是android在api 28版本后,appconmpat、cardview和constraintlayout等都移到Androidx包名下,所以修正方式为:1
2
3
4
5
6
7
8
9
10
11
12 implementation fileTree(dir: 'libs', include: ['*.jar'])
// implementation 'com.android.support:appcompat-v7:28.0.0'
// implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'androidx.slice:slice-builders:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha3'
参考地址:https://stackoverflow.com/questions/50782435/android-design-support-library-for-api-28-p-not-working
- sliceProvider编码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94public class PieSliceProvider extends SliceProvider {
/**
* Instantiate any required objects. Return true if the provider was successfully created,
* false otherwise.
*/
public boolean onCreateSliceProvider() {
return true;
}
/**
* Converts URL to content URI (i.e. content://com.mugwort.demo...)
*/
public Uri onMapIntentToUri(@Nullable Intent intent) {
// Note: implementing this is only required if you plan on catching URL requests.
// This is an example solution.
Uri.Builder uriBuilder = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT);
if (intent == null) return uriBuilder.build();
Uri data = intent.getData();
if (data != null && data.getPath() != null) {
String path = data.getPath().replace("/", "");
uriBuilder = uriBuilder.path(path);
}
Context context = getContext();
if (context != null) {
uriBuilder = uriBuilder.authority(context.getPackageName());
}
return uriBuilder.build();
}
/**
* Construct the Slice and bind data if available.
*/
public Slice onBindSlice(Uri sliceUri) {
Context context = getContext();
SliceAction activityAction = createActivityAction();
if (context == null || activityAction == null) {
return null;
}
if ("/".equals(sliceUri.getPath())) {
// Path recognized. Customize the Slice using the androidx.slice.builders API.
// Note: ANRs and strict mode is enforced here so don't do any heavy operations.
// Only bind data that is currently available in memory.
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.addRow(
new RowBuilder()
.setTitle("URI found.hello i'am here")
.setPrimaryAction(activityAction)
)
.build();
} else {
// Error: Path not found.
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.addRow(
new RowBuilder()
.setTitle("URI not found.")
.setPrimaryAction(activityAction)
)
.build();
}
}
private SliceAction createActivityAction() {
//return null;
//Instead of returning null, you should create a SliceAction. Here is an example:
return SliceAction.create(
PendingIntent.getActivity(
getContext(), 0, new Intent(getContext(), MainActivity.class), 0
),
IconCompat.createWithResource(getContext(), R.drawable.ic_launcher_foreground),
ListBuilder.ICON_IMAGE,
"Open App"
);
}
/**
* Slice has been pinned to external process. Subscribe to data source if necessary.
*/
public void onSlicePinned(Uri sliceUri) {
// When data is received, call context.contentResolver.notifyChange(sliceUri, null) to
// trigger PieSliceProvider#onBindSlice(Uri) again.
}
/**
* Unsubscribe from data source if necessary.
*/
public void onSliceUnpinned(Uri sliceUri) {
// Remove any observers if necessary to avoid memory leaks.
}
}
分析:
- 通过类我们发现SliceProvider继承于ContentProvider,其APP间数据的传递通过
ContentProvider的方式,应用APP向搜索APP对外提供其对应Slice的Uri,封装成Slice对象通过Parcelable序列化的方式实现APP之间的数据传递。 - slice的绑定和展示:onBindSlice,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public Slice onBindSlice(Uri sliceUri) {
Context context = getContext();
SliceAction activityAction = createActivityAction();
if (context == null || activityAction == null) {
return null;
}
//“/”是在manifest中定义的
if ("/".equals(sliceUri.getPath())) {
// Path recognized. Customize the Slice using the androidx.slice.builders API.
// Note: ANRs and strict mode is enforced here so don't do any heavy operations.
// Only bind data that is currently available in memory.
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.addRow(
new RowBuilder()
.setTitle("URI found.hello i'am here")
.setPrimaryAction(activityAction)
)
.build();
} else {
// Error: Path not found.
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.addRow(
new RowBuilder()
.setTitle("URI not found.")
.setPrimaryAction(activityAction)
)
.build();
}
}
Uri的scheme统一为content,如上述例子的Uri为:
content://x.x.x/
SliceAction,这个类似notifications,可以使用PendingIntents 来处理用户的点击事件,比如点击Slice模块打开宿主APP:
1
2
3
4
5
6
7
8
9
10
11
12private SliceAction createActivityAction() {
//return null;
//Instead of returning null, you should create a SliceAction. Here is an example:
return SliceAction.create(
PendingIntent.getActivity(
getContext(), 0, new Intent(getContext(), MainActivity.class), 0
),
IconCompat.createWithResource(getContext(), R.drawable.ic_launcher_foreground),
ListBuilder.ICON_IMAGE,
"Open App"
);
}通过安装
SliceViewer,在搜索框输入uri即可添加slice并跳转:
Slice模板
- ListBuilder:Slices通过ListBuilder类来创建。在ListBuilder中,你可以添加不同类型的行模块在你的Slice中进行展示。
- SliceAction:对于每一个Slice来说,最基础的构造实现类是SliceAction,在SliceAction你可以添加PendingIntent来实现用户操作,比如Toggle选择操作:
1
SliceAction toggleAction =SliceAction.createToggle(createToggleIntent(),"Toggle adaptive brightness",true);
SliceAction可以配置在搜索APP中显示的模块三种不同的显示方式:
ICON_IMAGE:tiny size and tintable:
SMALL_IMAGE:small size and non-tintable:
LARGE_IMAGE: largest size and non-tintable:
模块构造Builder
对于每个Slice模块的创建构造,谷歌官方提供了HeaderBuilder、RowBuilder、GridBuilder、RangeBuilder模块四种构造器。其中,HeaderBuilder只支持一行头部的展示view;RowBuilder可以添加一行view进行展示,如此前没有添加header,则首行row默认为header;GridBuilder支持上述所说的三种模块展示方式;而RangeBuilder则支持进度条相关的view展示。
延时加载
对于一些需要耗时加载数据的操作,比如网络请求图片等,可以采取与ListView加载图片类似的方法,先本地加载一个默认的占位数据,等耗时操作完成回调回来真实数据的时候调用getContentResolver().notifyChange(sliceUri) 方法,通知搜索APP调用Slice Uri,完成真实数据的显示。
结语
Slice的功能模板非常的强大,通过不同的builder组合可以在搜索模块中搭配出丰富多彩的Slice,快速直达用户想要的功能。但是Slice只提供了三种模板,自带模板中对安卓原生控件的支持有所欠缺,比如ScollView等,可能需要用户自定义自己的模板才能实现更强大的功能。
参考:
[0].https://www.androidauthority.com/android-slices-872250/
[1].https://www.jianshu.com/p/a90563606e1f
[2].http://digi.aili.com/1642/2811309.html