Android 11 开发者预览版现已推出;快来测试并分享您的反馈吧

使用入门

本页向您介绍如何在应用中设置环境和构建切片

注意:Android Studio 3.2 或更高版本包含一些可帮助您开发切片的附加工具和功能:

  • AndroidX 重构工具:如果您创建的是使用 AndroidX 库的项目,则需要此工具。
  • 切片 lint 检查:捕获构建切片时的常见违例
  • SliceProvider 模板:处理构建 时的样板

下载并安装切片查看器

下载最新的示例切片查看器 APK 版本,用于在不实现 SliceView API 的情况下测试切片。您还可以克隆切片查看器源代码

如果您的环境中未正确设置 ADB,请参阅 ADB 指南了解详情。

在下载了 slice-viewer.apk 的同一目录中运行以下命令,将切片查看器安装到您的设备上:

adb install -r -t slice-viewer.apk
    

运行切片查看器

您可以从 Android Studio 项目或命令行中启动切片查看器:

从 Android Studio 项目中启动切片查看器

  1. 在您的项目中,依次选择 Run > Edit Configurations...
  2. 点击左上角的绿色加号
  3. 选择 Android App

  4. 在名称字段中输入“slice”

  5. Module 下拉列表中选择您的应用模块

  6. Launch Options 下的 Launch 下拉列表中,选择 URL

  7. 在 URL 字段中输入 slice-<your slice URI>

    示例:slice-content://com.example.your.sliceuri

  8. 点击 OK

通过 ADB(命令行)启动切片查看器工具

从 Android Studio 运行您的应用:

adb install -t -r <yourapp>.apk
    

通过运行以下命令来查看您的切片:

adb shell am start -a android.intent.action.VIEW -d slice-<your slice URI>
    

切片查看器显示单个 WLAN 切片

在一个位置集中查看您的所有切片

除了启动单个切片外,您还可以查看切片的持久性列表。

  • 使用搜索栏通过 URI(例如,content://com.example.android.app/hello)手动搜索您的切片。每次搜索时,该切片都会添加到列表中。
  • 每当您使用切片 URI 启动切片查看器工具时,该切片都会添加到列表中。
  • 您可以滑动切片以将其从列表中移除。
  • 点按该切片的 URI 即可查看仅包含该切片的网页。这与使用切片 URI 启动切片查看器的效果相同。

切片查看器显示切片列表

在不同模式下查看切片

呈现切片的应用可以在运行时修改 SliceView#mode,因此您应该确保切片在每种模式下均按预期显示。 选择页面右上方的菜单图标即可更改模式。

模式设置为“小”的单个切片查看器

构建您的第一个切片

要构建切片,请打开您的 Android Studio 项目,右键点击 src 软件包,然后依次选择 New... > Other > Slice Provider。这会创建一个扩展 SliceProvider 的类,向您的 AndroidManifest.xml 添加所需的提供程序条目,并修改您的 build.gradle 以添加所需的切片依赖项。

AndroidManifest.xml 的修改如下所示:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.android.app">
        ...
        <application>
            ...
            <provider android:name="MySliceProvider"
                android:authorities="com.example.android.app"
                android:exported="true" >
                <intent-filter>
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.app.slice.category.SLICE" />
                </intent-filter>
            </provider>
            ...
        </application>

    </manifest>
    

以下依赖项会添加到您的 build.gradle

Kotlin

    dependencies {
    // ...
        implementation "androidx.slice:slice-builders-ktx:(latest version)"
    // ...
    }
    

Java

    dependencies {
    // ...
        implementation "androidx.slice:slice-builders:(latest version)"
    // ...
    }
    

每个切片都有一个关联的 URI。当界面想要显示切片时,它会通过该 URI 向您的应用发送绑定请求。然后您的应用会通过 onBindSlice 方法处理该请求,并动态构建切片。界面随后会根据情况显示切片。

以下示例中的 onBindSlice 方法会检查是否存在 /hello URI 路径并返回 Hello World 切片:

Kotlin

    override fun onBindSlice(sliceUri: Uri): Slice? {
        val activityAction = createActivityAction()
        return if (sliceUri.path == "/hello") {
            list(context, sliceUri, ListBuilder.INFINITY) {
                row {
                    primaryAction = activityAction
                    title = "Hello World."
                }
            }
        } else {
            list(context, sliceUri, ListBuilder.INFINITY) {
                row {
                    primaryAction = activityAction
                    title = "URI not recognized."
                }
            }
        }
    }
    

Java

    @Override
    public Slice onBindSlice(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        SliceAction activityAction = createActivityAction();
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
        // Create parent ListBuilder.
        if ("/hello".equals(sliceUri.getPath())) {
            listBuilder.addRow(new ListBuilder.RowBuilder()
                    .setTitle("Hello World")
                    .setPrimaryAction(activityAction)
            );
        } else {
            listBuilder.addRow(new ListBuilder.RowBuilder()
                    .setTitle("URI not recognized")
                    .setPrimaryAction(activityAction)
            );
        }
        return listBuilder.build();
    }
    

使用您在上述“切片查看器”部分中创建的 slice 运行配置,传入 Hello World 切片的切片 URI(例如,slice-content://com.android.example.slicesample/hello),以在切片查看器中查看该切片。

互动式切片

与通知类似,要处理切片中的点按操作,您可以附加一些在用户互动时触发的 PendingIntent 对象。以下示例会启动一个可以接收和处理这些 intent 的 Activity

Kotlin

    fun createSlice(sliceUri: Uri): Slice {
        val activityAction = createActivityAction()
        return list(context, sliceUri, INFINITY) {
            row {
                title = "Perform action in app"
                primaryAction = activityAction
            }
        }
    }

    fun createActivityAction(): SliceAction {
        val intent = Intent(context, MainActivity::class.java)
        return SliceAction.create(
            PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0),
            IconCompat.createWithResource(context, R.drawable.ic_home),
            ListBuilder.ICON_IMAGE,
            "Enter app"
        )
    }
    

Java

    public Slice createSlice(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        SliceAction activityAction = createActivityAction();
        return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                .addRow(new ListBuilder.RowBuilder()
                        .setTitle("Perform action in app.")
                        .setPrimaryAction(activityAction)
                ).build();
    }

    public SliceAction createActivityAction() {
        if (getContext() == null) {
            return null;
        }
        return SliceAction.create(
                PendingIntent.getActivity(
                        getContext(),
                        0,
                        new Intent(getContext(), MainActivity.class),
                        0
                ),
                IconCompat.createWithResource(getContext(), R.drawable.ic_home),
                ListBuilder.ICON_IMAGE,
                "Enter app"
        );
    }
    

切片还支持其他输入类型,例如切换开关,它们会在发送到应用的 intent 中包含状态。

Kotlin

    fun createBrightnessSlice(sliceUri: Uri): Slice {
        val toggleAction =
            SliceAction.createToggle(
                createToggleIntent(),
                "Toggle adaptive brightness",
                true
            )
        return list(context, sliceUri, ListBuilder.INFINITY) {
            row {
                title = "Adaptive brightness"
                subtitle = "Optimizes brightness for available light"
                primaryAction = toggleAction
            }
            inputRange {
                inputAction = (brightnessPendingIntent)
                max = 100
                value = 45
            }
        }
    }

    fun createToggleIntent(): PendingIntent {
        val intent = Intent(context, MyBroadcastReceiver::class.java)
        return PendingIntent.getBroadcast(context, 0, intent, 0)
    }
    

Java

    public Slice createBrightnessSlice(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        SliceAction toggleAction = SliceAction.createToggle(
                createToggleIntent(),
                "Toggle adaptive brightness",
                true
        );
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                .addRow(new ListBuilder.RowBuilder()
                        .setTitle("Adaptive brightness")
                        .setSubtitle("Optimizes brightness for available light.")
                        .setPrimaryAction(toggleAction)
                ).addInputRange(new ListBuilder.InputRangeBuilder()
                        .setInputAction(brightnessPendingIntent)
                        .setMax(100)
                        .setValue(45)
                );
        return listBuilder.build();
    }

    public PendingIntent createToggleIntent() {
        Intent intent = new Intent(getContext(), MyBroadcastReceiver.class);
        return PendingIntent.getBroadcast(getContext(), 0, intent, 0);
    }
    

然后接收器可以检查接收到的状态:

Kotlin

    class MyBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
                Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                        Slice.EXTRA_TOGGLE_STATE, false),
                        Toast.LENGTH_LONG).show()
            }
        }

        companion object {
            const val EXTRA_MESSAGE = "message"
        }
    }
    

Java

    public class MyBroadcastReceiver extends BroadcastReceiver {

        public static String EXTRA_MESSAGE = "message";

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.hasExtra(EXTRA_TOGGLE_STATE)) {
                Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                        EXTRA_TOGGLE_STATE, false),
                        Toast.LENGTH_LONG).show();
            }
        }
    }
    

动态切片

切片还可以包含动态内容。在以下示例中,切片的内容中现在包括接收的广播数量:

Kotlin

    fun createDynamicSlice(sliceUri: Uri): Slice {
        return when (sliceUri.path) {
            "/count" -> {
                val toastAndIncrementAction = SliceAction.create(
                    createToastAndIncrementIntent("Item clicked."),
                    actionIcon,
                    ListBuilder.ICON_IMAGE,
                    "Increment."
                )
                list(context, sliceUri, ListBuilder.INFINITY) {
                    row {
                        primaryAction = toastAndIncrementAction
                        title = "Count: ${MyBroadcastReceiver.receivedCount}"
                        subtitle = "Click me"
                    }
                }
            }

            else -> {
                list(context, sliceUri, ListBuilder.INFINITY) {
                    row {
                        primaryAction = createActivityAction()
                        title = "URI not found."
                    }
                }
            }
        }
    }

    fun createToastAndIncrementIntent(s: String): PendingIntent {
        return PendingIntent.getBroadcast(
            context, 0,
            Intent(context, MyBroadcastReceiver::class.java)
                .putExtra(MyBroadcastReceiver.EXTRA_MESSAGE, s), 0
        )
    }
    

Java

    public Slice createDynamicSlice(Uri sliceUri) {
        if (getContext() == null || sliceUri.getPath() == null) {
            return null;
        }
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
        switch (sliceUri.getPath()) {
            case "/count":
                SliceAction toastAndIncrementAction = SliceAction.create(
                        createToastAndIncrementIntent("Item clicked."),
                        actionIcon,
                        ListBuilder.ICON_IMAGE,
                        "Increment."
                );
                listBuilder.addRow(
                        new ListBuilder.RowBuilder()
                                .setPrimaryAction(toastAndIncrementAction)
                                .setTitle("Count: " + MyBroadcastReceiver.sReceivedCount)
                                .setSubtitle("Click me")
                );
                break;
            default:
                listBuilder.addRow(
                        new ListBuilder.RowBuilder()
                                .setPrimaryAction(createActivityAction())
                                .setTitle("URI not found.")
                );
                break;
        }
        return listBuilder.build();
    }

    public PendingIntent createToastAndIncrementIntent(String s) {
        Intent intent = new Intent(getContext(), MyBroadcastReceiver.class)
                .putExtra(MyBroadcastReceiver.EXTRA_MESSAGE, s);
        return PendingIntent.getBroadcast(getContext(), 0, intent, 0);
    }
    

在此示例中,虽然显示了计数,但它不会自行更新。您可以修改您的广播接收器,以使用 ContentResolver#notifyChange 来通知系统发生了更改。

Kotlin

    class MyBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
                Toast.makeText(
                    context, "Toggled:  " + intent.getBooleanExtra(
                        Slice.EXTRA_TOGGLE_STATE, false
                    ),
                    Toast.LENGTH_LONG
                ).show()
                receivedCount++;
                context.contentResolver.notifyChange(sliceUri, null)
            }
        }

        companion object {
            var receivedCount = 0
            val sliceUri = Uri.parse("content://com.android.example.slicesample/count")
            const val EXTRA_MESSAGE = "message"
        }
    }
    

Java

    public class MyBroadcastReceiver extends BroadcastReceiver {

        public static int sReceivedCount = 0;
        public static String EXTRA_MESSAGE = "message";

        private static Uri sliceUri = Uri.parse("content://com.android.example.slicesample/count");

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.hasExtra(EXTRA_TOGGLE_STATE)) {
                Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                        EXTRA_TOGGLE_STATE, false),
                        Toast.LENGTH_LONG).show();
                sReceivedCount++;
                context.getContentResolver().notifyChange(sliceUri, null);
            }
        }
    }
    

模板

切片支持各种模板。有关模板选项和行为的详情,请参阅模板