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ụ đều đồng ý để giao tiếp với nhau bằng cách sử dụng giao tiếp liên tiến 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 quy trình khác. Để giao tiếp, các thành phần này cần phân ly các đối tượng thành các đối tượng 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ã để thực hiện việc điều phối đó rất khó viế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 từ 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ý đa luồng trong dịch vụ của mình. Nếu bạn không cần thực hiện IPC đồng thời trên nhiều ứng dụng, hãy tạo giao diện bằng cách triển khai Binder. Nếu bạn muốn thực hiện IPC nhưng không cần xử lý nhiều luồng, hãy triển khai giao diện của bạn bằng Messenger. Dù sao, hãy đảm bảo rằng bạn hiểu rõ các dịch vụ liên kết 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à lệnh gọi hàm trực tiếp. Đừng giả định về luồng mà lệnh gọi xảy ra. Điều gì sẽ xảy ra 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 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, 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ộ mới truy cập vào dịch vụ, thì bạn có thể kiểm soát hoàn toàn luồng nào đang thực thi trong đó. Tuy nhiên, nếu đó là 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 được điều phố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. Hãy chuẩn bị cho các lệnh gọi đến từ các luồng không xác định, với nhiều lệnh gọi diễn ra cùng một 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 về 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 đến theo thứ tự ở đầu nhận.
  • Từ khoá oneway sửa đổi hành vi của lệnh gọi từ xa. Khi bạn sử dụng thuộc tính này, lệnh gọi từ xa sẽ không chặn. Phương thức này 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 sử dụng với lệnh gọi cục bộ, thì sẽ không có tác động nào và lệnh gọi vẫn đồng bộ.

Xác định giao diện AIDL

Xác định giao diện AIDL của bạn 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à 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, các công cụ SDK Android sẽ tạo giao diện IBinder dựa trên tệp .aidl và lưu giao diện đó 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 khách có thể liên kết với dịch vụ và gọi các phương thức từ IBinder để thực hiện IPC.

Để tạo một dịch vụ có giới hạn bằng AIDL, hãy làm theo các bước sau (được mô tả trong các phần sau):

  1. Tạo tệp .aidl

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

  2. Triển khai giao diện

    Các công cụ SDK Android sẽ tạo một 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 có tên là Stub 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. Hiển thị giao diện cho ứng dụng

    Triển khai Service và ghi đè onBind() để trả về cách 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 phải 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. Tức là vì tệp .aidl của bạn phải được sao chép sang các ứng dụng khác để các ứng dụng đó có thể truy cập vào giao diện của dịch vụ, nên bạn phải duy trì khả năng hỗ trợ giao diện gốc.

Tạo tệp .aidl

AIDL sử dụng cú pháp đơn giản cho phép bạn khai báo một giao diện có một hoặc nhiều phương thức có thể nhận 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, ngay 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 khai báo giao diện và chữ ký phương thức.

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

  • Tất cả kiểu dữ liệu gốc trong ngôn ngữ lập trình Java (chẳng hạn như int, long, char, boolean, v.v.)
  • Mảng thuộc bất kỳ loại nào, chẳng hạn như int[] hoặc MyParcelable[]
  • String
  • CharSequence
  • List

    Tất cả phần tử trong List phải là một trong các loại dữ liệu được hỗ trợ trong danh sách này hoặc một trong các giao diện hoặc phần tử có thể phân phố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 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ù phương thức đượ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 các loại dữ liệu được hỗ trợ trong danh sách này hoặc một trong các giao diện hoặc phần tử có thể phân phối khác do AIDL tạo mà bạn khai báo. Không hỗ trợ bản đồ loại được tham số hoá, chẳng hạn như bản đồ loại của biểu mẫu Map<String,Integer>. Lớp cụ thể thực tế mà 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 thế cho Map.

Bạn phải thêm câu lệnh import cho mỗi loại bổ sung chưa được liệt kê trước đó, ngay cả khi các loại đó đượ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:

  • Phương thức có thể nhận tham số từ 0 trở lên và có thể trả về một giá trị hoặc giá trị rỗng.
  • Tất cả tham số không gốc đều cần có thẻ định hướng để cho biết dữ liệu được chuyển đi theo hướng nào: in, out hoặc inout (xem ví dụ dưới đây).

    Các giao diện gốc, String, IBinder và giao diện do AIDL tạo là in theo mặc định và không thể khác.

    Thận trọng: Hãy giới hạn hướng đến những gì thực sự cần thiết, vì việc điều phối tham số sẽ tốn kém.

  • Tất cả các nhận xét mã có trong tệp .aidl đều được đưa vào giao diện IBinder được tạo, ngoại trừ các nhận xét trước câu lệnh nhập và 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;.
  • Lệnh gọi phương thức được gửi bằng transact(), 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 tạo phiên bản, nê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ú thích các đối số 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 bạn tạo ứng dụng, các 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 được 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 xây dựng ứng dụng. Tạo dự án bằng gradle assembleDebug hoặc gradle assembleRelease ngay khi bạn hoàn tất việc viết tệp .aidl để mã của bạn có thể liên kết với lớp được tạo.

Triển khai giao diện

Khi bạn tạo ứng dụng, các 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 Stub. Đây là một 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ả các phương thức trong tệp .aidl.

Lưu ý: Stub cũng xác định một số phương thức trợ giúp, đáng chú ý nhất là asInterface(), phương thức này 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à trả về một thực thể của giao diện giả lập. Để biết thêm thông tin chi tiết về cách truyền 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 bằng ví dụ IRemoteService.aidl trước đó, 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.
    }
};

Bây giờ, binder là một thực thể của lớp Stub (Binder), xác định giao diện IPC cho dịch vụ. Trong bước tiếp theo, thực thể này sẽ được hiển thị cho các ứng dụng để chúng 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 nghĩ đến việc đa luồng ngay từ đầu và xây dựng dịch vụ của mình một cách phù hợp để an toàn cho luồng.
  • Theo mặc định, các lệnh gọi IPC là đồng bộ. Nếu bạn biết rằng dịch vụ mất nhiều 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. Điều này có thể khiến ứng dụng bị treo, khiến Android hiển thị hộp thoại "Ứng dụng không phản hồi". Gọi 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 ứ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 ứng dụng để ứng dụng có thể liên kết với giao diện đó. Để hiển thị giao diện cho dịch vụ, hãy mở rộng Service và triển khai onBind() để trả về một thực thể của lớp triển khai Stub đã tạo, như đã thảo luận trong phần trước. Dưới đây là một dịch vụ mẫu hiển thị giao diện mẫu IRemoteService cho ứ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 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 khách và dịch vụ nằm trong các ứng dụng riêng biệt, thì ứng dụng của ứng dụng khách phải có một 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, cung cấp cho ứng dụng 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(), ứng dụng phải gọi YourServiceInterface.Stub.asInterface(service) để truyền tham số được trả về sang 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 đố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ợ làm đối số giao diện AIDL và các gói có thể đóng gói khác cũng được hỗ trợ tại đây. Điều này giúp tránh phải thực hiện thêm thao tác viết mã dồn và một lớp tuỳ chỉnh theo cách thủ công. Tuy nhiên, thao tác này cũng sẽ tạo ra một cấu trúc trần. 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ước đó 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ã dồn có liên quan đều được triển khai tự động và bạn có thể sử dụng trực tiếp đối tượng mà không cần thêm phương thức triển khai nào.

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 rằng mã cho lớp của bạn có sẵn cho 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 ly các đối tượng thành các đối tượng 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 triển khai giao diện Parcelable.
  2. Triển khai writeToParcel. Phương thức này sẽ lấy trạng thái hiện tại của đối tượng và ghi trạng thái đó vào Parcel.
  3. Thêm một trường tĩnh có tên CREATOR vào lớp của bạn. Đây là một đố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ể phân phố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 tạo bản dựng tuỳ chỉnh, hãy không thêm tệp .aidl vào bản dựng. Tương tự như tệp tiêu đề trong 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 để dồn và tách các đối tượng.

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;

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 điều phối 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 của việc nhận dữ liệu từ các quy trình khác. Trong trường hợp này, Rect đọc 4 số từ Parcel, nhưng bạn có thể đả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ỳ thao tác nào 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 phần 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ượng Bundle dự kiến chứa các gói, hãy đảm bảo rằng bạn đã đặt trình tải lớp của Bundle bằng cách gọi Bundle.setClassLoader(ClassLoader) trước khi tìm cách đọc từ Bundle. Nếu không, bạn sẽ gặp phải ClassNotFoundException mặc dù đối tượng có thể phân phối đượ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);
}
Như trong quá trình triển khai sau đây, 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 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 phương thức triển khai ServiceConnection.
  5. Trong quá trình triển khai onServiceConnected(), bạn sẽ 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 ngoại lệ DeadObjectException, ngoại lệ này đượ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.
  7. Để ngắt kết nối, hãy gọi Context.unbindService() bằng thực thể của giao diệ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 số lượt tham chiếu trên các quy trình.
  • Bạn có thể gửi các đối tượng ẩn danh làm đối số phương thức.

Để biết thêm thông tin về cách 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ụ liên kết.

Dưới đây là một số mã mẫu minh hoạ cách gọi dịch vụ do AIDL tạo, 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);
            }
        }
    }
}