Ngôn ngữ định nghĩa giao diện Android (AIDL)

Ngôn ngữ định nghĩa giao diện Android (AIDL) tương tự như các IDL khác: cho phép bạn xác định giao diện lập trình mà cả ứng dụng và dịch vụ tuân theo để giao tiếp với nhau bằng giao tiếp liên quy trình (IPC).

Trên Android, một tiến trình thường không thể truy cập vào bộ nhớ của một quy trình khác. Để nói chuyện, các lớp này cần phân tách các đối tượng thành các nguyên 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. Việc viết mã sẽ rất tẻ nhạt, vì vậy 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 ứng dụng của nhiều ứng dụng truy cập vào dịch vụ của bạn cho IPC và bạn muốn xử lý hoạt động đa luồng trong dịch vụ của mình. Nếu bạn không cần thực hiện đồng thời IPC trên nhiều ứng dụng, hãy 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, hãy triển khai giao diện bằng một Messenger. Dù vậy, hãy đảm bảo bạn hiểu rõ các 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 là các lệnh gọi hàm trực tiếp. Đừng giả định về luồng mà lệnh gọi xảy ra. Các lệnh gọi sẽ khác nhau tuỳ thuộc vào việc lệnh gọi là từ một luồng trong quy trình cục bộ hay từ quy trình từ xa:

  • Các lệnh gọi được tạo 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, thì luồng đó sẽ tiếp tục thực thi trong giao diện AIDL. Nếu đó là một luồng khác, thì đó là luồng thực thi mã của bạn trong dịch vụ. Do đó, nếu chỉ các luồng cục bộ truy cập vào 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 trong trường hợp đó, đừng sử dụng AIDL; thay vào đó, hãy tạo giao diện bằng cách triển khai Binder.
  • Các lệnh gọi từ một quy trình từ xa sẽ được gửi đ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 các cuộc gọi đến từ chuỗi không xác định với nhiều cuộc gọi diễn 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. Các 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 sẽ đến theo thứ tự ở phía nhận.
  • Từ khoá oneway sửa đổi hành vi của các lệnh gọi từ xa. Khi sử dụng thuộc tính này, lệnh gọi từ xa sẽ không chặn. Chrome sẽ gửi dữ liệu giao dịch và trả về ngay lập tức. Cuối cùng, quá trình triển khai giao diện sẽ nhận được lệnh gọi này dưới dạng lệnh gọi thông thường từ nhóm luồng Binder dưới dạng lệnh gọi từ xa thông thường. Nếu oneway được dùng với một lệnh gọi cục bộ thì sẽ không ảnh hưởng gì và lệnh gọi vẫn đồng bộ.

Xác định giao diện AIDL

Xác định giao diện AIDL trong tệp .aidl bằng cú pháp ngôn ngữ lập trình Java, sau đó lưu vào mã nguồn, trong thư mục src/, của cả ứng dụng lưu trữ dịch vụ và mọi ứng dụng khác liên kết với dịch vụ.

Khi bạn xây dựng từng ứng dụng chứa tệp .aidl, bộ công cụ SDK Android sẽ tạo một 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 giao diện IBinder sao cho phù hợp. Sau đó, các ứng dụng 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ả trong các phần tiếp theo:

  1. Tạo tệp .aidl

    Tệp này xác định giao diện lập trình bằng các chữ ký phương thức.

  2. Triển khai giao diện

    Bộ công cụ SDK Android tạo một giao diện bằng ngôn ngữ lập trình Java dựa trên tệp .aidl của bạn. 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ộng Binder và triển khai các phương thức từ giao diện AIDL. Bạn phải mở rộng lớp Stub và triển khai các phương thức.

  3. Cung cấp giao diện cho khách hàng

    Triển khai Service và ghi đè onBind() để trả về phương thức triển khai lớp Stub.

Thận trọng: Mọi thay đổi bạn thực hiện đối với giao diện AIDL sau bản phát hành đầu tiên vẫ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 dùng dịch vụ của bạn. Lý do là vì bạn phải sao chép tệp .aidl sang các ứng dụng khác để chúng có thể truy cập vào giao diện của dịch vụ, nên bạn phải duy trì dịch vụ hỗ trợ cho giao diện gốc.

Tạo tệp .aidl

AIDL sử dụng một cú pháp đơn giản cho phép bạn khai báo giao diện có một hoặc nhiều phương thức có thể lấy tham số và trả về giá trị. Các tham số và giá trị trả về có thể thuộc bất kỳ loại nào, kể cả các giao diện khác 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 tệp .aidl phải xác định một giao diện duy nhất và chỉ yêu cầu phần khai báo giao diện cũng như chữ ký phương thức.

Theo mặc định, AIDL hỗ trợ các loại dữ liệu sau:

  • Tất cả cá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ó các loại nguyên hàm, chẳng hạn như int[]
  • String
  • CharSequence
  • List

    Tất cả phần tử trong List phải là một trong những loại dữ liệu được hỗ trợ trong danh sách này hoặc một trong những giao diện/gói khác do AIDL tạo mà bạn khai báo. Bạn có thể tuỳ ý sử dụng List làm lớp kiểu tham số, chẳng hạn như List<String>. Lớp cụ thể thực tế mà phía bên kia nhận được luôn là ArrayList, mặc dù phương thức này được tạo để sử dụng giao diện List.

  • Map

    Tất cả phần tử trong Map phải là một trong những loại dữ liệu được hỗ trợ trong danh sách này hoặc một trong những giao diện/gói khác do AIDL tạo mà bạn khai báo. Các bản đồ kiểu tham số có tham số, chẳng hạn như các bản đồ có dạng Map<String,Integer>, không được hỗ trợ. Lớp cụ thể thực tế mà phía bên kia nhận được luôn là HashMap, mặc dù phương thức này được tạo để sử dụng giao diện Map. Hãy cân nhắc sử dụng Bundle thay vì Map.

Bạn phải đưa vào 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ể nhận hoặc không nhận nhiều tham số và có thể trả về một giá trị hoặc khoảng trống.
  • Tất cả các thông số không phải là tham số gốc đều phải có một thẻ định hướng cho biết cách dữ liệu được truyền: in, out hoặc inout (xem ví dụ bên dưới).

    Theo mặc định, giao diện gốc, String, IBinder và giao diện do AIDL tạo có giá trị in và không thể đặt giá trị này.

    Thận trọng: Giới hạn hướng ở những nội dung thực sự cần thiết, vì việc sắp xếp các tham số sẽ tốn kém.

  • Tất cả nhận xét về mã có trong tệp .aidl đều được đưa vào giao diện IBinder đã tạo, ngoại trừ những nhận xét trước các câu lệnh nhập và đóng gói.
  • Bạn có thể xác định hằng số chuỗi và int 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ằng một transact(). Mã này thường dựa trên chỉ mục phương thức trong giao diện. Vì việc 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: void method() = 10; theo cách thủ công.
  • Bạn phải chú giải các đối số có giá trị rỗng và kiểu 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 bạn tạo ứng dụng, bộ 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 tệp .aidl, nhưng có đuôi là .java. Ví dụ: IRemoteService.aidl cho kết quả là 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 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. Tạo dự án của bạ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. Giao diện được tạo bao gồm một lớp con có tên là Stub. Đây 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 từ 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(). Phương thức này sử dụng 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à trả về một thực thể của giao diện mã giả lập. Để biết thêm thông tin chi tiết về cách truyền nội dung này, hãy xem phần Gọi phương thức IPC.

Để triển khai giao diện được tạo từ .aidl, hãy mở rộng giao diện Binder đã tạo (chẳng hạn như YourInterface.Stub) và 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 IRemoteService, được xác định trong ví dụ IRemoteService.aidl trước đó bằng cách 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), xác định giao diện IPC cho dịch vụ. Ở bước tiếp theo, khách hàng sẽ nhìn thấy thực thể này để 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:

  • Các lệnh gọi đến không được đảm bảo sẽ thực thi trên luồng chính, vì vậy, bạn cần phải suy nghĩ về đa luồng ngay từ đầu và xây dựng dịch vụ đúng cách sao cho an toàn với luồng.
  • Theo mặc định, các lệnh gọi IPC có tính chất đồng bộ. Nếu bạn biết rằng dịch vụ mất hơn vài mili giây để hoàn tất một yêu cầu, đừng gọi dịch vụ đó từ luồng chính của hoạt động. Lỗi này có thể treo ứng dụng, dẫn đến việc Android hiển thị hộp thoại "Ứng dụng không phản hồi". Gọi lệnh này từ một luồng riêng trong ứng dụng.
  • Chỉ các loại ngoại lệ được liệt kê trong tài liệu tham khảo cho Parcel.writeException() mới được gửi lại cho phương thức gọi.

Hiển thị giao diện cho khách hàng

Sau khi triển khai giao diện cho dịch vụ, bạn cần hiển thị giao diện đó cho ứng dụng để họ có thể liên kết với giao diện. Để 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ể của lớp giúp triển khai Stub đã tạo, như đã thảo luận trong phần trước. Sau đây là một dịch vụ mẫu 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.
        }
    };
}

Giờ đây, 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 phương thức 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 ứng dụng và dịch vụ nằm trong các ứng dụng riêng biệt, thì ứng dụng phải có bản sao của tệp .aidl trong thư mục src/. Tệp này sẽ tạo giao diện android.os.Binder, cấp cho ứng dụng quyền truy cập vào các phương thức AIDL.

Khi nhận được IBinder trong lệnh gọi lại onServiceConnected(), máy khách phải gọi YourServiceInterface.Stub.asInterface(service) để truyền tham số được trả về 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 Apidemos.

Truyền các đối tượng qua IPC

Trong Android 10 (API cấp 29 trở lên), bạn có thể xác định các đối tượng Parcelable ngay trong AIDL. Các loại được hỗ trợ dưới dạng đối số giao diện AIDL và các gói khác cũng được hỗ trợ tại đây. Điều này giúp bạn tránh phải thực hiện thêm công việc để viết mã sắp xếp theo cách thủ công và một lớp tuỳ chỉnh. Tuy nhiên, thao tác này cũng tạo ra một cấu trúc thô. Nếu bạn muốn trình truy cập tuỳ chỉnh hoặc chức năng khác, 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 sẽ tự động tạo một lớp Java có các trường số nguyên left, top, rightbottom. Tất cả mã sắp xếp có liên quan sẽ được triển khai tự động và đối tượng có thể được sử dụng trực tiếp mà không cần triển khai thêm.

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, hãy đảm bảo mã cho lớp 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. Việc hỗ trợ Parcelable là rất 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 nguyên gốc có thể được sắp xếp trên các quy trình.

Để tạo một lớp tuỳ chỉnh hỗ trợ Parcelable, hãy làm như sau:

  1. Yêu cầu lớp của bạn triển khai giao diện Parcelable.
  2. Triển khai writeToParcel để lấy trạng thái hiện tại của đối tượng và ghi vào Parcel.
  3. Thêm trường tĩnh có tên là CREATOR vào lớp. Trường này là đối tượng triển khai giao diện Parcelable.Creator.
  4. Cuối cùng, hãy tạo một tệp .aidl khai báo lớp có thể đóng gói của bạn, như minh hoạ cho tệp Rect.aidl sau.

    Nếu bạn đang sử dụng quy trình xây dựng tuỳ chỉnh, đừng thêm tệp .aidl vào bản dựng. Tương tự như tệp tiêu đề ở 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à AIDL tạo ra để sắp xếp và tách biệt các đối tượng của bạn.

Ví dụ: đây là tệp Rect.aidl để tạo một lớp Rect có thể đóng gói:

package android.graphics;

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

Và dưới đâ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;
    }
}

Việc sắp xếp trong lớp Rect rất đơn giản. Hãy xem các phương thức khác trên Parcel để biết các loại giá trị khác mà bạn có thể ghi vào Parcel.

Cảnh báo: Hãy nhớ các hệ quả về bảo mật khi nhận dữ liệu từ các quy 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 rằng các số này nằm trong phạm vi giá trị chấp nhận được cho bất kỳ hành động nào mà phương thức gọi đang cố thực hiện. Để biết thêm thông tin về cách bảo vệ ứng dụng 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ố theo Gói chứa Parcelables

Nếu một phương thức chấp nhận đối tượng Bundle dự kiến chứa các gói, hãy đảm bảo bạn đã đặt trình tải lớp của Bundle bằng cách gọi Bundle.setClassLoader(ClassLoader) trước khi cố gắng đọc từ Bundle. Nếu không, bạn sẽ gặp ClassNotFoundException mặc dù parcelable được xác định chính xác trong ứng dụng của bạn.

Ví dụ: hãy xem xét tệp .aidl mẫu sau đây:

// 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);
}
Như minh hoạ trong cách triển khai sau, ClassLoader đượ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 một 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:

  1. Đưa tệp .aidl vào thư mục src/ của dự án.
  2. Khai báo một thực thể của giao diện IBinder, được tạo dựa trên AIDL.
  3. Triển khai ServiceConnection.
  4. Gọi Context.bindService(), truyền vào cách triển khai ServiceConnection.
  5. Trong quá trình triển khai onServiceConnected(), bạn nhận được một thực thể IBinder có tên là service. Gọi YourInterfaceName.Stub.asInterface((IBinder)service) để truyền tham số được trả về thành loại YourInterface.
  6. Gọi các phương thức mà bạn đã xác định trên giao diện. Luôn chặn các ngoại lệ DeadObjectException. Các ngoại lệ này được gửi khi kết nối bị ngắt. Ngoài ra, kiểm soát các ngoại lệ SecurityException được gửi khi hai 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.
  7. Để ngắt kết nối, hãy gọi Context.unbindService() bằng bản sao giao diện của bạn.

Hãy ghi nhớ những điểm sau khi gọi dịch vụ IPC:

  • Các đối tượng là tệp đối chiếu được tính trong các quy trình.
  • Bạn có thể gửi các đối tượng ẩn danh dưới dạng đố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ạ cách 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&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);
            }
        }
    }
}