ภาษานิยามอินเทอร์เฟซ Android (AIDL)

ภาษาที่ใช้สื่อสารข้อมูลระหว่างคอมโพเนนต์ของ Android (AIDL) คล้ายกับ IDL อื่นๆ ตรงที่ช่วยให้คุณกำหนดอินเทอร์เฟซการเขียนโปรแกรมที่ทั้งไคลเอ็นต์และบริการยอมรับเพื่อสื่อสารกันโดยใช้การสื่อสารระหว่างกระบวนการ (IPC)

ใน Android โดยทั่วไปแล้ว กระบวนการหนึ่งจะเข้าถึงหน่วยความจำของอีกกระบวนการหนึ่งไม่ได้ หากต้องการพูดคุย โปรแกรมจะต้องแยกวัตถุออกเป็นองค์ประกอบพื้นฐานที่ระบบปฏิบัติการเข้าใจและจัดเรียงวัตถุข้ามขอบเขตนั้นให้คุณ โค้ดสำหรับการจัดระเบียบนั้นเขียนได้ยาก Android จึงจัดการให้คุณด้วย AIDL

หมายเหตุ: คุณต้องใช้ AIDL เฉพาะในกรณีที่อนุญาตให้ไคลเอ็นต์จากแอปพลิเคชันต่างๆ เข้าถึงบริการของคุณสําหรับ IPC และคุณต้องการจัดการการแยกหลายเธรดในบริการ หากไม่จําเป็นต้องทํา IPC พร้อมกันในแอปพลิเคชันต่างๆ ให้สร้างอินเทอร์เฟซโดยการใช้ Binder หากต้องการใช้ IPC แต่ไม่ต้องจัดการการแยกหลายเธรด ให้ติดตั้งใช้งานอินเทอร์เฟซโดยใช้ Messenger อย่างไรก็ตาม โปรดทำความเข้าใจบริการที่เชื่อมโยงก่อนติดตั้งใช้งาน AIDL

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

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

การกําหนดอินเทอร์เฟซ AIDL

กำหนดอินเทอร์เฟซ AIDL ในไฟล์ .aidl โดยใช้ไวยากรณ์ภาษาโปรแกรม Java จากนั้นบันทึกไว้ในซอร์สโค้ดในไดเรกทอรี src/ ของแอปพลิเคชันที่โฮสต์บริการและแอปพลิเคชันอื่นๆ ที่เชื่อมโยงกับบริการ

เมื่อคุณสร้างแอปพลิเคชันแต่ละรายการที่มีไฟล์ .aidl เครื่องมือ Android SDK จะสร้างอินเทอร์เฟซ IBinder โดยอิงตามไฟล์ .aidl และบันทึกไว้ในไดเรกทอรี gen/ ของโปรเจ็กต์ บริการต้องใช้อินเทอร์เฟซ IBinder ตามความเหมาะสม จากนั้นแอปพลิเคชันไคลเอ็นต์จะเชื่อมโยงกับบริการและเรียกใช้เมธอดจาก IBinder เพื่อดำเนินการ IPC ได้

หากต้องการสร้างบริการที่มีขอบเขตโดยใช้ AIDL ให้ทําตามขั้นตอนต่อไปนี้ ซึ่งอธิบายไว้ในส่วนต่อไปนี้

  1. สร้างไฟล์ .aidl

    ไฟล์นี้จะกำหนดอินเทอร์เฟซการเขียนโปรแกรมด้วยลายเซ็นเมธอด

  2. ติดตั้งใช้งานอินเทอร์เฟซ

    เครื่องมือ Android SDK จะสร้างอินเทอร์เฟซในภาษาการเขียนโปรแกรม Java โดยอิงตามไฟล์ .aidl อินเทอร์เฟซนี้มีคลาสนามธรรมภายในชื่อ Stub ที่ขยายจาก Binder และใช้เมธอดจากอินเทอร์เฟซ AIDL คุณต้องขยายคลาส Stub และติดตั้งใช้งานเมธอด

  3. แสดงอินเทอร์เฟซต่อไคลเอ็นต์

    ใช้ Service และลบล้าง onBind() เพื่อแสดงการใช้งานคลาส Stub

ข้อควรระวัง: การเปลี่ยนแปลงใดๆ ที่คุณทำกับอินเทอร์เฟซ AIDL หลังจากรุ่นแรกต้องยังคงใช้งานร่วมกันได้ย้อนหลังเพื่อไม่ให้แอปพลิเคชันอื่นๆ ที่ใช้บริการของคุณทำงานผิดพลาด กล่าวคือ เนื่องจากต้องคัดลอกไฟล์ .aidl ไปยังแอปพลิเคชันอื่นๆ เพื่อให้เข้าถึงอินเทอร์เฟซของบริการได้ คุณจึงต้องรองรับอินเทอร์เฟซเดิมต่อไป

สร้างไฟล์ .aidl

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

คุณต้องสร้างไฟล์ .aidl โดยใช้ภาษาในการเขียนโปรแกรม Java ไฟล์ .aidl แต่ละไฟล์ต้องกำหนดอินเทอร์เฟซเดียวและต้องมีเพียงการประกาศอินเทอร์เฟซและลายเซ็นเมธอดเท่านั้น

โดยค่าเริ่มต้น AIDL รองรับประเภทข้อมูลต่อไปนี้

  • ประเภทพื้นฐานทั้งหมดยกเว้น short ในภาษาโปรแกรม Java (เช่น int, long,char, boolean และอื่นๆ)
  • อาร์เรย์ประเภทใดก็ได้ เช่น int[] หรือ MyParcelable[]
  • String
  • CharSequence
  • List

    องค์ประกอบทั้งหมดใน List ต้องเป็นประเภทข้อมูลที่รองรับในรายการนี้ หรืออินเทอร์เฟซหรือ Parcelable ที่ AIDL สร้างขึ้นรายการใดรายการหนึ่งที่คุณประกาศ คุณสามารถใช้ List เป็นคลาสประเภทที่มีพารามิเตอร์ได้ เช่น List<String> คลาสที่แท้จริงที่อีกฝั่งหนึ่งได้รับจะเป็น ArrayList เสมอ แม้ว่าจะมีการสร้างขึ้นเพื่อใช้อินเทอร์เฟซ List

  • Map

    องค์ประกอบทั้งหมดใน Map ต้องเป็นประเภทข้อมูลที่รองรับในรายการนี้ หรืออินเทอร์เฟซหรือ Parcelable ที่ AIDL สร้างขึ้นรายการใดรายการหนึ่งที่คุณประกาศ ระบบไม่รองรับการแมปประเภทที่มีพารามิเตอร์ เช่น Map<String,Integer> คลาสที่แท้จริงที่ฝั่งตรงข้ามได้รับจะเป็น HashMap เสมอ แม้ว่าระบบจะสร้างเมธอดให้ใช้อินเทอร์เฟซ Map ก็ตาม ลองใช้ Bundle แทน Map

คุณต้องใส่คำสั่ง import สำหรับประเภทเพิ่มเติมแต่ละประเภทที่ไม่ได้ระบุไว้ก่อนหน้านี้ แม้ว่าจะกำหนดไว้ในแพ็กเกจเดียวกับอินเทอร์เฟซก็ตาม

โปรดคำนึงถึงสิ่งต่อไปนี้เมื่อกำหนดอินเทอร์เฟซบริการ

  • เมธอดสามารถใช้พารามิเตอร์ได้ตั้งแต่ 0 รายการขึ้นไป และสามารถแสดงผลค่าหรือค่าว่าง
  • พารามิเตอร์ที่ไม่ใช่แบบพื้นฐานทั้งหมดต้องมีแท็กทิศทางซึ่งระบุทิศทางของข้อมูล ดังนี้ in, out หรือ inout (ดูตัวอย่างด้านล่าง)

    อินเทอร์เฟซพื้นฐาน, String, IBinder และที่ AIDL สร้างขึ้นจะเป็น in โดยค่าเริ่มต้นและไม่สามารถเป็นอย่างอื่นได้

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

  • ความคิดเห็นโค้ดทั้งหมดที่รวมอยู่ในไฟล์ .aidl จะรวมอยู่ในอินเทอร์เฟซ IBinder ที่สร้างขึ้น ยกเว้นความคิดเห็นก่อนคำสั่งการนําเข้าและแพ็กเกจ
  • คุณกำหนดค่าคงที่สตริงและ int ได้ในอินเทอร์เฟซ AIDL เช่น const int VERSION = 1;
  • การเรียกใช้เมธอดจะส่งโดยtransact() โค้ด ซึ่งปกติจะอิงตามดัชนีเมธอดในอินเทอร์เฟซ เนื่องจากวิธีนี้ทำให้การแยกเวอร์ชันทำได้ยาก คุณจึงกำหนดรหัสธุรกรรมให้กับเมธอด void method() = 10; ด้วยตนเองได้
  • อาร์กิวเมนต์และประเภทผลลัพธ์ที่อนุญาตค่า Null ต้องมีการกำกับเนื้อหาโดยใช้ @nullable

ตัวอย่างไฟล์ .aidl มีดังนี้

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements.

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

บันทึกไฟล์ .aidl ในไดเรกทอรี src/ ของโปรเจ็กต์ เมื่อคุณสร้างแอปพลิเคชัน เครื่องมือ SDK จะสร้างไฟล์อินเทอร์เฟซ IBinder ในไดเรกทอรี gen/ ของโปรเจ็กต์ ชื่อของไฟล์ที่สร้างขึ้นจะตรงกับชื่อของไฟล์ .aidl แต่มีส่วนขยาย .java เช่น IRemoteService.aidl จะแสดงผลเป็น IRemoteService.java

หากคุณใช้ Android Studio การสร้างแบบเพิ่มจะสร้างคลาส Binder เกือบจะทันที หากคุณไม่ได้ใช้ Android Studio เครื่องมือ Gradle จะสร้างคลาส Binder เมื่อคุณสร้างแอปพลิเคชันครั้งถัดไป บิลด์โปรเจ็กต์ด้วย gradle assembleDebug หรือ gradle assembleRelease ทันทีที่เขียนไฟล์ .aidl เสร็จแล้ว เพื่อให้โค้ดลิงก์กับคลาสที่สร้างขึ้นได้

ใช้อินเทอร์เฟซ

เมื่อคุณสร้างแอปพลิเคชัน เครื่องมือ Android SDK จะสร้างไฟล์อินเทอร์เฟซ .java โดยตั้งชื่อตามไฟล์ .aidl อินเทอร์เฟซที่สร้างขึ้นจะมีคลาสย่อยชื่อ Stub ซึ่งเป็นการใช้งานนามธรรมของอินเทอร์เฟซหลัก เช่น YourInterface.Stub และประกาศเมธอดทั้งหมดจากไฟล์ .aidl

หมายเหตุ: Stub ยังกำหนดเมธอดตัวช่วย 2-3 เมธอดด้วย โดยเฉพาะอย่างยิ่ง asInterface() ซึ่งใช้ IBinder ซึ่งโดยปกติแล้วจะเป็นเมธอดที่ส่งไปยังเมธอด onServiceConnected() ของไคลเอ็นต์ และแสดงผลอินสแตนซ์ของอินเทอร์เฟซสแต็บ ดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีแคสต์ได้ที่ส่วนการเรียกใช้เมธอด IPC

หากต้องการใช้อินเทอร์เฟซที่สร้างขึ้นจาก .aidl ให้ขยายอินเทอร์เฟซ Binder ที่สร้างขึ้น เช่น YourInterface.Stub และใช้เมธอดที่รับค่ามาจากไฟล์ .aidl

ต่อไปนี้คือตัวอย่างการใช้งานอินเทอร์เฟซชื่อ IRemoteService ซึ่งกำหนดโดยตัวอย่าง IRemoteService.aidl ก่อนหน้า โดยใช้อินสแตนซ์ที่ไม่ระบุตัวตน

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing.
    }
};

ตอนนี้ binder จึงเป็นอินสแตนซ์ของคลาส Stub (Binder) ซึ่งกำหนดอินเทอร์เฟซ IPC สำหรับบริการ ในขั้นตอนถัดไป ระบบจะแสดงอินสแตนซ์นี้ต่อไคลเอ็นต์เพื่อให้ไคลเอ็นต์โต้ตอบกับบริการได้

โปรดคำนึงถึงกฎต่อไปนี้เมื่อติดตั้งใช้งานอินเทอร์เฟซ AIDL

  • ระบบไม่รับประกันว่าสายเรียกเข้าจะดำเนินการบนเธรดหลัก ดังนั้นคุณต้องคำนึงถึงมัลติเธรดตั้งแต่เริ่มต้นและสร้างบริการอย่างเหมาะสมเพื่อให้มีความปลอดภัยของเธรด
  • โดยค่าเริ่มต้น การเรียก IPC จะเป็นแบบซิงค์ หากคุณทราบว่าบริการใช้เวลามากกว่า 2-3 มิลลิวินาทีในการดำเนินการตามคำขอให้เสร็จสมบูรณ์ อย่าเรียกบริการนั้นจากเธรดหลักของกิจกรรม ซึ่งอาจทำให้แอปพลิเคชันค้าง ทำให้ Android แสดงกล่องโต้ตอบ "แอปพลิเคชันไม่ตอบสนอง" เรียกใช้จากเธรดแยกต่างหากในไคลเอ็นต์
  • ระบบจะส่งเฉพาะประเภทข้อยกเว้นที่ระบุไว้ในเอกสารอ้างอิงสำหรับ Parcel.writeException() กลับไปให้ผู้โทร

แสดงอินเทอร์เฟซต่อไคลเอ็นต์

เมื่อติดตั้งใช้งานอินเทอร์เฟซสําหรับบริการแล้ว คุณต้องแสดงอินเทอร์เฟซต่อไคลเอ็นต์เพื่อให้ไคลเอ็นต์สามารถเชื่อมโยงกับอินเทอร์เฟซได้ หากต้องการแสดงอินเทอร์เฟซสำหรับบริการ ให้ขยาย Service และใช้งาน onBind() เพื่อแสดงอินสแตนซ์ของคลาสที่ใช้ Stub ที่สร้างขึ้น ตามที่อธิบายไว้ในส่วนก่อนหน้า ต่อไปนี้คือตัวอย่างบริการที่แสดงอินเทอร์เฟซตัวอย่าง IRemoteService แก่ลูกค้า

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface.
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing.
        }
    };
}

ตอนนี้เมื่อไคลเอ็นต์ เช่น กิจกรรม เรียก bindService() เพื่อเชื่อมต่อกับบริการนี้ ฟังก์ชันการเรียกกลับ onServiceConnected() ของไคลเอ็นต์จะได้รับอินสแตนซ์ binder ที่แสดงผลโดยเมธอด onBind() ของบริการ

นอกจากนี้ ไคลเอ็นต์ต้องมีสิทธิ์เข้าถึงคลาสอินเทอร์เฟซด้วย ดังนั้นหากไคลเอ็นต์และบริการอยู่ในแอปพลิเคชันที่แตกต่างกัน แอปพลิเคชันไคลเอ็นต์ต้องมีสำเนาไฟล์ .aidl ในไดเรกทอรี src/ ซึ่งจะสร้างอินเทอร์เฟซ android.os.Binder เพื่อให้ไคลเอ็นต์เข้าถึงเมธอด AIDL ได้

เมื่อไคลเอ็นต์ได้รับ IBinder ใน Callback ของ onServiceConnected() จะต้องเรียก YourServiceInterface.Stub.asInterface(service) เพื่อแคสต์พารามิเตอร์ที่แสดงผลเป็นประเภท YourServiceInterface ดังนี้

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

ดูโค้ดตัวอย่างเพิ่มเติมได้จากคลาส RemoteService.java ใน ApiDemos

การส่งออบเจ็กต์ผ่าน IPC

ใน Android 10 (API ระดับ 29 ขึ้นไป) คุณสามารถกำหนดออบเจ็กต์ Parcelable ใน AIDL ได้โดยตรง ระบบยังรองรับประเภทที่ใช้เป็นอาร์กิวเมนต์อินเทอร์เฟซ AIDL และประเภทอื่นๆ ที่ส่งผ่านได้ วิธีนี้จะช่วยประหยัดเวลาในการเขียนโค้ดการจัดเรียงและคลาสที่กำหนดเองด้วยตนเอง แต่การดำเนินการนี้จะสร้างสตรูคเจอร์ที่ไม่มีข้อมูล หากต้องการใช้ตัวรับเฉพาะหรือฟังก์ชันการทำงานอื่นๆ ให้ใช้ Parcelable แทน

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}

ตัวอย่างโค้ดก่อนหน้านี้จะสร้างคลาส Java ที่มีช่องจำนวนเต็ม left, top, right และ bottom โดยอัตโนมัติ ระบบจะติดตั้งใช้งานโค้ดการจัดระเบียบที่เกี่ยวข้องทั้งหมดโดยอัตโนมัติ และคุณใช้ออบเจ็กต์ได้โดยตรงโดยไม่ต้องติดตั้งใช้งานใดๆ

นอกจากนี้ คุณยังส่งคลาสที่กำหนดเองจากกระบวนการหนึ่งไปยังอีกกระบวนการหนึ่งผ่านอินเทอร์เฟซ IPC ได้ด้วย อย่างไรก็ตาม ให้ตรวจสอบว่าโค้ดของคลาสพร้อมใช้งานสำหรับอีกฝั่งของช่องทาง IPC และคลาสของคุณต้องรองรับอินเทอร์เฟซ Parcelable การรองรับ Parcelable มีความสำคัญเนื่องจากช่วยให้ระบบ Android สามารถแยกองค์ประกอบของออบเจ็กต์ออกเป็นองค์ประกอบพื้นฐานที่จัดการได้ทั่วทั้งกระบวนการ

หากต้องการสร้างคลาสที่กำหนดเองซึ่งรองรับ Parcelable ให้ทําดังนี้

  1. ทำให้คลาสของคุณใช้อินเทอร์เฟซ Parcelable
  2. ใช้ writeToParcel ซึ่งจะนําสถานะปัจจุบันของออบเจ็กต์ไปเขียนลงใน Parcel
  3. เพิ่มฟิลด์แบบคงที่ชื่อ CREATOR ลงในคลาสที่เป็นออบเจ็กต์ที่ใช้อินเทอร์เฟซ Parcelable.Creator
  4. สุดท้าย ให้สร้างไฟล์ .aidl ที่ประกาศคลาสที่แยกชิ้นส่วนได้ ดังที่แสดงในไฟล์ Rect.aidl ต่อไปนี้

    หากคุณใช้กระบวนการสร้างที่กำหนดเอง โปรดอย่าเพิ่มไฟล์ .aidl ลงในบิลด์ ไฟล์ .aidl นี้จะไม่ได้รับการคอมไพล์ ซึ่งคล้ายกับไฟล์ส่วนหัวในภาษา C

AIDL จะใช้เมธอดและฟิลด์เหล่านี้ในโค้ดที่สร้างขึ้นเพื่อจัดระเบียบและจัดระเบียบวัตถุของคุณ

ตัวอย่างเช่น ไฟล์ Rect.aidl เพื่อสร้างคลาส Rect ที่แบ่งออกเป็นส่วนๆ ได้

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

และนี่คือตัวอย่างวิธีที่คลาส Rect ใช้โปรโตคอล Parcelable

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

การจัดเรียงในคลาส Rect ทำได้ง่าย ดูเมธอดอื่นๆ ใน Parcel เพื่อดูค่าประเภทอื่นๆ ที่คุณเขียนลงใน Parcel ได้

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

เมธอดที่มีอาร์กิวเมนต์ Bundle ซึ่งมี Parcelables

หากเมธอดยอมรับออบเจ็กต์ Bundle ที่คาดว่าจะมีออบเจ็กต์ที่แยกพาร์ติชันได้ ให้ตรวจสอบว่าคุณได้ตั้งค่าคลาสโหลดเดอร์ของ Bundle โดยการเรียกใช้ Bundle.setClassLoader(ClassLoader) ก่อนที่จะพยายามอ่านจาก Bundle มิเช่นนั้น คุณอาจพบ ClassNotFoundException แม้ว่าจะกำหนด Parcelable อย่างถูกต้องในแอปพลิเคชันแล้วก็ตาม

ตัวอย่างเช่น ลองดูตัวอย่างไฟล์ .aidl ต่อไปนี้

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
ดังที่แสดงในการใช้งานต่อไปนี้ ClassLoader ได้รับการตั้งค่าอย่างชัดเจนใน Bundle ก่อนอ่าน Rect

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

การเรียกใช้เมธอด IPC

หากต้องการเรียกอินเทอร์เฟซระยะไกลที่กําหนดด้วย AIDL ให้ทําตามขั้นตอนต่อไปนี้ในคลาสที่เรียกใช้

  1. ใส่ไฟล์ .aidl ในไดเรกทอรี src/ ของโปรเจ็กต์
  2. ประกาศอินสแตนซ์ของอินเทอร์เฟซ IBinder ซึ่งสร้างขึ้นตาม AIDL
  3. ใช้งาน ServiceConnection
  4. โทรหา Context.bindService() โดยส่งการใช้งาน ServiceConnection
  5. ในการใช้งาน onServiceConnected() คุณได้รับอินสแตนซ์ IBinder ชื่อว่า service โทรไปที่ YourInterfaceName.Stub.asInterface((IBinder)service) เพื่อแคสต์พารามิเตอร์ที่แสดงผลเป็นประเภท YourInterface
  6. เรียกใช้เมธอดที่คุณกำหนดไว้ในอินเทอร์เฟซ วางกับดักข้อยกเว้น DeadObjectException เสมอ ซึ่งจะแสดงขึ้นเมื่อการเชื่อมต่อขาด นอกจากนี้ ให้จับข้อยกเว้น SecurityException ซึ่งจะแสดงขึ้นเมื่อ 2 กระบวนการที่เกี่ยวข้องกับการเรียกเมธอด IPC มีการกําหนด AIDL ที่ขัดแย้งกัน
  7. หากต้องการยกเลิกการเชื่อมต่อ ให้เรียกใช้ Context.unbindService() พร้อมอินสแตนซ์ของอินเทอร์เฟซ

โปรดคำนึงถึงประเด็นต่อไปนี้เมื่อเรียกใช้บริการ IPC

  • ระบบจะนับการอ้างอิงออบเจ็กต์ในกระบวนการต่างๆ
  • คุณส่งออบเจ็กต์นิรนามเป็นอาร์กิวเมนต์ของเมธอดได้

อ่านข้อมูลเพิ่มเติมเกี่ยวกับการเชื่อมโยงกับบริการได้ที่ภาพรวมบริการที่เชื่อมโยง

ต่อไปนี้คือตัวอย่างโค้ดที่แสดงการเรียกบริการที่สร้างโดย AIDL ซึ่งนำมาจากตัวอย่างบริการระยะไกลในโปรเจ็กต์ ApiDemos

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case, the service crashes before we can
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT
            ).show()
        }
    }

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private val secondaryConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service)
            killButton.isEnabled = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            secondaryService = null
            killButton.isEnabled = false
        }
    }

    private val mBindListener = View.OnClickListener {
        // Establish a couple connections with the service, binding
        // by interface names. This lets other applications be
        // installed that replace the remote service by implementing
        // the same interface.
        val intent = Intent(this@Binding, RemoteService::class.java)
        intent.action = IRemoteService::class.java.name
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        intent.action = ISecondary::class.java.name
        bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE)
        isBound = true
        callbackText.text = "Binding."
    }

    private val unbindListener = View.OnClickListener {
        if (isBound) {
            // If we have received the service, and hence registered with
            // it, then now is the time to unregister.
            try {
                mService?.unregisterCallback(mCallback)
            } catch (e: RemoteException) {
                // There is nothing special we need to do if the service
                // crashes.
            }

            // Detach our existing connection.
            unbindService(mConnection)
            unbindService(secondaryConnection)
            killButton.isEnabled = false
            isBound = false
            callbackText.text = "Unbinding."
        }
    }

    private val killListener = View.OnClickListener {
        // To kill the process hosting the service, we need to know its
        // PID.  Conveniently, the service has a call that returns
        // that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API lets us request to
                // kill any process based on its PID, the kernel
                // still imposes standard restrictions on which PIDs you
                // can actually kill. Typically this means only
                // the process running your application and any additional
                // processes created by that app, as shown here. Packages
                // sharing a common UID are also able to kill each
                // other's processes.
                Process.killProcess(pid)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // Recover gracefully from the process hosting the
            // server dying.
            // For purposes of this sample, put up a notification.
            Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show()
        }
    }

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private val mCallback = object : IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        override fun valueChanged(value: Int) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0))
        }
    }

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button taps.
        var button: Button = findViewById(R.id.bind)
        button.setOnClickListener(mBindListener)
        button = findViewById(R.id.unbind)
        button.setOnClickListener(unbindListener)
        killButton = findViewById(R.id.kill)
        killButton.setOnClickListener(killListener)
        killButton.isEnabled = false

        callbackText = findViewById(R.id.callback)
        callbackText.text = "Not attached."
        handler = InternalHandler(callbackText)
    }

    private class InternalHandler(
            textView: TextView,
            private val weakTextView: WeakReference<TextView> = WeakReference(textView)
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}"
                else -> super.handleMessage(msg)
            }
        }
    }
}

Java

public static class Binding extends Activity {
    /** The primary interface we are calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary secondaryService = null;

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

    /**
     * Standard initialization of this activity. Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button taps.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(unbindListener);
        killButton = (Button)findViewById(R.id.kill);
        killButton.setOnClickListener(killListener);
        killButton.setEnabled(false);

        callbackText = (TextView)findViewById(R.id.callback);
        callbackText.setText("Not attached.");
        handler = new InternalHandler(callbackText);
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            killButton.setEnabled(true);
            callbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service crashes before we can even
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null;
            killButton.setEnabled(false);
            callbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection secondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service);
            killButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            secondaryService = null;
            killButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names. This lets other applications be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE);
            isBound = true;
            callbackText.setText("Binding.");
        }
    };

    private OnClickListener unbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (isBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // crashes.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(secondaryConnection);
                killButton.setEnabled(false);
                isBound = false;
                callbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener killListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently, our service has a call that returns
            // that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API lets us request to
                    // kill any process based on its PID, the kernel
                    // still imposes standard restrictions on which PIDs you
                    // can actually kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here. Packages
                    // sharing a common UID are also able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    callbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // For purposes of this sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private static class InternalHandler extends Handler {
        private final WeakReference<TextView> weakTextView;

        InternalHandler(TextView textView) {
            weakTextView = new WeakReference<>(textView);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    TextView textView = weakTextView.get();
                    if (textView != null) {
                        textView.setText("Received from service: " + msg.arg1);
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}