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>。另一方接收的实际具体类始终是ArrayList,尽管生成的方法使用List接口。

  • Map

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

对于前面未列出的每种附加类型,您都必须包含一个import语句,即使它们在与您的接口相同的包中定义也是如此。

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

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

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

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

  • 包含在.aidl文件中的所有代码注释都包含在生成的IBinder接口中,导入和包语句之前的注释除外。
  • 可以在AIDL接口中定义字符串和整数常量,例如const int VERSION = 1;
  • 方法调用由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,增量构建会几乎立即生成Binder类。如果您不使用Android Studio,Gradle工具会在您下次构建应用程序时生成Binder类。在完成.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调用是同步的。如果您知道服务完成请求需要超过几毫秒的时间,请不要从活动的
  • 只有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);
            }
        }
    }
}