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 ให้ทำตามขั้นตอนต่อไปนี้ ซึ่งอธิบายไว้ ในหัวข้อต่อไปนี้
- สร้างไฟล์
.aidl
ไฟล์นี้ระบุอินเทอร์เฟซการเขียนโปรแกรมด้วยลายเซ็นเมธอด
- ใช้อินเทอร์เฟซ
เครื่องมือ Android SDK จะสร้างอินเทอร์เฟซในภาษาโปรแกรม Java ตาม
.aidl
ไฟล์ อินเทอร์เฟซนี้มีคลาสนามธรรมภายในชื่อStub
ที่ขยายBinder
และติดตั้งใช้งานเมธอดจากอินเทอร์เฟซ AIDL คุณต้องขยายStub
และนำเมธอดไปใช้ - แสดงอินเทอร์เฟซแก่ลูกค้า
ใช้
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
โปรดดำเนินการต่อไปนี้
ดังต่อไปนี้:
- ให้ชั้นเรียนใช้อินเทอร์เฟซ
Parcelable
- ใช้งาน
writeToParcel
ซึ่งจะนำ สถานะปัจจุบันของออบเจ็กต์และเขียนลงในParcel
- เพิ่มช่องแบบคงที่ชื่อ
CREATOR
ลงในคลาสซึ่งเป็นออบเจ็กต์ที่กำลังใช้งานอยู่ อินเทอร์เฟซของParcelable.Creator
- สุดท้าย ให้สร้างไฟล์
.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 ให้ทำตามขั้นตอนต่อไปนี้ใน ชั้นเรียนการโทรของคุณ:
- รวมไฟล์
.aidl
ไว้ในไดเรกทอรีโครงการsrc/
- ประกาศอินสแตนซ์ของอินเทอร์เฟซ
IBinder
ซึ่งสร้างขึ้นตาม AIDL - นำ
ServiceConnection
มาใช้ - โทร
Context.bindService()
การส่งผ่านในการใช้งานServiceConnection
- ในการใช้งาน
onServiceConnected()
คุณได้รับIBinder
ชื่อservice
โทรYourInterfaceName.Stub.asInterface((IBinder)service)
ถึง แคสต์พารามิเตอร์ที่แสดงผลไปยังประเภทYourInterface
- เรียกใช้เมธอดที่คุณกำหนดในอินเทอร์เฟซ ดักเสมอ
ข้อยกเว้น
DeadObjectException
รายการ ซึ่งจะมีการส่งเมื่อ การเชื่อมต่อก็ใช้ไม่ได้ นอกจากนี้ ยังดักจับข้อยกเว้นSecurityException
รายการที่เกิดขึ้นเมื่อกระบวนการ 2 ส่วนที่เกี่ยวข้องในการเรียกเมธอด IPC มีคำนิยามของ AIDL ที่ขัดแย้งกัน - หากต้องการยกเลิกการเชื่อมต่อ ให้เรียกใช้
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—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—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); } } } }