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 创建绑定服务,请按照以下步骤操作,这些步骤将在后续部分中进行描述。
- 创建
.aidl
文件此文件定义了具有方法签名的编程接口。
- 实现接口
Android SDK 工具根据您的
.aidl
文件生成一个 Java 编程语言的接口。此接口包含一个名为Stub
的内部抽象类,该类扩展了Binder
并实现了 AIDL 接口中的方法。您必须扩展Stub
类并实现这些方法。 - 向客户端公开接口
注意:在首次发布后对 AIDL 接口进行的任何更改都必须保持向后兼容,以避免破坏使用您的服务的其他应用程序。也就是说,由于您的 .aidl
文件必须复制到其他应用程序中以便它们能够访问您的服务的接口,因此您必须维护对原始接口的支持。
创建 .aidl 文件
AIDL 使用简单的语法,允许您声明一个接口,该接口包含一个或多个可以接受参数并返回值的方法。参数和返回值可以是任何类型,甚至可以是其他 AIDL 生成的接口。
您必须使用 Java 编程语言构造 .aidl
文件。每个 .aidl
文件必须定义一个接口,并且只需要接口声明和方法签名。
默认情况下,AIDL 支持以下数据类型
- Java 编程语言中的所有原始类型(例如
int
、long
、char
、boolean
等) - 原始类型的数组,例如
int[]
字符串
CharSequence
列表
List
中的所有元素必须是此列表中支持的数据类型之一,或者是由您声明的其他 AIDL 生成的接口或可打包对象之一。List
可选地用作参数化类型类,例如List<String>
。另一方接收的实际具体类始终是ArrayList
,尽管生成的方法使用List
接口。地图
Map
中的所有元素必须是此列表中支持的数据类型之一,或者是由您声明的其他 AIDL 生成的接口或可打包对象之一。不支持参数化类型映射,例如Map<String,Integer>
形式的映射。另一方接收的实际具体类始终是HashMap
,尽管生成的方法使用Map
接口。考虑使用Bundle
作为Map
的替代方案。
您必须为之前未列出的每个其他类型包含一个 import
语句,即使它们与您的接口定义在同一个包中。
在定义服务接口时,请注意
- 方法可以接受零个或多个参数,并且可以返回值或 void。
- 所有非原始参数都需要一个指示数据流向的方向标签:
in
、out
或inout
(请参阅下面的示例)。原始类型、
String
、IBinder
和 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 assembleDebug
或 gradle 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. } };
现在,binder
是 Stub
类(一个 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 类,其中包含整数字段 left
、top
、right
和 bottom
。所有相关的编组代码都是自动实现的,并且可以无需添加任何实现即可直接使用该对象。
您还可以通过 IPC 接口将自定义类从一个进程发送到另一个进程。但是,请确保您的类的代码可供 IPC 通道的另一端使用,并且您的类必须支持 Parcelable
接口。支持 Parcelable
非常重要,因为它允许 Android 系统将对象分解成可以跨进程编组的基元类型。
要创建支持 Parcelable
的自定义类,请执行以下操作
- 使您的类实现
Parcelable
接口。 - 实现
writeToParcel
方法,该方法接收对象的当前状态并将其写入Parcel
。 - 在您的类中添加一个名为
CREATOR
的静态字段,该字段是一个实现Parcelable.Creator
接口的对象。 - 最后,创建一个
.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
的其他类型的数值。
警告:请记住接收来自其他进程的数据的安全隐患。在本例中,Rect
从Parcel
读取四个数字,但确保这些数字在调用方尝试执行的操作的可接受数值范围内是您的责任。有关如何保护您的应用程序免受恶意软件侵害的更多信息,请参阅安全提示。
包含可打包对象的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定义的远程接口,请在您的调用类中执行以下步骤。
- 将
.aidl
文件包含在项目的src/
目录中。 - 声明
IBinder
接口的一个实例,该接口是根据AIDL生成的。 - 实现
ServiceConnection
。 - 调用
Context.bindService()
,传入您的ServiceConnection
实现。 - 在您的
onServiceConnected()
实现中,您将收到一个IBinder
实例,称为service
。调用YourInterfaceName.Stub.asInterface((IBinder)service)
将返回的参数转换为YourInterface
类型。 - 调用您在接口上定义的方法。始终捕获
DeadObjectException
异常,该异常在连接断开时抛出。此外,捕获SecurityException
异常,该异常在参与IPC方法调用的两个进程具有冲突的AIDL定义时抛出。 - 要断开连接,请使用您的接口实例调用
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—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—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); } } } }