Android pie slice开发小记

Android pie slice开发小记

slice创建和运行

  1. 首先新建一个项目并选择使用的api为API 28:Android 9(pie)或者在已有的项目调整其编译、最低和目标版本为28(最低)。
  2. 创建 slice provider:
    new slice step
    当生成slice provider后,编译器会报如下错误:
    1
    2
    3
    Manifest 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

  1. 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
    94
    public class PieSliceProvider extends SliceProvider {
    /**
    * Instantiate any required objects. Return true if the provider was successfully created,
    * false otherwise.
    */
    @Override
    public boolean onCreateSliceProvider() {
    return true;
    }

    /**
    * Converts URL to content URI (i.e. content://com.mugwort.demo...)
    */
    @Override
    @NonNull
    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.
    */
    @Override
    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.
    */
    @Override
    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
    29
    public 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
    12
    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"
    );
    }
  • 通过安装
    SliceViewer,在搜索框输入uri即可添加slice并跳转:
    SliceViewer

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:
shortcut

SMALL_IMAGE:small size and non-tintable:
small

  LARGE_IMAGE: largest size and non-tintable:
large

模块构造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