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

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

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

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

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

  • การเรียกใช้จากกระบวนการภายในจะทำงานในเทรดเดียวกับที่เรียกใช้ ถ้า นี่คือเทรด UI หลัก โดยเทรดดังกล่าวจะยังทํางานต่อไปในอินเทอร์เฟซ AIDL หากใช่ เทรดอื่น ซึ่งก็คือเทรดที่เรียกใช้โค้ดของคุณในบริการ ดังนั้น หากเลือกเฉพาะ เทรดกำลังเข้าถึงบริการ คุณควบคุมได้เต็มที่ว่าจะเรียกใช้เทรดในชุดข้อความใด แต่ หากเป็นเช่นนั้น อย่าใช้ AIDL เลย ให้สร้าง อินเทอร์เฟซโดยการนำ Binder
  • การเรียกใช้จากกระบวนการระยะไกลจะส่งมาจาก Thread Pool ที่แพลตฟอร์มเก็บไว้ กระบวนการของคุณเอง เตรียมพร้อมรับสายเรียกเข้าจากชุดข้อความที่ไม่รู้จักด้วยการโทรหลายครั้ง ที่เกิดขึ้นในเวลาเดียวกัน กล่าวคือ การใช้งานอินเทอร์เฟซ AIDL ต้องเป็น แบบ Thread-safe อย่างแท้จริง การเรียกใช้จากเทรดเดียวบนออบเจ็กต์ระยะไกลเดียวกัน มาถึงตามลำดับทางฝั่งผู้รับ
  • คีย์เวิร์ด 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 ไปยังแอปพลิเคชันอื่นๆ เพื่อให้ผู้ใช้สามารถเข้าถึงอินเทอร์เฟซของบริการของคุณได้ คุณจะต้องรักษาการสนับสนุนสำหรับ ของ Google

สร้างไฟล์ .aidl

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

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

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

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

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

  • Map

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

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

ข้อควรทราบขณะกำหนดอินเทอร์เฟซบริการมีดังนี้

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

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

    ข้อควรระวัง: จํากัดทิศทางที่ถูกต้องตามความเป็นจริง เนื่องจากพารามิเตอร์มาร์แชลล์มีราคาแพง

  • ความคิดเห็นเกี่ยวกับโค้ดทั้งหมดในไฟล์ .aidl จะรวมอยู่ใน สร้างรายได้ IBinder อินเทอร์เฟซ ยกเว้นความคิดเห็นก่อนนำเข้าและแพ็กเกจ ข้อความ
  • คุณจะกำหนดค่าสตริงและค่าคงที่ก็ได้ในอินเทอร์เฟซ AIDL เช่น const int VERSION = 1;
  • การเรียกเมธอดจะส่งโดย transact() ซึ่งโดยปกติจะอ้างอิงตามดัชนีเมธอดในอินเทอร์เฟซ เพราะว่า ทำให้การกำหนดเวอร์ชันเป็นเรื่องยาก กำหนดรหัสธุรกรรมด้วยตนเองให้กับเมธอด: void method() = 10;
  • อาร์กิวเมนต์ Nullable และประเภทผลลัพธ์ต้องมีคำอธิบายประกอบโดยใช้ @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 นอกจากนี้ กำหนดเมธอดตัวช่วยบางอย่าง โดยเฉพาะ asInterface() ซึ่งใช้ IBinder ซึ่งโดยปกติแล้ววิธีการนี้ส่งต่อไปยังเมธอด Callback onServiceConnected() ของไคลเอ็นต์ และ จะแสดงผลอินสแตนซ์ของอินเทอร์เฟซ Stub สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีแคสต์รายการนี้ โปรดดูส่วนการโทร 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 สำหรับบริการ ในขั้นตอนถัดไป อินสแตนซ์นี้จะแสดง ลูกค้าเพื่อให้โต้ตอบกับบริการได้

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

  • เราไม่รับประกันว่าสายเรียกเข้าจะทำงานในเทรดหลัก คุณจึงต้องคิดให้ดี เกี่ยวกับการทำหลายชุดข้อความตั้งแต่เริ่มต้น และสร้างบริการของคุณอย่างเหมาะสมเพื่อรักษาความปลอดภัยของชุดข้อความ
  • โดยค่าเริ่มต้น การเรียก IPC จะเป็นแบบพร้อมกัน ถ้าคุณทราบว่าบริการใช้เวลามากกว่า มิลลิวินาทีในการดำเนินการตามคำขอให้เสร็จสมบูรณ์ อย่าเรียกใช้คำขอจากเทรดหลักของกิจกรรม แอปอาจค้างและทำให้ 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() ของลูกค้าจะได้รับ onBind() ของบริการส่งคืนอินสแตนซ์ binder รายการ

ไคลเอ็นต์ต้องมีสิทธิ์เข้าถึงคลาสอินเทอร์เฟซด้วย ดังนั้นหากลูกค้าและบริการ แอปพลิเคชันแยกต่างหาก แอปพลิเคชันของไคลเอ็นต์ต้องมีสำเนาของไฟล์ .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 และไฟล์พาร์เซลอื่นๆ ก็เช่นกัน ที่นี่ วิธีนี้จะทำให้ไม่ต้องเขียนโค้ด Marshalling ด้วยตนเองและตั้ง อย่างไรก็ตาม การทำเช่นนี้จะเป็นการสร้างโครงสร้างเปล่าๆ ถ้าตัวเข้าถึงที่กำหนดเองหรือฟังก์ชันอื่นๆ ที่ต้องการ ให้ใช้ 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 ที่คาดว่าจะมี parcelables ให้ตรวจดูว่าคุณได้ตั้งค่า classloader ของ Bundle แล้ว กำลังโทรหา Bundle.setClassLoader(ClassLoader) ก่อนที่จะพยายามอ่าน จาก Bundle ไม่เช่นนั้น คุณจะเจอ ClassNotFoundException แม้ว่าการระบุพัสดุในใบสมัครของคุณไว้อย่างถูกต้องแล้วก็ตาม

เช่น ลองดูตัวอย่างไฟล์ .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 จากตัวอย่าง Remote Service ในโปรเจ็กต์ 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);
            }
        }
    }
}