使用入门

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

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

  • AndroidX 重构工具:如果您在使用 AndroidX 库的项目中工作,则需要此工具。
  • 切片 lint 检查:捕获构建切片时的常见违例
  • SliceProvider 模板:处理构建 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);
        }
    }
}

模板

切片支持各种模板。如需详细了解模板选项和行为,请参阅模板