รับเนื้อหาอย่างละเอียด

รูปที่ 1 API แบบรวมศูนย์มีที่เดียวสำหรับจัดการ เนื้อหาที่เข้ามา ไม่ว่ากลไก UI ที่เฉพาะเจาะจงจะเป็นอย่างไร เช่น การวาง จากเมนูแตะค้างไว้หรือการใช้การลากและวาง

ผู้ใช้ชื่นชอบรูปภาพ วิดีโอ และเนื้อหาที่แสดงออกอื่นๆ แต่การแทรกและย้ายเนื้อหาเหล่านี้ในแอปไม่ใช่เรื่องง่ายเสมอไป เพื่อให้แอปรับเนื้อหาที่สมบูรณ์ได้ง่ายขึ้น Android 12 (ระดับ API 31) จึงขอแนะนำ API แบบรวมศูนย์ที่ช่วยให้แอปยอมรับเนื้อหาจากแหล่งที่มาใดก็ได้ ไม่ว่าจะเป็นคลิปบอร์ด แป้นพิมพ์ หรือการลาก

คุณสามารถแนบอินเทอร์เฟซ เช่น OnReceiveContentListener, กับคอมโพเนนต์ UI และรับการเรียกกลับเมื่อมีการแทรกเนื้อหาผ่านกลไกใดก็ตาม การเรียกกลับจะกลายเป็นที่เดียวที่โค้ดของคุณใช้จัดการการรับเนื้อหาทั้งหมด ตั้งแต่ข้อความธรรมดาและข้อความที่มีสไตล์ ไปจนถึงมาร์กอัป รูปภาพ วิดีโอ ไฟล์เสียง และอื่นๆ

API นี้ยังพร้อมใช้งานใน AndroidX ตั้งแต่ Core 1.7 และ Appcompat 1.4 ซึ่งเราขอแนะนำให้คุณใช้เมื่อใช้งานฟังก์ชันนี้ เพื่อให้มีความเข้ากันได้แบบย้อนหลังกับ Android เวอร์ชันก่อนหน้า

ภาพรวม

API อื่นๆ ที่มีอยู่แต่ละกลไก UI เช่น เมนูแตะค้างไว้หรือการลาก จะมี API ที่เกี่ยวข้องของตัวเอง ซึ่งหมายความว่าคุณต้องผสานรวมกับ API แต่ละรายการแยกกัน โดยเพิ่มโค้ดที่คล้ายกันสำหรับกลไกแต่ละรายการที่แทรกเนื้อหา ดังนี้

รูปภาพแสดงการดำเนินการต่างๆ และ API ที่เกี่ยวข้องเพื่อใช้
รูปที่ 2 ก่อนหน้านี้ แอปจะใช้ API ที่แตกต่างกันสำหรับกลไก UI แต่ละรายการในการแทรกเนื้อหา

API OnReceiveContentListener จะรวมเส้นทางโค้ดที่แตกต่างกันเหล่านี้เข้าด้วยกันโดยสร้าง API เดียวเพื่อใช้งาน คุณจึงมุ่งเน้นไปที่ตรรกะเฉพาะของแอปได้ และปล่อยให้แพลตฟอร์มจัดการส่วนที่เหลือ

รูปภาพแสดง API แบบรวมที่เรียบง่าย
รูปที่ 3 API แบบรวมศูนย์ช่วยให้คุณใช้ API เดียวที่รองรับกลไก UI ทั้งหมดได้

แนวทางนี้ยังหมายความว่าเมื่อมีการเพิ่มวิธีใหม่ๆ ในการแทรกเนื้อหาลงในแพลตฟอร์ม คุณไม่จำเป็นต้องทำการเปลี่ยนแปลงโค้ดเพิ่มเติมเพื่อเปิดใช้การรองรับในแอป และหากแอปต้องใช้การปรับแต่งแบบเต็มสำหรับกรณีการใช้งานที่เฉพาะเจาะจง คุณยังคงใช้ API ที่มีอยู่ได้ ซึ่งจะยังคงทำงานในลักษณะเดิม

การใช้งาน

API เป็นอินเทอร์เฟซ Listener ที่มีเมธอดเดียวคือ OnReceiveContentListener เราขอแนะนำให้ใช้ matching OnReceiveContentListener อินเทอร์เฟซที่ตรงกันในไลบรารี AndroidX Core เพื่อรองรับแพลตฟอร์ม Android เวอร์ชันเก่า

หากต้องการใช้ API ให้ใช้ Listener โดยระบุประเภทเนื้อหาที่แอปจัดการได้

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 ของเนื้อหาทั้งหมดที่แอปของคุณรองรับแล้ว ให้ใช้ Listener ที่เหลือ

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 ของเนื้อหาได้ ส่งคืนข้อมูลที่เหลือเพื่อมอบหมายการจัดการข้อมูลนั้นให้กับแพลตฟอร์ม

หลังจากใช้ Listener แล้ว ให้ตั้งค่า Listener ในองค์ประกอบ 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

แพลตฟอร์มจะให้และยกเลิกสิทธิ์อ่านโดยอัตโนมัติสำหรับ URI ของเนื้อหาทั้งหมดใน เพย์โหลดที่ส่งไปยัง OnReceiveContentListener

โดยปกติแล้วแอปจะประมวลผล URI ของเนื้อหาในบริการหรือกิจกรรม สำหรับการประมวลผลที่ใช้เวลานาน ให้ใช้ WorkManager เมื่อใช้ฟีเจอร์นี้ ให้ขยายสิทธิ์ไปยังบริการหรือกิจกรรมเป้าหมายโดยส่ง เนื้อหาโดยใช้ Intent.setClipData และ ตั้งค่า แฟล็ก FLAG_GRANT_READ_URI_PERMISSION

หรือคุณจะใช้เธรดเบื้องหลังภายในบริบทปัจจุบันเพื่อประมวลผลเนื้อหาก็ได้ ในกรณีนี้ คุณต้องเก็บข้อมูลอ้างอิงถึงออบเจ็กต์ payload ที่ Listener ได้รับไว้ เพื่อช่วยให้มั่นใจว่าแพลตฟอร์มจะไม่เพิกถอนสิทธิ์ก่อนเวลาอันควร

มุมมองที่กำหนดเอง

หากแอปใช้คลาสย่อย View ที่กำหนดเอง ให้ระมัดระวังเพื่อให้แน่ใจว่าจะไม่มีการข้าม OnReceiveContentListener

หากคลาส View ลบล้างเมธอด onCreateInputConnection ให้ใช้ Jetpack API InputConnectionCompat.createWrapper เพื่อกำหนดค่า InputConnection

หากคลาส View ลบล้างเมธอด onTextContextMenuItem ให้มอบหมายไปยัง Super เมื่อรายการในเมนูคือ R.id.paste หรือ R.id.pasteAsPlainText

การเปรียบเทียบกับ Keyboard Image API

คุณสามารถคิดว่า OnReceiveContentListener API เป็น Keyboard Image API เวอร์ชันถัดไปของ ที่มีอยู่ API แบบรวมศูนย์นี้รองรับฟังก์ชันการทำงานของ Keyboard Image API รวมถึงฟีเจอร์เพิ่มเติมบางอย่าง ความเข้ากันได้ของอุปกรณ์และฟีเจอร์จะแตกต่างกันไปตามว่าคุณใช้ไลบรารี Jetpack หรือ API ดั้งเดิมจาก Android SDK

ตารางที่ 1 ฟีเจอร์และ API ระดับที่รองรับสำหรับ Jetpack
การกระทำหรือฟีเจอร์ รองรับโดย Keyboard Image API รองรับโดย API แบบรวมศูนย์
แทรกจากแป้นพิมพ์ ใช่ (ระดับ API 13 ขึ้นไป) ใช่ (ระดับ API 13 ขึ้นไป)
แทรกโดยใช้การวางจากเมนูแตะค้างไว้ ไม่ ใช่
แทรกโดยใช้การลากและวาง ไม่ ใช่ (ระดับ API 24 ขึ้นไป)
ตารางที่ 2 ฟีเจอร์และ API ระดับที่รองรับสำหรับ API ดั้งเดิม
การกระทำหรือฟีเจอร์ รองรับโดย Keyboard Image API รองรับโดย API แบบรวมศูนย์
แทรกจากแป้นพิมพ์ ใช่ (ระดับ API 25 ขึ้นไป) ใช่ (Android 12 ขึ้นไป)
แทรกโดยใช้การวางจากเมนูแตะค้างไว้ ไม่
แทรกโดยใช้การลากและวาง ไม่