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 接口

.aidl 文件中使用 Java 编程语言语法定义您的 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[]
  • 字符串
  • CharSequence
  • 列表

    List 中的所有元素必须是此列表中支持的数据类型之一,或者是由您声明的其他 AIDL 生成的接口或可打包对象之一。List 可选地用作参数化类型类,例如 List<String>。另一方接收的实际具体类始终是 ArrayList,尽管生成的方法使用 List 接口。

  • 地图

    Map 中的所有元素必须是此列表中支持的数据类型之一,或者是由您声明的其他 AIDL 生成的接口或可打包对象之一。不支持参数化类型映射,例如 Map<String,Integer> 形式的映射。另一方接收的实际具体类始终是 HashMap,尽管生成的方法使用 Map 接口。考虑使用 Bundle 作为 Map 的替代方案。

您必须为之前未列出的每个其他类型包含一个 import 语句,即使它们与您的接口定义在同一个包中。

在定义服务接口时,请注意

  • 方法可以接受零个或多个参数,并且可以返回值或 void。
  • 所有非原始参数都需要一个指示数据流向的方向标签:inoutinout(请参阅下面的示例)。

    原始类型、StringIBinder 和 AIDL 生成的接口默认为 in,并且不能更改。

    注意:将方向限制在真正需要的范围内,因为编组参数代价很高。

  • .aidl 文件中包含的所有代码注释都包含在生成的 IBinder 接口中,除了 import 和 package 语句之前的注释。
  • 可以在 AIDL 接口中定义字符串和整数常量,例如 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;
    }
};

有关更多示例代码,请参阅 RemoteService.java 类,该类位于 ApiDemos 中。

通过 IPC 传递对象

在 Android 10(API 级别 29 或更高版本)中,您可以在 AIDL 中直接定义 Parcelable 对象。此处也支持作为 AIDL 接口参数和其他可打包对象支持的类型。这避免了手动编写编组代码和自定义类的额外工作。但是,这也会创建一个裸结构。如果需要自定义访问器或其他功能,请改用实现 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;
}

前面的代码示例会自动生成一个 Java 类,其中包含整数字段 lefttoprightbottom。所有相关的编组代码都是自动实现的,并且可以无需添加任何实现即可直接使用该对象。

您还可以通过 IPC 接口将自定义类从一个进程发送到另一个进程。但是,请确保您的类的代码可供 IPC 通道的另一端使用,并且您的类必须支持 Parcelable 接口。支持 Parcelable 非常重要,因为它允许 Android 系统将对象分解成可以跨进程编组的基元类型。

要创建支持 Parcelable 的自定义类,请执行以下操作

  1. 使您的类实现 Parcelable 接口。

  2. 实现writeToParcel方法,该方法接收对象的当前状态并将其写入Parcel
  3. 在您的类中添加一个名为CREATOR的静态字段,该字段是一个实现Parcelable.Creator接口的对象。
  4. 最后,创建一个.aidl文件来声明您的可打包类,如下面的Rect.aidl文件所示。

    如果您使用的是自定义构建过程,则*不要*将.aidl文件添加到您的构建中。类似于C语言中的头文件,此.aidl文件不会被编译。

AIDL使用代码生成过程中这些方法和字段来编组和解组您的对象。

例如,以下是一个Rect.aidl文件,用于创建一个可打包的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的其他类型的数值。

警告:请记住接收来自其他进程的数据的安全隐患。在本例中,RectParcel读取四个数字,但确保这些数字在调用方尝试执行的操作的可接受数值范围内是您的责任。有关如何保护您的应用程序免受恶意软件侵害的更多信息,请参阅安全提示

包含可打包对象的Bundle参数方法

如果某个方法接受一个预期包含可打包对象的Bundle对象,请确保在尝试从Bundle读取之前,通过调用Bundle.setClassLoader(ClassLoader)设置Bundle的类加载器。否则,即使可打包对象在您的应用程序中正确定义,也会遇到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);
}
如以下实现所示,在读取Rect之前,在Bundle中显式设置了ClassLoader

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. .aidl文件包含在项目的src/目录中。
  2. 声明IBinder接口的一个实例,该接口是根据AIDL生成的。
  3. 实现ServiceConnection
  4. 调用Context.bindService(),传入您的ServiceConnection实现。
  5. 在您的onServiceConnected()实现中,您将收到一个IBinder实例,称为service。调用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);
            }
        }
    }
}