Android 接口定义语言 (AIDL)

Android 接口定义语言 (AIDL) 与其他 IDL: 您可以定义编程接口 同意使用 进程间通信 (IPC)。

在 Android 上,一个进程通常无法访问 另一个进程的内存区域为了进行语音通信,它们需要将对象分解为原语, 操作系统可以为您理解和编组跨越该边界的对象。用于 编写这类编组是一项繁琐的工作,因此 Android 会使用 AIDL 为您处理。

注意:仅当您让客户 不同的应用访问您的 IPC 服务,并且您想要在 服务。如果您不需要跨资源执行并发 IPC 创建界面时,您需要通过实现 Binder。 如果您想执行 IPC,但需要处理多线程处理, 使用 Messenger 实现接口。 无论如何,请务必先了解绑定服务,然后再 如何实现 AIDL

在开始设计 AIDL 接口之前,请注意,对 AIDL 接口的调用 直接函数调用。不要对调用所在的线程做假设 。实际情况因调用是否来自 本地进程或远程进程:

  • 从本地进程发出的调用在进行调用的同一线程中执行。如果 这是您的主界面线程,该线程会继续在 AIDL 接口中执行。如果 另一个线程,也就是在服务中执行代码的线程。因此,如果仅本地 因此您可以完全控制哪些线程正在该服务中执行。但是 如果是这样,则完全不使用 AIDL;而是创建 来实施 Binder
  • 来自远程进程的调用从平台内部维护的线程池中分派 自己的流程准备好应对来自未知线程的多次调用的来电 同时发生换言之,AIDL 接口的实现必须是 完全线程安全从一个线程对同一个远程对象发出的调用次数 在接收方端按顺序到达。
  • oneway 关键字用于修改远程调用的行为。使用它时,远程调用会 。它发送交易数据并立即返回。 该接口的实现最终会将此调用作为常规远程调用从 Binder 线程池接收。如果使用 oneway 拨打本地电话号码, 不会产生任何影响,并且调用仍是同步调用。

定义 AIDL 接口

使用 Java 在 .aidl 文件中定义 AIDL 接口 然后保存到源代码语法和 Python 2.1 的 src/ 目录中, 托管服务的应用以及绑定到该服务的任何其他应用

当您构建每个包含 .aidl 文件的应用时,Android SDK 工具 根据 .aidl 文件生成 IBinder 接口,并将其保存在 项目的 gen/ 目录下。该服务必须实现 IBinder 进行必要的更改然后,客户端应用便可绑定到该服务,并从 IBinder 以执行 IPC。

如需使用 AIDL 创建绑定服务,请按以下步骤操作, :

  1. 创建 .aidl 文件

    此文件定义带有方法签名的编程接口。

  2. 实现接口

    Android SDK 工具会根据您的 .aidl 文件。此接口有一个名为 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 在导入和打包之前添加的注释以外的界面 语句。
  • 字符串和 int 常量可以在 AIDL 接口(例如 const int VERSION = 1;)中定义。
  • 方法调用由 transact() 代码,通常基于接口中的方法索引。因为这 会让版本控制变得困难 可以手动将交易代码分配给某个方法:void method() = 10;
  • 必须使用 @nullable 为可为 null 的参数和返回类型进行注解。

下面是一个 .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,增量构建几乎会立即生成 binder 类。 如果您不使用 Android Studio,则 Gradle 工具会在您下次使用 Binder 类时, 构建应用使用 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 调用是同步的。如果您知道这项服务 以毫秒为单位完成请求,不要从 Activity 的主线程调用它。 它可能会挂起应用,导致 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.
        }
    };
}

现在,当客户端(如 activity)调用 bindService() 以连接到此服务时,客户端的 onServiceConnected() 回调会收到 binder 实例(由服务的 onBind() 返回) 方法。

客户端还必须有权访问接口类。因此,如果客户端和服务 那么客户端应用必须拥有 .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;
    }
};

如需更多示例代码,请参阅 <ph type="x-smartling-placeholder"></ph> 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. 最后,创建一个声明 Parcelable 类的 .aidl 文件,如下所示 Rect.aidl 文件。

    如果您使用的是自定义构建流程,请勿.aidl文件添加到您的 build。此 .aidl 文件与 C 语言中的头文件类似,并未经过编译,

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 中读取四个数字,但您应确保这些数字在 值。如需详细了解如何保护应用免受恶意软件的侵扰,请参阅安全提示

带软件包参数(包含 Parcelable 类型)的方法

如果某个方法接受了预期包含的 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);
}
如以下实现所示,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. .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);
            }
        }
    }
}