Android 介面定義語言 (AIDL)

Android 介面定義語言 (AIDL) 與 IDL: 可讓您定義程式設計介面 用戶端與服務會取得同意,才能使用 處理序間通訊 (IPC)

在 Android 裝置上,某個程序通常無法存取 另一個程序的記憶體為了交談,他們需要將物體分解為 使作業系統能理解和管理橫跨該邊界的物件。要使用的程式碼 「搜尋」作業的繁瑣之處在於編寫作業時 Android 會使用 AIDL 為你處理

注意:只有在客戶從下列情況發生時,您才需要使用 AIDL: 不同的應用程式會存取您的服務以接收處理序間通訊 (IPC),而您想要在 課程中也會快速介紹 Memorystore 這是 Google Cloud 的全代管 Redis 服務如果跨 建構介面時,請實作 Binder。 如果您要執行 IPC,但「不需要」處理多執行緒, 使用 Messenger 實作介面。 無論如何,請務必先瞭解「將服務繫結」 實作 AIDL

開始設計 AIDL 介面之前,請注意對 AIDL 介面的呼叫 直接函式呼叫。請勿假設呼叫內容的執行緒 會發生什麼事回應方式取決於呼叫是否來自 本機處理程序或遠端程序:

  • 從本機程序發出的呼叫會在發出呼叫的執行緒中執行。如果 這是您的主要 UI 執行緒,該執行緒會繼續在 AIDL 介面中執行。如果是 也就是另一個執行緒,也就是在服務中執行程式碼的執行緒。因此,如果只有本機 執行緒正在存取服務,您可以完全控制其中正在執行的執行緒。但 如果是這種情況,完全不要使用 AIDL;請改為建立 導入 Binder
  • 遠端程序呼叫會從平台維護的執行緒集區分派 自己的程序為不明執行緒的來電做好準備,避免重複呼叫 同時發生的問題換句話說,實作 AIDL 介面時必須 完全執行緒安全的執行緒從同一個遠端物件的一個執行緒呼叫 按照順序送達。
  • oneway 關鍵字會修改遠端呼叫的行為。使用這個元件時,遠端呼叫會 不會封鎖它會傳送交易資料並立即傳回。 對介面的實作方式最終會將此視為來自 Binder 執行緒集區的一般呼叫,做為一般遠端呼叫。如果 oneway 用於本機呼叫, 沒有影響,而且呼叫仍為同步狀態。

定義 AIDL 介面

使用 Java 在 .aidl 檔案中定義 AIDL 介面 然後儲存到原始碼的 src/ 目錄 ( 。

建構包含 .aidl 檔案的每個應用程式時,Android SDK 工具 根據 .aidl 檔案產生 IBinder 介面,並將其儲存至 專案的 gen/ 目錄內。服務必須實作 IBinder 存取 API接著,用戶端應用程式即可繫結至服務,以及呼叫 執行 IPC 的 IBinder

如要使用 AIDL 建立受限服務,請按照下列步驟操作: 下文將深入說明

  1. 建立 .aidl 檔案

    此檔案使用方法簽章定義程式設計介面。

  2. 實作介面

    Android SDK 工具會根據您的 .aidl 檔案。這個介麵包含名為 Stub 的內部抽象類別,該類別可擴充 Binder,並實作 AIDL 介面中的方法。因此您必須擴充 Stub 類別並實作這些方法。

  3. 向用戶端公開介面

    實作 Service 並覆寫 onBind(),傳回 Stub 的實作內容 類別

注意:如果您在下列日期後對 AIDL 介面做出任何變更, 您的第一個版本必須保持回溯相容,以免破壞其他應用程式 使用您的服務這是因為 .aidl 檔案必須複製到其他應用程式 如要讓他們存取服務的介面,您必須維持原始版本的支援 存取 API

建立 .aidl 檔案

AIDL 使用簡單的語法,可讓您透過一或多個方法宣告介面 擷取參數並傳回值參數和傳回值可以是任何類型,即使是 AIDL 產生的介面。

您必須使用 Java 程式設計語言建構 .aidl 檔案。每.aidl 檔案必須定義單一介面,且只需要介面宣告和方法 簽章。

根據預設,AIDL 支援下列資料類型:

  • Java 程式設計語言中的所有基本類型 (例如 intlongcharboolean 等)
  • 原始類型的陣列,例如 int[]
  • String
  • CharSequence
  • List

    List 中的所有元素必須是此物件中支援的資料類型之一 或是您宣告的其他 AIDL 產生的介面或 Parcelable。A 罩杯 您可以選擇使用 List 做為參數化類型類別,例如 List<String>。 另一端接收到的實際具體類別一律為 ArrayList,不過 方法,以便使用 List 介面。

  • Map

    Map 中的所有元素必須是此物件中支援的資料類型之一 或是您宣告的其他 AIDL 產生的介面或 Parcelable。參數化類型對應 例如表單形式 系統不支援 Map<String,Integer>。另一端實際具體類別 接收的一律為 HashMap 但由於系統產生的方法是使用 Map 介面而產生。建議做法 Bundle 做為 Map 的替代選項。

您必須為先前未列出的每個類型加入 import 陳述式。 即使和介面位於同一個套件中也一樣。

定義服務介面時,請注意下列事項:

  • 方法可採用零或多個參數,可以傳回值或 void。
  • 所有非原始參數都需要有指示標記來指出資料流向: inoutinout (請參閱以下範例)。

    基本、StringIBinder 和 AIDL 生成 介面預設為 in,在其他情況下則否。

    注意:請將導航範圍限制在真正的範圍內 因為管理參數非常昂貴

  • .aidl 檔案內含的所有程式碼註解都會包含在 產生的IBinder 介面除外 聲明。
  • 可在 AIDL 介面 (例如 const int VERSION = 1;) 中定義字串和 int 常數。
  • 方法呼叫會由 transact() 程式碼,一般以介面中的方法索引為基礎。因為這是 會造成版本管理困難 可以將交易代碼手動指派給 void method() = 10; 方法。
  • 可為空值的引數和傳回類型必須使用 @nullable 加上註解。

以下是 .aidl 的範例檔案:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements.

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

.aidl 檔案儲存在專案的 src/ 目錄中。當您 SDK 工具會在以下位置產生 IBinder 介面檔案: 專案的 gen/ 目錄產生的檔案名稱與 .aidl 檔案名稱相符,但 且擴充功能為 .java 擴充功能舉例來說,IRemoteService.aidl 會產生 IRemoteService.java

如果使用 Android Studio,漸進式版本會立即產生繫結器類別。 如果您不使用 Android Studio,下次您存取 Gradle 工具時,Gradle 工具會產生繫結器類別 建構應用程式使用 gradle assembleDebug 建立專案 或 gradle assembleRelease 建立完成的 .aidl 檔案。 以便您的程式碼能連結至產生的類別

實作介面

建構應用程式時,Android SDK 工具會產生 .java 介面檔案。 以 .aidl 檔案命名產生的介麵包含一個名為 Stub 的子類別 是其父項介面的抽象實作 (例如 YourInterface.Stub),並宣告 .aidl 檔案中的所有方法。

注意: Stub 也 定義幾個輔助方法 (特別是 asInterface()),這些方法會採用 IBinder (通常是傳遞至用戶端 onServiceConnected() 回呼方法的方法)。 會傳回虛設常式介面的例項。如要進一步瞭解如何進行這種投放程序,請參閱呼叫處理序間通訊 (IPC) 方法。

如要實作從 .aidl 產生的介面,請擴充產生的 Binder 介面 (例如 YourInterface.Stub),然後實作 繼承自 .aidl 檔案。

以下實作名為 IRemoteService 的介面實作範例,由上述程式碼定義 IRemoteService.aidl 範例:使用匿名例項:

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing.
    }
};

現在 binderStub 類別 (Binder) 的例項。 ,定義服務的 IPC 介面。在下一個步驟中, 才能與服務互動

實作 AIDL 介面時,請注意下列幾項規則:

  • 來電並不保證會在主執行緒上執行,因此您需要 從一開始就設計多執行緒,並妥善建構能確保執行緒安全的服務。
  • 根據預設,IPC 呼叫是同步的。如果您知道這項服務需要 以毫秒為單位完成要求,請不要從活動的主執行緒呼叫該要求。 它可能會停止運作,導致 Android 顯示「應用程式無回應」 對話方塊從用戶端的不同執行緒呼叫。
  • 只有參照文件所列出的例外狀況類型 Parcel.writeException()敬上 傳回給呼叫者。

向客戶公開介面

為服務實作介面後,您必須向 以便加以繫結。如要顯示介面 針對服務,擴充 Service 並實作 onBind(),以傳回實作 如上節所述,產生的 Stub。舉例來說 向用戶端公開 IRemoteService 範例介面的服務。

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface.
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing.
        }
    };
}

現在,當用戶端 (例如活動) 呼叫 bindService() 以連線至這項服務時,用戶端的 onServiceConnected() 回呼會收到 服務的 onBind() 傳回 binder 個例項 方法。

用戶端也必須有權存取介面類別。因此,如果用戶端和服務 如果是個別的應用程式,用戶端的應用程式必須有 .aidl 檔案的副本 位於 src/ 目錄中,這樣會產生 android.os.Binder 介面,以便用戶端存取 AIDL 方法。

用戶端收到 IBinder 時 在 onServiceConnected() 回呼中,您必須呼叫 YourServiceInterface.Stub.asInterface(service) 即可投放 YourServiceInterface 類型的參數:

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

如需更多程式碼範例,請參閱 RemoteService.java 類別出現在 ApiDemos

透過 IPC 傳遞物件

在 Android 10 (API 級別 29 以上版本) 中,您可以定義 直接存取 Parcelable 物件 AIDL。支援的類型做為 AIDL 介面引數和其他 parcelable 支援這裡。如此一來,就能避免手動編寫管理程式碼和自訂程式碼 類別然而,這也會建立裸體結構。如果自訂存取子或其他功能 但改為導入 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;
}

上述程式碼範例會自動產生含有整數欄位 left 的 Java 類別, toprightbottom。所有相關管理程式碼為 因此您可以直接使用物件,不必新增任何設定 。

您也可以透過 IPC 介面,將自訂類別從一個程序傳送至另一個程序。不過 請確認類別的程式碼可供 IPC 通道的另一端取得 您的類別必須支援 Parcelable 介面。提供支援 Parcelable 重要 因為這樣可讓 Android 系統把物件分解為可進行管理的原始物件 跨處理程序

如要建立支援 Parcelable 的自訂類別,請按照下列步驟操作: 包括:

  1. 讓類別實作 Parcelable 介面。
  2. 實作 writeToParcel,後者會採用 物件目前的狀態並將其寫入 Parcel
  3. 在類別中新增名為 CREATOR 的靜態欄位,這是實作物件 Parcelable.Creator 介面。
  4. 最後,建立 .aidl 檔案來宣告 parcelable 類別,如下所示 Rect.aidl 檔案。

    如果您使用自訂建構程序,請「不要」.aidl 檔案新增至您的 建構應用程式與 C 語言的標頭檔案類似,這個 .aidl 檔案未經編譯。

AIDL 會在產生的程式碼中使用這些方法和欄位,進行打包及解組 物件。

舉例來說,以下 Rect.aidl 檔案會建立 Rect 類別,且這個類別將 parcelable:

package android.graphics;

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

以下舉例說明 Rect 類別如何實作 Parcelable 通訊協定。

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

Rect 類別中的管理方式相當簡單。來看另一個 看看 Parcel 方法,看看您還可以寫入其他種類的值 至 Parcel

警告:提醒您, 擷取來自其他程序的資料在這種情況下,Rect 會從 Parcel 讀取四個數字,但您必須自行確保這些數字落在可接受的範圍內 值。如要進一步瞭解如何保護應用程式不受惡意軟體侵擾,請參閱安全性提示

具有包含 Parcelables 的 Bundle 引數的方法

假設方法接受應包含的 Bundle 物件 parcelables,請務必透過以下項目,設定 Bundle 的類別載入器: 在嘗試讀取前呼叫 Bundle.setClassLoader(ClassLoader) 來自 Bundle。否則,即使 parcelable 已正確定義在應用程式中,您仍會執行 ClassNotFoundException

例如,請參考下列 .aidl 範例檔案:

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
敬上 如以下實作所示,ClassLoader 是 在讀取 Rect 之前,明確在 Bundle 中設定:

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

呼叫處理序間通訊 (IPC) 方法

如要呼叫以 AIDL 定義的遠端介面,請在以下項目中按照下列步驟操作: 呼叫類別:

  1. 在專案 src/ 目錄中納入 .aidl 檔案。
  2. 宣告 IBinder 介面的例項,這是依據 AIDL。
  3. 導入 ServiceConnection
  4. 撥打 Context.bindService(): 傳入 ServiceConnection 實作。
  5. 實作 onServiceConnected() 時, 您收到IBinder 稱為 service致電 YourInterfaceName.Stub.asInterface((IBinder)service)到 將傳回的參數轉換為 YourInterface 類型。
  6. 呼叫您在介面中定義的方法。一律關閉通知 DeadObjectException 例外狀況,會在發生以下情況時擲回 連線中斷。此外,處理 IPC 方法呼叫的兩個程序有衝突的 AIDL 定義時,系統會擲回 SecurityException 例外狀況。
  7. 如要中斷連線,請使用介面的執行個體呼叫 Context.unbindService()

呼叫處理序間通訊 (IPC) 服務時,請留意下列要點:

  • 物件會跨程序計算的參考資料。
  • 您可以傳送匿名物件 做為方法引數

如要進一步瞭解如何繫結至服務,請參閱「繫結服務總覽」。

下列程式碼範例示範如何呼叫 AIDL 建立的服務, 使用遠端服務範例

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);
            }
        }
    }
}