接收富媒体内容

图 1. 统一 API 提供了一个单一入口,用于处理传入的内容,而无需考虑具体的 UI 机制(例如从触摸并按住菜单粘贴,或使用拖放操作)。

用户喜欢图片、视频和其他富有表现力的内容,但在应用中插入和移动这些内容并不总是那么容易。为了让应用更轻松地接收富媒体内容,Android 12(API 级别 31)引入了一个统一的 API,让您的应用能够从任何来源接收内容:剪贴板、键盘或拖放操作。

您可以将接口(例如 OnReceiveContentListener)附加到 UI 组件上,并在通过任何机制插入内容时获得回调。该回调将成为您代码处理所有内容接收的唯一入口,从纯文本和样式文本到标记、图像、视频、音频文件等。

为了与以前的 Android 版本保持向后兼容,此 API 也可在 AndroidX 中使用,起始版本为 Core 1.7Appcompat 1.4,我们建议您在实现此功能时使用这些库。

概览

在现有的其他 API 中,每种 UI 机制(例如触摸并按住菜单或拖放操作)都有其对应的 API。这意味着您必须分别与每个 API 集成,并为每个插入内容的机制添加类似的代码。

An image showing the different actions and the relative API to implement
图 2. 此前,应用需要为每种插入内容的 UI 机制实现不同的 API。

OnReceiveContentListener API 通过创建一个单一的实现 API 来整合这些不同的代码路径,因此您可以专注于特定于应用的逻辑,而让平台处理其余部分。

An image showing the simplified unified API
图 3. 统一 API 让您能够实现一个支持所有 UI 机制的单一 API。

这种方法还意味着,当平台增加新的内容插入方式时,您无需进行额外的代码更改即可在应用中启用支持。如果您的应用需要针对特定用例实现完全自定义,您仍然可以使用现有的 API,它们将以相同的方式继续工作。

实现

该 API 是一个仅包含单个方法的监听器接口 OnReceiveContentListener。为了支持旧版本的 Android 平台,我们建议使用 AndroidX Core 库中匹配的 OnReceiveContentListener 接口。

要使用该 API,请通过指定您的应用可以处理的内容类型来实现监听器。

Kotlin

object MyReceiver : OnReceiveContentListener {
    val MIME_TYPES = arrayOf("image/*", "video/*")
    
    // ...
    
    override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
        TODO("Not yet implemented")
    }
}

Java

public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
     // ...
}

在指定了您的应用支持的所有内容 MIME 类型后,实现监听器的其余部分。

Kotlin

class MyReceiver : OnReceiveContentListener {
    override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat {
        val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
        val uriContent = split.first
        val remaining = split.second
        if (uriContent != null) {
            // App-specific logic to handle the URI(s) in uriContent.
        }
        // Return anything that your app didn't handle. This preserves the
        // default platform behavior for text and anything else that you aren't
        // implementing custom handling for.
        return remaining
    }

    companion object {
        val MIME_TYPES = arrayOf("image/*", "video/*")
    }
}

Java

 public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};

     @Override
     public ContentInfoCompat onReceiveContent(View view, ContentInfoCompat contentInfo) {
         Pair<ContentInfoCompat, ContentInfoCompat> split = contentInfo.partition(
                 item -> item.getUri() != null);
         ContentInfo uriContent = split.first;
         ContentInfo remaining = split.second;
         if (uriContent != null) {
             // App-specific logic to handle the URI(s) in uriContent.
         }
         // Return anything that your app didn't handle. This preserves the
         // default platform behavior for text and anything else that you aren't
         // implementing custom handling for.
         return remaining;
     }
 }

如果您的应用已经支持通过 Intent 进行共享,您可以复用处理内容 URI 的应用特定逻辑。返回任何剩余的数据,以将该数据的处理委托给平台。

实现监听器后,将其设置在应用中适当的 UI 元素上。

Kotlin

class MyActivity : Activity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        val myInput = findViewById(R.id.my_input)
        ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, MyReceiver())
    }
}

Java

public class MyActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // ...

         AppCompatEditText myInput = findViewById(R.id.my_input);
         ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, new MyReceiver());
     }
}

URI 权限

对于传递给 OnReceiveContentListener 的载荷(payload)中包含的任何 内容 URI,读取权限均由平台自动授予和释放。

通常,您的应用会在服务或 Activity 中处理内容 URI。对于长时间运行的处理,请使用 WorkManager。实现此操作时,通过使用 Intent.setClipData 传递内容并设置 FLAG_GRANT_READ_URI_PERMISSION 标志,将权限扩展到目标服务或 Activity。

或者,您可以使用当前上下文中的后台线程来处理内容。在这种情况下,您必须保持对监听器接收到的 payload 对象的引用,以帮助确保权限不会被平台过早撤销。

自定义视图

如果您的应用使用了自定义 View 子类,请注意确保 OnReceiveContentListener 不会被绕过。

如果您的 View 类重写了 onCreateInputConnection 方法,请使用 Jetpack API InputConnectionCompat.createWrapper 来配置 InputConnection

如果您的 View 类重写了 onTextContextMenuItem 方法,当菜单项为 R.id.pasteR.id.pasteAsPlainText 时,请委托给父类方法。

与键盘图像 API 的比较

您可以将 OnReceiveContentListener API 视为现有 键盘图像 API 的下一版本。此统一 API 支持键盘图像 API 的功能以及一些附加特性。设备和功能兼容性取决于您是使用 Jetpack 库还是 Android SDK 中的原生 API。

表 1. Jetpack 支持的功能和 API 级别。
操作或功能 键盘图像 API 支持 统一 API 支持
从键盘插入 是(API 级别 13 及更高) 是(API 级别 13 及更高)
从触摸并按住菜单使用粘贴功能插入
使用拖放操作插入 是(API 级别 24 及更高)
表 2. 原生 API 支持的功能和 API 级别。
操作或功能 键盘图像 API 支持 统一 API 支持
从键盘插入 是(API 级别 25 及更高) 是(Android 12 及更高)
从触摸并按住菜单使用粘贴功能插入
使用拖放操作插入