Ngôn ngữ định nghĩa giao diện Android (AIDL) tương tự như ngôn ngữ khác IDL: nó cho phép bạn xác định giao diện lập trình mà cả hai khách hàng và dịch vụ thoả thuận với nhau để liên lạc với nhau thông qua giao tiếp liên quy trình (IPC).
Trên Android, một quy trình thường không thể truy cập vào bộ nhớ của một tiến trình khác. Để trò chuyện, chúng cần phân huỷ đối tượng của mình thành các dữ liệu gốc mà hệ điều hành có thể hiểu và sắp xếp các đối tượng trên ranh giới đó cho bạn. Mã để việc tập hợp các trang tổng hợp có vẻ tẻ nhạt, nên Android sẽ xử lý việc này cho bạn bằng AIDL.
Lưu ý: AIDL chỉ cần thiết nếu bạn cho phép khách hàng
nhiều ứng dụng truy cập dịch vụ của bạn cho IPC và bạn muốn xử lý đa luồng trong
. Nếu bạn không cần thực hiện IPC đồng thời trên
tạo giao diện bằng cách triển khai một
Binder
.
Nếu bạn muốn thực hiện IPC nhưng không cần xử lý đa luồng,
triển khai giao diện của bạn bằng Messenger
.
Dù vậy, hãy đảm bảo rằng bạn hiểu rõ về dịch vụ ràng buộc trước khi
triển khai AIDL.
Trước khi bắt đầu thiết kế giao diện AIDL, hãy lưu ý rằng các lệnh gọi đến giao diện AIDL các lệnh gọi hàm trực tiếp. Không đưa ra giả định về luồng mà lệnh gọi xảy ra. Điều gì xảy ra sẽ khác nhau tuỳ thuộc vào việc lệnh gọi có phải từ một luồng trong quy trình cục bộ hoặc quy trình từ xa:
- Các lệnh gọi được thực hiện từ quy trình cục bộ sẽ thực thi trong cùng một luồng đang thực hiện lệnh gọi. Nếu
đây là luồng giao diện người dùng chính của bạn, luồng đó sẽ tiếp tục thực thi trong giao diện AIDL. Nếu có
một luồng khác, đó là luồng thực thi mã của bạn trong dịch vụ. Do đó, nếu chỉ địa phương
luồng nào đang truy cập dịch vụ, thì bạn hoàn toàn có thể kiểm soát luồng nào đang thực thi trong dịch vụ đó. Nhưng
nếu vậy thì đừng sử dụng AIDL; thay vào đó, hãy tạo
bằng cách triển khai một
Binder
. - Các lệnh gọi trong một quy trình từ xa được gửi từ một nhóm luồng mà nền tảng duy trì bên trong quy trình của riêng bạn. Chuẩn bị cho cuộc gọi đến từ chuỗi không xác định, có nhiều cuộc gọi đang xảy ra cùng lúc. Nói cách khác, việc triển khai giao diện AIDL phải hoàn toàn an toàn cho luồng. Lệnh gọi được thực hiện từ một luồng trên cùng một đối tượng từ xa đến theo thứ tự trên đầu người nhận.
- Từ khoá
oneway
sửa đổi hành vi của lệnh gọi từ xa. Khi được sử dụng, lệnh gọi từ xa sẽ không chặn. Lệnh này sẽ gửi dữ liệu giao dịch và trả về ngay lập tức. Việc triển khai giao diện sẽ nhận được lệnh gọi này dưới dạng một lệnh gọi thông thường từ nhóm luồngBinder
dưới dạng một lệnh gọi từ xa thông thường. Nếu bạn dùngoneway
với một cuộc gọi cục bộ, không có tác động gì và lệnh gọi này vẫn đồng bộ.
Xác định giao diện AIDL
Xác định giao diện AIDL trong tệp .aidl
bằng Java
cú pháp ngôn ngữ lập trình, sau đó lưu vào mã nguồn, trong thư mục src/
của cả hai
ứng dụng lưu trữ dịch vụ và bất kỳ ứng dụng nào khác liên kết với dịch vụ.
Khi bạn tạo từng ứng dụng chứa tệp .aidl
, bộ công cụ SDK Android
tạo giao diện IBinder
dựa trên tệp .aidl
và lưu tệp đó vào
thư mục gen/
của dự án. Dịch vụ phải triển khai IBinder
khi thích hợp. Sau đó, các ứng dụng khách có thể liên kết với dịch vụ và phương thức gọi từ
IBinder
để thực hiện IPC.
Để tạo một dịch vụ bị ràng buộc bằng AIDL, hãy làm theo các bước được mô tả dưới đây trong các phần sau:
- Tạo tệp
.aidl
Tệp này xác định giao diện lập trình có chữ ký phương thức.
- Triển khai giao diện
Bộ công cụ SDK Android tạo giao diện bằng ngôn ngữ lập trình Java dựa trên Tệp
.aidl
. Giao diện này có một lớp trừu tượng bên trong tên làStub
, lớp này mở rộngBinder
và triển khai các phương thức từ giao diện AIDL. Bạn phải mở rộng LớpStub
và triển khai các phương thức. - Hiển thị giao diện cho ứng dụng
Triển khai
Service
và ghi đèonBind()
để trả về phương thức triển khaiStub
.
Thận trọng: Mọi thay đổi mà bạn thực hiện đối với giao diện AIDL sau
bản phát hành đầu tiên của bạn phải duy trì khả năng tương thích ngược để tránh làm hỏng các ứng dụng khác
sử dụng dịch vụ của bạn. Nguyên nhân là do tệp .aidl
của bạn phải được sao chép sang các ứng dụng khác
để chúng có thể truy cập vào giao diện dịch vụ của bạn, bạn phải tiếp tục hỗ trợ
.
Tạo tệp .aidl
AIDL sử dụng cú pháp đơn giản cho phép bạn khai báo giao diện bằng một hoặc nhiều phương thức có thể lấy tham số và trả về giá trị. Tham số và giá trị trả về có thể thuộc bất kỳ loại nào, kể cả các loại khác Giao diện do AIDL tạo.
Bạn phải tạo tệp .aidl
bằng ngôn ngữ lập trình Java. Mỗi .aidl
tệp phải xác định một giao diện duy nhất và chỉ yêu cầu khai báo giao diện và phương thức
chữ ký.
Theo mặc định, AIDL hỗ trợ các loại dữ liệu sau:
- Tất cả loại nguyên hàm trong ngôn ngữ lập trình Java (chẳng hạn như
int
,long
,char
,boolean
, v.v.) - Mảng có kiểu nguyên gốc, chẳng hạn như
int[]
String
CharSequence
List
Tất cả các phần tử trong
List
phải là một trong những kiểu dữ liệu được hỗ trợ trong thuộc tính này hoặc một trong các giao diện hoặc gói bưu kiện khác do AIDL tạo mà bạn khai báo. Đáp Bạn có thể tuỳ ý sử dụngList
làm lớp loại có tham số, chẳng hạn nhưList<String>
Lớp cụ thể thực tế mà bên kia nhận được luôn làArrayList
, mặc dù được tạo để sử dụng giao diệnList
.Map
Tất cả các phần tử trong
Map
phải là một trong những kiểu dữ liệu được hỗ trợ trong thuộc tính này hoặc một trong các giao diện hoặc gói bưu kiện khác do AIDL tạo mà bạn khai báo. Bản đồ loại có tham số, chẳng hạn như các kết quả của biểu mẫuMap<String,Integer>
, không được hỗ trợ. Lớp bê tông thực tế mà mặt kia nhận luôn làHashMap
, mặc dù phương thức này được tạo để sử dụng giao diệnMap
. Cân nhắc sử dụngBundle
để thay thế choMap
.
Bạn phải bao gồm câu lệnh import
cho mỗi loại bổ sung chưa được liệt kê trước đó,
ngay cả khi chúng được xác định trong cùng một gói với giao diện của bạn.
Khi xác định giao diện dịch vụ, hãy lưu ý rằng:
- Các phương thức có thể không nhận hoặc có nhiều tham số và có thể trả về một giá trị hoặc giá trị trống.
- Tất cả tham số không ban đầu đều cần có một thẻ định hướng cho biết dữ liệu đi theo hướng nào:
in
,out
hoặcinout
(xem ví dụ bên dưới).Nguồn gốc,
String
,IBinder
và AIDL tạo giao diện làin
theo mặc định và không thể là giao diện khác.Thận trọng: Giới hạn chỉ hướng đến những gì thực sự là vì việc tổng hợp các tham số sẽ rất tốn kém.
- Tất cả nhận xét về mã trong tệp
.aidl
đều có trong đã tạoIBinder
giao diện ngoại trừ các nhận xét trước khi nhập và đóng gói tuyên bố. - Hằng số chuỗi và số nguyên có thể được xác định trong giao diện AIDL, chẳng hạn như
const int VERSION = 1;
. - Các lệnh gọi phương thức được gửi bởi
transact()
, thường dựa trên chỉ mục phương thức trong giao diện. Bởi vì điều này gây khó khăn cho việc lập phiên bản, bạn có thể chỉ định mã giao dịch cho một phương thức theo cách thủ công:void method() = 10;
. - Bạn phải chú giải các đối số có thể rỗng và loại dữ liệu trả về bằng
@nullable
.
Dưới đây là tệp .aidl
mẫu:
// 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); }
Lưu tệp .aidl
vào thư mục src/
của dự án. Khi
tạo ứng dụng của bạn, công cụ SDK sẽ tạo tệp giao diện IBinder
trong
thư mục gen/
của dự án. Tên của tệp đã tạo khớp với tên của tệp .aidl
, nhưng
có đuôi .java
. Ví dụ: IRemoteService.aidl
dẫn đến IRemoteService.java
.
Nếu bạn sử dụng Android Studio, bản dựng tăng dần sẽ tạo lớp liên kết gần như ngay lập tức.
Nếu bạn không sử dụng Android Studio, công cụ Gradle sẽ tạo lớp liên kết vào lần tiếp theo bạn
tạo ứng dụng của bạn. Tạo dự án bằng gradle assembleDebug
hoặc gradle assembleRelease
ngay khi bạn ghi xong tệp .aidl
,
để mã của bạn có thể liên kết với lớp đã tạo.
Triển khai giao diện
Khi bạn xây dựng ứng dụng, bộ công cụ SDK Android sẽ tạo một tệp giao diện .java
được đặt tên theo tệp .aidl
của bạn. Giao diện được tạo bao gồm một lớp con có tên là Stub
là phương thức triển khai trừu tượng của giao diện mẹ (chẳng hạn như YourInterface.Stub
) và khai báo tất cả phương thức qua tệp .aidl
.
Lưu ý: Stub
cũng
xác định một vài phương thức trợ giúp, đáng chú ý nhất là asInterface()
, lấy IBinder
, thường là phương thức được truyền đến phương thức gọi lại onServiceConnected()
của ứng dụng và
sẽ trả về một thực thể của giao diện mã giả lập. Để biết thêm thông tin về cách truyền nội dung này, hãy xem phần Gọi IPC
.
Để triển khai giao diện được tạo từ .aidl
, hãy mở rộng Binder
đã tạo
giao diện (chẳng hạn như YourInterface.Stub
) rồi triển khai các phương thức
kế thừa từ tệp .aidl
.
Dưới đây là ví dụ về cách triển khai giao diện có tên là IRemoteService
, được xác định theo hàm trước
Ví dụ về IRemoteService.aidl
sử dụng một thực thể ẩn danh:
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
hiện là một thực thể của lớp Stub
(Binder
),
mã này xác định giao diện IPC cho dịch vụ. Trong bước tiếp theo, thực thể này sẽ hiển thị với
khách hàng để họ có thể tương tác với dịch vụ.
Hãy lưu ý một số quy tắc khi triển khai giao diện AIDL:
- Tuy nhiên, bạn cần lưu ý rằng các cuộc gọi đến không được đảm bảo sẽ thực thi trên luồng chính về tính năng đa luồng ngay từ đầu và xây dựng đúng cách dịch vụ của bạn để đảm bảo an toàn cho luồng.
- Theo mặc định, các lệnh gọi IPC có tính đồng bộ. Nếu bạn biết rằng dịch vụ sẽ mất hơn một vài mili giây để hoàn tất yêu cầu, đừng gọi từ luồng chính của hoạt động. Thao tác này có thể làm treo ứng dụng, khiến Android hiển thị thông báo "Ứng dụng không phản hồi" . Gọi từ một luồng riêng trong ứng dụng.
- Chỉ những loại ngoại lệ được liệt kê trong tài liệu tham khảo cho
Parcel.writeException()
được gửi lại cho phương thức gọi.
Hiển thị giao diện cho ứng dụng
Sau khi triển khai giao diện cho dịch vụ, bạn cần hiển thị giao diện đó cho
để họ có thể liên kết với ứng dụng đó. Để hiển thị giao diện
cho dịch vụ của bạn, hãy mở rộng Service
và triển khai onBind()
để trả về một thực thể lớp giúp triển khai
Stub
được tạo, như đã thảo luận trong phần trước. Sau đây là ví dụ
dịch vụ hiển thị giao diện mẫu IRemoteService
cho các ứng dụng.
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. } }; }
Bây giờ, khi một ứng dụng (chẳng hạn như một hoạt động) gọi bindService()
để kết nối với dịch vụ này, lệnh gọi lại onServiceConnected()
của ứng dụng sẽ nhận được
Thực thể binder
do onBind()
của dịch vụ trả về
.
Ứng dụng cũng phải có quyền truy cập vào lớp giao diện. Vì vậy, nếu khách hàng và dịch vụ đang ở
các ứng dụng riêng biệt, thì ứng dụng của ứng dụng phải có một bản sao của tệp .aidl
trong thư mục src/
. Thư mục này sẽ tạo android.os.Binder
giao diện, cung cấp cho máy khách quyền truy cập vào các phương thức AIDL.
Khi ứng dụng nhận được IBinder
trong lệnh gọi lại onServiceConnected()
, nó phải gọi
YourServiceInterface.Stub.asInterface(service)
để truyền dữ liệu được trả về
tham số thành loại 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; } };
Để biết thêm mã mẫu, hãy xem
Lớp RemoteService.java
trong
API Apidemo.
Truyền đối tượng qua IPC
Trong Android 10 (API cấp 29 trở lên), bạn có thể xác định
Parcelable
đối tượng ngay trong
AIDL. Các loại được hỗ trợ làm đối số giao diện AIDL và các Parcelable khác cũng được
được hỗ trợ tại đây. Điều này giúp tránh việc phải viết mã dồn theo cách thủ công và mã tuỳ chỉnh
. Tuy nhiên, thao tác này cũng sẽ tạo ra một cấu trúc trần. Nếu trình truy cập tuỳ chỉnh hoặc chức năng khác
mong muốn, hãy triển khai 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; }
Mã mẫu ở trên tự động tạo một lớp Java có các trường số nguyên left
,
top
, right
và bottom
. Tất cả mã dồn có liên quan
được triển khai tự động và đối tượng có thể được sử dụng trực tiếp mà không phải thêm bất kỳ
trong quá trình triển khai.
Bạn cũng có thể gửi một lớp tuỳ chỉnh từ quy trình này sang quy trình khác thông qua giao diện IPC. Tuy nhiên,
đảm bảo mã cho lớp của bạn có sẵn ở phía bên kia của kênh IPC và
lớp của bạn phải hỗ trợ giao diện Parcelable
. Hỗ trợ
Parcelable
quan trọng
vì nó cho phép hệ thống Android phân tách các đối tượng thành các dữ liệu gốc có thể tổng hợp
trong nhiều quy trình.
Để tạo một lớp tuỳ chỉnh hỗ trợ Parcelable
, hãy thực hiện
sau:
- Yêu cầu lớp triển khai giao diện
Parcelable
. - Triển khai
writeToParcel
, thao tác này sẽ đưa trạng thái hiện tại của đối tượng và ghi vàoParcel
. - Thêm một trường tĩnh có tên là
CREATOR
vào lớp (lớp này là đối tượng triển khai) giao diệnParcelable.Creator
. - Cuối cùng, hãy tạo một tệp
.aidl
khai báo lớp theo gói, như minh hoạ sauRect.aidl
.Nếu bạn đang sử dụng quy trình xây dựng tuỳ chỉnh, vui lòng không thêm tệp
.aidl
vào bản dựng. Tương tự như tệp tiêu đề bằng ngôn ngữ C, tệp.aidl
này không được biên dịch.
AIDL sử dụng các phương thức và trường này trong mã mà nó tạo ra để Marshall và Unmarshall các đối tượng của bạn.
Ví dụ: đây là tệp Rect.aidl
để tạo một lớp Rect
theo gói:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
Sau đây là ví dụ về cách lớp Rect
triển khai
Giao thức 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; } }
Quá trình tổng hợp trong lớp Rect
rất đơn giản. Hãy xem xét khác
trên Parcel
để xem các loại giá trị khác mà bạn có thể viết
thành Parcel
.
Cảnh báo: Hãy nhớ các hệ quả bảo mật của việc nhận
dữ liệu từ các quá trình khác. Trong trường hợp này, Rect
sẽ đọc 4 số từ Parcel
, nhưng bạn phải đảm bảo những số này nằm trong phạm vi chấp nhận được
cho bất cứ điều gì mà phương thức gọi đang cố gắng thực hiện. Để biết thêm thông tin về cách bảo vệ ứng dụng của bạn khỏi phần mềm độc hại, hãy xem Mẹo bảo mật.
Các phương thức có đối số Gói chứa Parcelables
Nếu một phương thức chấp nhận đối tượngBundle
dự kiến có chứa
các bưu kiện, hãy đảm bảo rằng bạn đã đặt trình tải lớp của Bundle
bằng cách
đang gọi Bundle.setClassLoader(ClassLoader)
trước khi cố gắng đọc
khỏi Bundle
. Nếu không, bạn sẽ gặp phải ClassNotFoundException
mặc dù gói bưu kiện đã được xác định chính xác trong ứng dụng.
Ví dụ: hãy xem xét tệp .aidl
mẫu sau:
// 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
được
được đặt rõ ràng trong Bundle
trước khi đọc 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. } };
Gọi một phương thức IPC
Để gọi giao diện từ xa được xác định bằng AIDL, hãy thực hiện các bước sau trong lớp gọi của bạn:
- Đưa tệp
.aidl
vào thư mụcsrc/
của dự án. - Khai báo một thực thể của giao diện
IBinder
được tạo dựa trên AIDL. - Triển khai
ServiceConnection
. - Gọi
Context.bindService()
, chuyển dữ liệu triển khaiServiceConnection
. - Trong quá trình triển khai
onServiceConnected()
, bạn sẽ nhận được mộtIBinder
được gọi làservice
. Gọi điệnYourInterfaceName.Stub.asInterface((IBinder)service)
thành truyền tham số trả về tới loạiYourInterface
. - Gọi các phương thức mà bạn đã xác định trên giao diện. Luôn đặt bẫy
DeadObjectException
ngoại lệ, được gửi khi kết nối bị ngắt. Ngoài ra, các ngoại lệSecurityException
bẫy, được gửi khi 2 quy trình liên quan đến lệnh gọi phương thức IPC có các định nghĩa AIDL xung đột. - Để ngắt kết nối, hãy gọi
Context.unbindService()
bằng thực thể giao diện của bạn.
Hãy lưu ý những điểm sau khi gọi dịch vụ IPC:
- Các đối tượng được tính trong các quy trình.
- Bạn có thể gửi đồ vật ẩn danh làm đối số phương thức.
Để biết thêm thông tin về việc liên kết với một dịch vụ, hãy đọc bài viết Tổng quan về dịch vụ ràng buộc.
Dưới đây là một số mã mẫu minh hoạ việc gọi một dịch vụ do AIDL tạo, được lấy từ mẫu Dịch vụ từ xa trong dự án 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); } } } }