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[]MyParcelable[]
  • String
  • CharSequence
  • List

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

  • 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 工具會在專案的 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() 回呼方法的 IBinder,並傳回 Stub 介面的例項。如要進一步瞭解如何進行投放,請參閱「呼叫 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 檔案,如以下 Rect.aidl 檔案所示。

    如果您使用自訂建構程序,請不要.aidl 檔案新增至建構項目。與 C 語言中的標頭檔案類似,這個 .aidl 檔案不會編譯。

AIDL 會在產生的程式碼中使用這些方法和欄位,以便將物件進行對齊和反對齊。

例如,以下是用來建立可分割的 Rect 類別的 Rect.aidl 檔案:

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 讀取四個數字,但您必須確保這些數字在呼叫端嘗試執行的任何操作中,都屬於可接受的值範圍。如要進一步瞭解如何保護應用程式免受惡意軟體侵害,請參閱安全性提示

含有包含 Parcelable 的套件引數的方法

如果方法接受預期包含可分割的 Bundle 物件,請務必在嘗試從 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() 的實作中,您會收到名為 serviceIBinder 例項。呼叫 YourInterfaceName.Stub.asInterface((IBinder)service) 將傳回的參數轉換為 YourInterface 類型。
  6. 呼叫您在介面上定義的方法。請一律擷取 DeadObjectException 例外狀況,這些例外狀況會在連線中斷時擲回。此外,請擷取 SecurityException 例外狀況,當 IPC 方法呼叫中涉及的兩個程序有衝突的 AIDL 定義時,系統會擲回此例外狀況。
  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);
            }
        }
    }
}