Nhận nội dung đa dạng thức

Hình 1. API hợp nhất cung cấp một nơi duy nhất để xử lý nội dung được chia sẻ bất kể cơ chế giao diện người dùng cụ thể là gì, chẳng hạn như dán từ trình đơn nhấn và giữ hoặc sử dụng tính năng kéo và thả.

Người dùng thích hình ảnh, video và các nội dung biểu đạt khác nhưng việc chèn và di chuyển nội dung này trong ứng dụng không phải lúc nào cũng dễ dàng. Để giúp các ứng dụng nhận được nội dung đa dạng thức một cách đơn giản hơn, Android 12 (API cấp độ 31) đã giới thiệu một API hợp nhất cho phép ứng dụng của bạn chấp nhận nội dung từ bất kỳ nguồn nào: bảng nhớ tạm, bàn phím hoặc thao tác kéo.

Bạn có thể đính kèm giao diện, chẳng hạn như OnReceiveContentListener vào các thành phần trên giao diện người dùng và nhận lệnh gọi lại khi nội dung được chèn thông qua mọi cơ chế. Lệnh gọi lại trở thành nơi duy nhất để mã của bạn xử lý việc nhận tất cả nội dung, từ văn bản thuần túy và có kiểu cho đến các dấu được đánh, hình ảnh, video, tệp âm thanh, v.v.

Để tương thích ngược với các phiên bản Android trước, API này cũng có trong AndroidX, bắt đầu từ Core 1.7Appcompat 1.4 (nên dùng khi triển khai chức năng này).

Tổng quan

Với các API hiện có khác, mỗi cơ chế giao diện người dùng (chẳng hạn như trình đơn chạm và giữ hoặc kéo) đều có API riêng. Điều này có nghĩa là bạn phải tích hợp riêng từng API, thêm mã tương tự cho từng cơ chế chèn nội dung:

Hình ảnh cho thấy các thao tác khác nhau và API tương đối để triển khai
Hình 2. Trước đây, các ứng dụng triển khai một API riêng cho mỗi cơ chế giao diện người dùng để chèn nội dung.

API OnReceiveContentListener hợp nhất các đường dẫn mã này bằng cách tạo một API duy nhất để triển khai, nhờ đó, bạn có thể tập trung vào logic riêng từng ứng dụng và để nền tảng xử lý phần còn lại:

Hình ảnh cho thấy API hợp nhất được đơn giản hoá
Hình 3. API hợp nhất này cho phép bạn triển khai một API duy nhất hỗ trợ tất cả các cơ chế giao diện người dùng.

Phương pháp này cũng có nghĩa là khi thêm các cách chèn nội dung mới vào nền tảng, bạn không cần phải thực hiện thêm thay đổi nào về mã để bật tính năng hỗ trợ trong ứng dụng. Và nếu ứng dụng của bạn cần triển khai tính năng tuỳ chỉnh đầy đủ cho một trường hợp sử dụng cụ thể, bạn vẫn có thể sử dụng các API hiện có, các API này sẽ tiếp tục hoạt động theo cách tương tự.

Triển khai

API là giao diện trình nghe có phương thức duy nhất là OnReceiveContentListener. Để hỗ trợ các phiên bản nền tảng Android cũ, bạn nên sử dụng giao diện OnReceiveContentListener phù hợp trong thư viện AndroidX Core.

Để sử dụng API này, hãy triển khai trình nghe bằng cách chỉ định loại nội dung mà ứng dụng của bạn có thể xử lý:

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/*"};
     // ...
}

Sau khi chỉ định tất cả các loại MIME nội dung mà ứng dụng hỗ trợ, hãy triển khai phần còn lại của trình nghe:

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;
     }
 }

Nếu ứng dụng đã hỗ trợ tính năng chia sẻ bằng ý định, bạn có thể sử dụng lại logic dành riêng cho ứng dụng để xử lý URI nội dung. Trả về mọi dữ liệu còn lại để ủy quyền việc xử lý dữ liệu đó cho nền tảng.

Sau khi triển khai trình nghe, hãy đặt nó trên các thành phần trên giao diện người dùng thích hợp trong ứng dụng:

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());
     }
}

Quyền URI

Nền tảng cấp và phát hành tự động quyền đọc cho mọi URI nội dung trong tải trọng được truyền đến OnReceiveContentListener.

Thông thường, ứng dụng xử lý URI nội dung trong một dịch vụ hoặc hoạt động. Đối với quy trình xử lý trong thời gian dài, hãy sử dụng WorkManager. Khi bạn triển khai việc này, hãy mở rộng quyền truy cập vào dịch vụ hoặc hoạt động mục tiêu bằng cách chuyển nội dung sử dụng Intent.setClipDatacài đặt cờ FLAG_GRANT_READ_URI_PERMISSION.

Ngoài ra, bạn có thể sử dụng chuỗi (thread) nền trong ngữ cảnh hiện tại để xử lý nội dung. Trong trường hợp này, bạn phải duy trì tham chiếu đến đối tượng payload mà trình nghe nhận được để đảm bảo nền tảng không thu hồi sớm các quyền đã cấp.

Chế độ xem tùy chỉnh

Nếu ứng dụng của bạn sử dụng lớp con View tuỳ chỉnh, hãy chú ý đảm bảo rằng OnReceiveContentListener không bị bỏ qua.

Nếu lớp View ghi đè phương thức onCreateInputConnection, hãy sử dụng Jetpack API InputConnectionCompat.createWrapper để định cấu hình InputConnection.

Nếu lớp View ghi đè phương thức onTextContextMenuItem, hãy uỷ quyền cho lớp cha khi mục trong trình đơn là R.id.paste hoặc R.id.pasteAsPlainText.

So sánh với API hình ảnh bàn phím

Bạn có thể coi API OnReceiveContentListener là phiên bản tiếp theo của API hình ảnh bàn phím hiện có. API thống nhất này hỗ trợ chức năng của API hình ảnh bàn phím cũng như một số tính năng bổ sung. Khả năng tương thích của thiết bị và tính năng sẽ thay đổi tùy thuộc vào việc bạn sử dụng thư viện Jetpack hoặc API gốc từ SDK Android.

Bảng 1. Các tính năng và cấp độ API được hỗ trợ cho Jetpack.
Hành động hoặc tính năng Được hỗ trợ bởi API hình ảnh bàn phím Được API hợp nhất hỗ trợ
Chèn từ bàn phím Có (API cấp 13 trở lên) Có (API cấp 13 trở lên)
Chèn bằng cách dán từ trình đơn chạm và giữ Không
Chèn bằng cách kéo và thả Không Có (API cấp 24 trở lên)
Bảng 2. Các tính năng và cấp độ API được hỗ trợ cho API gốc.
Hành động hoặc tính năng Được hỗ trợ bởi API hình ảnh bàn phím Được API hợp nhất hỗ trợ
Chèn từ bàn phím Có (API cấp 25 trở lên) Có (Android 12 trở lên)
Chèn bằng cách dán từ trình đơn chạm và giữ Không
Chèn bằng cách kéo và thả Không