Android 介面定義語言 (AIDL)

Android 介面定義語言 (AIDL) 與其他 IDL 類似,可讓您定義用戶端和服務雙方同意的程式設計介面,透過處理序間通訊 (IPC) 彼此通訊。

在 Android 中,一個程序通常無法存取其他程序的記憶體。如要通訊,這些物件需要將物件分解為原始物件,讓作業系統能夠理解並組合該邊界內的物件。執行壓縮作業的程式碼容易編寫,因此 Android 會使用 AIDL 為您處理。

注意:只有在您允許不同應用程式的用戶端存取 IPC 服務,且您想要在服務中處理多執行緒時,才需要 AIDL。如果您不需要在不同應用程式之間執行並行處理序間通訊 (IPC),請實作 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 介面。然後,用戶端應用程式可以繫結至服務,並呼叫 IBinder 中的方法以執行 IPC。

如要使用 AIDL 建立繫結的服務,請按照下列步驟操作:

  1. 建立 .aidl 檔案

    這個檔案定義了使用方法簽章的程式設計介面。

  2. 實作介面

    Android SDK 工具會根據您的 .aidl 檔案,以 Java 程式設計語言產生介面。這個介面含有名為 Stub 的內部抽象類別,該類別可擴充 Binder 並實作 AIDL 介面的方法。您必須擴充 Stub 類別,並實作方法。

  3. 向用戶端公開介面

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

注意:您在第一個版本後對 AIDL 介面所做的任何變更,都必須保有回溯相容性,以免造成使用服務的其他應用程式中斷。也就是說,由於您的 .aidl 檔案必須複製到其他應用程式,以便應用程式存取服務介面,因此您必須維持對原始介面的支援。

建立 .aidl 檔案

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

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

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

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

    List 中的所有元素都必須是這份清單支援的資料類型之一,或您宣告的其他 AIDL 產生的介面或 Parcelable。List 也可視需要做為參數化類型類別使用,例如 List<String>。其他端收到的實際具體類別一律為 ArrayList,但該方法為使用 List 介面而產生。

  • Map

    Map 中的所有元素都必須是這份清單支援的資料類型之一,或您宣告的其他 AIDL 產生的介面或 Parcelable。不支援參數化類型對應,例如 Map<String,Integer> 形式的形式對應。雖然其他端接收的實際具體類別一律為 HashMap,雖然該方法是為了使用 Map 介面而產生。請考慮使用 Bundle 做為 Map 的替代方案。

您必須為先前未列出的每種額外類型加入 import 陳述式,即使與介面在相同的套件中定義也一樣。

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

  • 方法可以接受零個或多個參數,並可能會傳回值或無效。
  • 所有非原始參數都需要有方向標記,用來指出資料傳輸方式:inoutinout (請參閱以下範例)。

    原始介面、StringIBinder 和 AIDL 產生的介面預設為 in,且無法採用。

    注意:請將方向限制在真正所需的方向,因為管理參數非常昂貴。

  • .aidl 檔案內含的所有程式碼註解都會包含在產生的 IBinder 介面中,但匯入和套件陳述式之前的註解除外。
  • 您可以在 AIDL 介面中定義字串和 int 常數,例如 const int VERSION = 1;
  • 方法呼叫會由 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 工具會在專案的 gen/ 目錄中產生 IBinder 介面檔案。產生的檔案名稱與 .aidl 檔案名稱相符,但副檔名為 .java。例如,IRemoteService.aidl 會產生 IRemoteService.java 結果。

如果您使用 Android Studio,漸進式版本幾乎會立即產生繫結器類別。如果您未使用 Android Studio,Gradle 工具會在您下次建構應用程式時產生繫結器類別。編寫 .aidl 檔案後,請立即使用 gradle assembleDebuggradle assembleRelease 建構專案,這樣您的程式碼才能連結至產生的類別。

實作介面

建構應用程式時,Android SDK 工具會產生名為 .aidl 檔案的 .java 介面檔案。產生的介麵包含名為 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 例項。

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

用戶端在 onServiceConnected() 回呼中收到 IBinder 時,必須呼叫 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;
    }
};

如需更多程式碼範例,請參閱 ApiDemos 中的 RemoteService.java 類別。

透過處理序間通訊 (IPC) 傳遞物件

在 Android 10 (API 級別 29 或以上) 中,您可以直接在 AIDL 中定義 Parcelable 物件。這裡也支援做為 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;
}

上述程式碼範例會自動產生含有整數欄位 lefttoprightbottom 的 Java 類別。系統會自動實作所有相關管理程式碼,因此您可以直接使用物件,無須新增任何實作。

您也可以透過 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 檔案,用於建立可 Parcelable 的 Rect 類別:

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 讀取四個數字,但您可以自行決定要針對呼叫端嘗試執行哪些操作,確保這些數字的可接受值範圍。如要進一步瞭解如何讓應用程式免於惡意軟體的攻擊,請參閱安全性提示

使用 Bundle 引數 (包含 Parcelables) 的方法

如果方法接受的 Bundle 物件預計會包含 Parcelable,則在您嘗試從 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);
}
如下列實作方式所示,系統會在 Bundle 中明確設定 ClassLoader,然後再讀取 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.
    }
};

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

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

  1. 在專案 src/ 目錄中加入 .aidl 檔案。
  2. 宣告 IBinder 介面的執行個體,此介面是根據 AIDL 產生。
  3. 實作 ServiceConnection
  4. 呼叫 Context.bindService(),傳入 ServiceConnection 實作。
  5. 實作 onServiceConnected() 時,您會收到名為 serviceIBinder 執行個體。呼叫 YourInterfaceName.Stub.asInterface((IBinder)service) 即可將傳回的參數轉換為 YourInterface 類型。
  6. 呼叫您在介面中定義的方法。一律攔截 DeadObjectException 例外狀況,系統會在連線中斷時擲回例外狀況。此外,如果涉及處理序間通訊 (IPC) 方法的兩個程序有衝突的 AIDL 定義,則擲回 SecurityException 例外狀況。
  7. 如要中斷連線,請使用介面的執行個體呼叫 Context.unbindService()

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

  • 物件會在各個程序間計入。
  • 您可以傳送匿名物件做為方法引數。

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

以下程式碼範例說明如何呼叫 AIDL 建立的服務,該服務取自 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);
            }
        }
    }
}