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ư 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ồng Binder dưới dạng một lệnh gọi từ xa thông thường. Nếu bạn dùng oneway 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:

  1. 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.

  2. 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ộ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ề phương thức triển khai Stub .

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ụng List 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ện List.

  • 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ẫu Map<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ện Map. Cân nhắc sử dụng Bundle để thay thế cho Map.

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ặc inout (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ạo IBinder 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, rightbottom. 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:

  1. Yêu cầu lớp triển khai giao diện Parcelable.
  2. 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ào Parcel.
  3. 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ện Parcelable.Creator.
  4. Cuối cùng, hãy tạo một tệp .aidl khai báo lớp theo gói, như minh hoạ sau Rect.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ượng Bundle 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);
}
Như minh hoạ trong cách triển khai sau, 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:

  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(), chuyển dữ liệu triển khai ServiceConnection.
  5. Trong quá trình triển khai onServiceConnected(), bạn sẽ nhận được một IBinder được gọi là service. Gọi điện YourInterfaceName.Stub.asInterface((IBinder)service) thành truyền tham số trả về tới 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 đặ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.
  7. Để 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&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);
            }
        }
    }
}