Android AIDL 服务端客户端双向死亡监听和”连接后执行”的几种方式

2,133 阅读7分钟

前言

AIDL(Android 接口定义语言)是 Android 提供的一种进程间通信(IPC)机制,用于定义客户端与服务端之间的编程接口。在 Android 中,每个进程运行在独立的内存中,相互隔离。但有时我们需要应用之间进行数据传递或任务委托,这时就可以使用 AIDL。通过 AIDL,我们可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,满足进程间通信的需求。

有关 AIDL 详细介绍可直接阅读官网的 Android 接口定义语言 (AIDL) ,这篇博客,就让我们专注于通信的进程双方死亡的互相监听和对 AIDL 接口使用时的安全封装。

双向死亡监听

AIDL 通信的客户端和服务端,其中一方出现进程崩溃、进程被杀死、覆盖安装之类情况时,无法继续进行通信,而存活的一方持有的无效的引用还有可能导致 DeadObjectException ,这就需要采取适当的措施处理这种情况,例如重新绑定服务、捕获 DeadObjectException 异常或执行其他清理操作。

如果客户端和服务端需要长期保持连接,例如下面的示例,客户端向服务发生消息并监听来自服务端的消息,便可使用双向死亡监听的方式处理异常情况的发生。

示例程序

定义 .aidl 文件如下

interface TestInterface {

      void add(in int value,in MethodCallback methodCallback);

      int send(in String msg);

      int registerReceiveMessageCallback(in ReceiveMessageCallback callback);

      int unregisterReceiveMessageCallback(in ReceiveMessageCallback callback);
      
			// 用来反向监听服务是否死亡
      oneway void registerServiceCallback(in ServiceConnectCallback callback);

      oneway void unregisterServiceCallback(in ServiceConnectCallback callback);

}

服务端的实现如下

class TestService : Service() {
    private var count = 0
    private var autoIncreaseNum = 0
    private val receiveMessageCallbacks: HashMap<Int, ReceiveMessageCallback> = HashMap()
    private val deathRecipientMap: HashMap<Int, ClientDeathRecipient> = HashMap()

    override fun onCreate() {
        super.onCreate()
        initTimer()
    }

    private fun initTimer() {
        Timer().schedule(object : TimerTask() {
            override fun run() {
                autoIncreaseNum += 10
                dealDispatchMsg("count:${count} autoIncreaseNum:${autoIncreaseNum}")
            }
        }, 0, 5000)
    }

    /**
	   * TODO: 注意事项 ①
     * 处理 DeadObjectException 问题,当遍历到已死亡 client 的引用,从列表中删除
     * 虽然在 ClientDeathRecipient 已经做了监听删除(遍历先于删除发生,依然会有问题),更安全的方式,在遍历过程中再处理一次
     */
    private fun dealDispatchMsg(msg: String?) {
        val iterator: Iterator<Int> = receiveMessageCallbacks.keys.toList().iterator()
        while (iterator.hasNext()) {
            val key = iterator.next()
            val receiveMessageCallback = receiveMessageCallbacks[key]
            try {
                receiveMessageCallback?.onReceive(msg)
            } catch (e: Exception) {
                if (e is DeadObjectException) {
                    receiveMessageCallbacks.remove(key)
                }
                error(e.message ?: "")
            }
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return TestStub()
    }

    /**
	   * TODO: 注意事项 ③
     * 没有客户端与其绑定时,便会销毁
     */
    override fun onDestroy() {
        super.onDestroy()
        debug("onDestroy()")
    }

    inner class TestStub : TestInterface.Stub() {
        /**
         * methodCallback 依然有可能发生 DeadObjectException 异常,对此做 try catch 处理
         */
        override fun add(value: Int, methodCallback: MethodCallback?) {
            // 模拟耗时操作
            Thread.sleep(500 + Random().nextInt(1500).toLong())
            // TODO: 注意事项 ②
            try {
                if (System.currentTimeMillis() % 2 == 0L) {
                    count += value
                    methodCallback?.onSuccess(count.toString())
                } else {
                    methodCallback?.onFailure("add exec fail")
                }
            } catch (e: Exception) {
                error(e.message ?: "")
            }
        }

        override fun send(msg: String?): Int {
            debug("msg.length:${msg?.length}")
            return 0
        }

        override fun registerReceiveMessageCallback(callback: ReceiveMessageCallback?): Int {
            callback ?: return -1
            receiveMessageCallbacks[getCallingPid()] = callback
            return 0
        }

        override fun unregisterReceiveMessageCallback(callback: ReceiveMessageCallback?): Int {
            callback ?: return -1
            receiveMessageCallbacks.remove(getCallingPid())
            return 0
        }

        override fun registerServiceCallback(callback: ServiceConnectCallback?) {
            callback ?: return
            val deathRecipient = ClientDeathRecipient(getCallingPid(), callback)
            deathRecipientMap[getCallingPid()] = deathRecipient
            callback.asBinder().linkToDeath(deathRecipient, 0)
        }

        override fun unregisterServiceCallback(callback: ServiceConnectCallback?) {
            if (deathRecipientMap.containsKey(getCallingPid())) {
                val deathRecipient: ClientDeathRecipient? = deathRecipientMap.remove(getCallingPid())
                deathRecipient?.callBack?.asBinder()?.unlinkToDeath(deathRecipient, 0)
            }
        }
    }

    inner class ClientDeathRecipient(private val pid: Int, val callBack: ServiceConnectCallback) :
        IBinder.DeathRecipient {
        override fun binderDied() {
            debug("receive client death recipient")
            deathRecipientMap.remove(pid)
            // 移除对应死亡客户端的回调,否则会触发 DeadObjectException
            receiveMessageCallbacks.remove(pid)
        }
    }
}

服务端注意事项说明

① 向客户端分发消息时,要处理有可能得 DeadObjectException 异常

② aidl 文件中定义的接口,如果包含回调,依然有可能发生 DeadObjectException 异常

③ 服务端没有客户端与其绑定时,会结束自己

④ 这个示例不包含前台服务的相关处理

客户端绑定服务的代码如下

// 服务端的 applicationId
private const val SERVICE_PKG_NAME = "com.dafay.demo.aidl.server"

// 服务端的 Service
private const val SERVICE_CLASS_NAME = "com.dafay.demo.aidl.service.TestService"

abstract class BaseServiceConnect {
    private var application: Application = ApplicationUtils.getApp()
    private var intent: Intent = Intent().apply { component = ComponentName(SERVICE_PKG_NAME, SERVICE_CLASS_NAME) }
    @Volatile
    protected var remoteServiceProxy: TestInterface? = null
    protected var receiveListeners = CopyOnWriteArrayList<ReceiveListener>()

    private val receiveMessageCallback: ReceiveMessageCallback = object : ReceiveMessageCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onReceive(payload: String) {
            debug("onReceive:$payload")
            HandlerUtils.mainHandler.post {
                receiveListeners.forEach { it.onReceive(payload) }
            }
        }
    }
    private val serviceConnectCallback: ServiceConnectCallback = object : ServiceConnectCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onConnectReply(message: String) {
        }
    }

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
		        // TODO: 注意事项 ①
            debug("onServiceConnected")
            remoteServiceProxy = TestInterface.Stub.asInterface(service)
            // TODO: 注意事项 ②
            service.linkToDeath(object : IBinder.DeathRecipient {
                override fun binderDied() {
                    debug("binderDied")
                    release()
                }
            }, 0)
            remoteServiceProxy!!.registerServiceCallback(serviceConnectCallback)
            remoteServiceProxy!!.registerReceiveMessageCallback(receiveMessageCallback)
        }

        override fun onServiceDisconnected(name: ComponentName) {
            debug("onServiceDisconnected")
            release()
            autoReconnect()
        }
    }

    fun bindToService(): Boolean {
        if (remoteServiceProxy != null && remoteServiceProxy!!.asBinder().isBinderAlive) {
            return true
        }
        // bindService 的 ServiceConnection 回调是在主线程中
        application.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
        // TODO: 注意事项 ③
        return remoteServiceProxy != null
    }

    /**
     * 自动重连机制
     */
    private fun autoReconnect() {
        HandlerUtils.mainHandler.postDelayed({
            bindToService()
        }, 5 * 1000)
    }

    protected fun isServiceConnected(): Boolean {
        return bindToService()
    }

    private fun release() {
        remoteServiceProxy = null
    }
}

客户端注意事项说明

application.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)serviceConnection 回调是在主线程中,服务绑定的结果通过 UI 线程的 handler 回调。

② 可以不用注册服务的讣告监听,服务端死亡时 onServiceDisconnected 会回调

bindService 返回服务是否绑定成功,但回调之后才能拿到服务端的代理

连接后执行

Android 多进程通信,不可避免的会遇到客户端或者服务端 anr、crash、oom 或覆盖安装等问题,这些问题发生时,怎么合理地处理这些问题,在便捷和安全之间找到一种合适的平衡?便是下面要探讨的几种方式。

使用回调

客户端本质上主要处理一个问题:客户端与服务端尚未建立连接时,如何调用 AIDL 定义的方法。

// 服务端的 applicationId
private const val SERVICE_PKG_NAME = "com.dafay.demo.aidl.server"

// 服务端的 Service
private const val SERVICE_CLASS_NAME = "com.dafay.demo.aidl.service.TestService"

abstract class BaseServiceConnect {
    private var application: Application = ApplicationUtils.getApp()
    private var intent: Intent = Intent().apply { component = ComponentName(SERVICE_PKG_NAME, SERVICE_CLASS_NAME) }

    @Volatile
    protected var remoteServiceProxy: TestInterface? = null
    protected var receiveListeners = CopyOnWriteArrayList<ReceiveListener>()

    private val connectedSingleThreadExecutor = Executors.newSingleThreadExecutor()
    protected val funCachedThreadPool = Executors.newCachedThreadPool()
    protected val mainHandler = Handler(Looper.getMainLooper())

    private val receiveMessageCallback: ReceiveMessageCallback = object : ReceiveMessageCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onReceive(payload: String) {
            debug("onReceive:$payload")
            mainHandler.post {
                receiveListeners.forEach { it.onReceive(payload) }
            }
        }
    }
    private val serviceConnectCallback: ServiceConnectCallback = object : ServiceConnectCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onConnectReply(message: String) {
        }
    }

		/**
		 * TODO:注意事项 ① 
		 * 绑定服务成功或失败,添加对应的回调,注意 ServiceConnection 回调在主线程,其它在当前线程
		 */
    protected fun bindToService(onSuccess: () -> Unit, onFail: () -> Unit) {
        if (remoteServiceProxy != null && remoteServiceProxy!!.asBinder().isBinderAlive) {
            onSuccess.invoke()
            return
        }

        val serviceConnection = object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName, service: IBinder) {
                debug("onServiceConnected")
                remoteServiceProxy = TestInterface.Stub.asInterface(service)
                remoteServiceProxy!!.registerServiceCallback(serviceConnectCallback)
                remoteServiceProxy!!.registerReceiveMessageCallback(receiveMessageCallback)
                onSuccess.invoke()
            }

            override fun onServiceDisconnected(name: ComponentName) {
                debug("onServiceDisconnected")
                release()
                autoReconnect()
            }
        }
        connectedSingleThreadExecutor.submit {
            // bindService 的 ServiceConnection 回调是在主线程中
            val bindResult = application.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
            if (!bindResult) {
                onFail.invoke()
            }
        }
    }

    /**
     * 自动重连机制
     */
    private fun autoReconnect() {
        HandlerUtils.mainHandler.postDelayed({
            bindToService({}, {})
        }, 5 * 1000)
    }

    private fun release() {
        remoteServiceProxy = null
    }
}

对服务端代理的封装

/**
 * 回调的方式
 */
object TestServiceProxy : BaseServiceConnect() {
    /**
     * 连接后执行
     */
    private fun connectedExec(onSuccess: () -> Unit, onFail: (Throwable) -> Unit) {
        bindToService(onSuccess) {
            onFail.invoke(RuntimeException("service connecte failure"))
        }
    }

    fun bindService(callback: FunCallback<Boolean>) {
        connectedExec({ callback.onSuccess(true) }, {
            callback.onFail(it)
        })
    }
		/**
     * TODO:注意事项 ②
     * 调用 add 函数时,如果服务未连接,先进行服务连接
     * MethodCallback 回调在所调用它的线程
     */
    fun add(value: Int, callback: FunCallback<Int>) {
        connectedExec({
            funCachedThreadPool.submit {
                remoteServiceProxy!!.add(value, object : MethodCallback.Stub() {
                    override fun onSuccess(resultJson: String?) {
                        mainHandler.post {
                            debug("add result:${resultJson}")
                            callback.onSuccess(resultJson?.toInt() ?: 0)
                        }
                    }

                    override fun onFailure(errorJson: String?) {
                        mainHandler.post {
                            debug("add result:${errorJson}")
                            callback.onFail(RuntimeException(errorJson))
                        }
                    }
                })
            }
        }) {
            callback.onFail(it)
        }
    }

    fun send(msg: String, callback: FunCallback<Boolean>) {
        connectedExec({
            funCachedThreadPool.submit {
                val result = remoteServiceProxy!!.send(msg)
                mainHandler.post {
                    if (result == 0) {
                        callback.onSuccess(true)
                    } else {
                        callback.onFail(RuntimeException("send fail"))
                    }
                }
            }
        }) {
            callback.onFail(it)
        }
    }

    fun registerReceiverListener(listener: ReceiveListener?) {
        if (!receiveListeners.contains(listener)) {
            receiveListeners.add(listener)
        }
    }

    interface FunCallback<T> {
        fun onSuccess(t: T)
        fun onFail(e: Throwable)
    }
}

客户端注意事项说明

① 绑定服务成功或失败,添加对应的回调,注意 ServiceConnection 回调在主线程,其它函数调用在当前所在线程。

② 调用 add 函数的示例,如果服务未连接,先进行服务连接,MethodCallback 回调在所调用它的线程中。

除了使用回调,也可以使用任务队列来保证先连接后执行;但回调的方式更易于理解,并且通常情况下我们希望调用 AIDl 方法的当时就能知悉执行结果。

使用协程

bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) 回调中拿到服务端的代理IBinder ,我们可以使用 kotlin 协程把回调的写法转成流式的写法,这样就能线性地写代码,不用跳来跳去,kotlin 协程本质上是回调,在这个场景下,RxJava 不可适用(RxJava 流式写法,例如blockingFirst 阻塞执行,会阻塞主线程导致 ServiceConnection 无法正常回调),这也是 kotlin 协程与 RxJava 的一个重要的差异点。

private const val SERVICE_PKG_NAME = "com.dafay.demo.aidl.server"
private const val SERVICE_CLASS_NAME = "com.dafay.demo.aidl.service.TestService"

/**
 * 使用协程,包装服务先连接,再执行远程方法
 */
abstract class BaseServiceConnect {
    private var application: Application = ApplicationUtils.getApp()
    private var intent: Intent = Intent().apply { component = ComponentName(SERVICE_PKG_NAME, SERVICE_CLASS_NAME) }

    protected var remoteServiceProxy: TestInterface? = null
    protected var receiveListeners = CopyOnWriteArrayList<ReceiveListener>()
    protected val mainHandler = Handler(Looper.getMainLooper())
    private val receiveMessageCallback: ReceiveMessageCallback = object : ReceiveMessageCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onReceive(payload: String) {
            debug("onReceive:$payload")
            mainHandler.post {
                receiveListeners.forEach { it.onReceive(payload) }
            }
        }
    }
    private val serviceConnectCallback: ServiceConnectCallback = object : ServiceConnectCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onConnectReply(message: String) {
        }
    }

    private fun bindToService(callback: ((Boolean) -> Unit)) {
        if (remoteServiceProxy != null && remoteServiceProxy!!.asBinder().isBinderAlive) {
            callback.invoke(true)
            return
        }
        val serviceConnection = object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName, service: IBinder) {
                debug("bindToService onServiceConnected")
                remoteServiceProxy = TestInterface.Stub.asInterface(service)
                remoteServiceProxy!!.registerServiceCallback(serviceConnectCallback)
                remoteServiceProxy!!.registerReceiveMessageCallback(receiveMessageCallback)
                callback.invoke(true)
            }

            override fun onServiceDisconnected(name: ComponentName) {
                debug("bindToService onServiceDisconnected")
                release()
            }
        }
        val result = application.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
        if (!result) {
            callback.invoke(false)
        }
    }
		/**
		 * TODO: 注意事项 ①
		 * 把回调转成流式写法
		 */
    suspend fun bindToService(): Boolean = withContext(Dispatchers.Default) {
        return@withContext suspendCoroutine { continuation ->
            bindToService { continuation.resume(it) }
        }
    }

    private fun release() {
        remoteServiceProxy = null
    }
}

对服务端代理的封装

object TestServiceProxy : BaseServiceConnect() {
		/**
		 * TODO: 注意事项 ②
		 * 用 async 方式,保证连接服务先执行完成
		 */
    private suspend fun checkConnectedService(): Boolean {
        val job = CoroutineScope(Dispatchers.Default).async {
            bindToService()
        }
        return job.await()
    }

    suspend fun bindService() {
        bindToService()
    }

    suspend fun add(value: Int): Int {
        if (!checkConnectedService()) {
            return -1
        }
        val job = CoroutineScope(Dispatchers.Default).async {
            doAdd(value)
        }
        try {
            return job.await()
        } catch (e: Exception) {
            error(e.message ?: "")
        }
        return -1
    }

    /**
     * 回调转同步
     */
    private suspend fun doAdd(value: Int): Int = withContext(Dispatchers.Default) {
        return@withContext suspendCoroutine { continuation ->
            remoteServiceProxy!!.add(value, object : MethodCallback.Stub() {
                override fun onSuccess(resultJson: String?) {
                    debug("add result:${resultJson}")
                    continuation.resume(resultJson?.toInt() ?: 0)
                }

                override fun onFailure(errorJson: String?) {
                    debug("add result:${errorJson}")
                    continuation.resumeWithException(RuntimeException("add fail"))
                }
            })
        }
    }

    suspend fun send(msg: String): Int {
        if (!checkConnectedService()) {
            return -1
        }
        return remoteServiceProxy!!.send(msg)
    }

    fun registerReceiverListener(listener: ReceiveListener?) {
        if (!receiveListeners.contains(listener)) {
            receiveListeners.add(listener)
        }
    }
}

客户端注意事项说明

① 使用 kotlin 协程把回调转成流式写法。

② 在 kotlin 协程作用域内,保证连接服务先执行。

如果项目代码大量使用 kotlin,自然使用 kotlin 协程这种方式更合适,而且使用 kotlin 协程便于对一些耗时操作做处理。

阻塞执行 bindService (Android Api >29 )

在 Android Api >29 时,bindService 增加了一个重载方法,ServiceConnection 的回调可以在子线程中发生。

    /**
     * Same as {@link #bindService(Intent, ServiceConnection, int)
     * bindService(Intent, ServiceConnection, int)} with executor to control ServiceConnection
     * callbacks.
     *
     * @param executor Callbacks on ServiceConnection will be called on executor. Must use same
     *      instance for the same instance of ServiceConnection. (ServiceConnection 将在 executor 定义的线程执行)
	   * 
     * @return The result of the binding as described in
     *      {@link #bindService(Intent, ServiceConnection, int)
     *      bindService(Intent, ServiceConnection, int)}.
     */
    public boolean bindService(@RequiresPermission @NonNull Intent service,
            @BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
            @NonNull ServiceConnection conn) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

基于这个方法,便可对客户端连接代码进行改写

abstract class BaseServiceConnect {
    private var application: Application = ApplicationUtils.getApp()
    private var intent: Intent = Intent().apply { component = ComponentName(SERVICE_PKG_NAME, SERVICE_CLASS_NAME) }

    @Volatile
    protected var remoteServiceProxy: TestInterface? = null
    protected var receiveListeners = CopyOnWriteArrayList<ReceiveListener>()
    protected val mainHandler = Handler(Looper.getMainLooper())

    private val receiveMessageCallback: ReceiveMessageCallback = object : ReceiveMessageCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onReceive(payload: String) {
            debug("onReceive:$payload")
            mainHandler.post {
                receiveListeners.forEach { it.onReceive(payload) }
            }
        }
    }
    private val serviceConnectCallback: ServiceConnectCallback = object : ServiceConnectCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onConnectReply(message: String) {
        }
    }

    fun bindToService(): Boolean {
        if (remoteServiceProxy != null && remoteServiceProxy!!.asBinder().isBinderAlive) {
            return true
        }
        // TODO: 注意事项 ①
        val countDownLatch = CountDownLatch(1)
        val serviceConnection = object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName, service: IBinder) {
                debug("bindToService onServiceConnected")
                remoteServiceProxy = TestInterface.Stub.asInterface(service)
                remoteServiceProxy!!.registerServiceCallback(serviceConnectCallback)
                remoteServiceProxy!!.registerReceiveMessageCallback(receiveMessageCallback)
                countDownLatch.countDown()
            }

            override fun onServiceDisconnected(name: ComponentName) {
                debug("bindToService onServiceDisconnected")
                release()
                countDownLatch.countDown()
            }
        }
        val result = application.bindService(
            intent,
            Context.BIND_AUTO_CREATE,
            Executors.newSingleThreadExecutor(), // 使回调在子线程
            serviceConnection
        )
        if (!result) {
            countDownLatch.countDown()
        }
        debug("countDownLatch start")
        countDownLatch.await(10 * 1000, TimeUnit.SECONDS)
        debug("countDownLatch end")
        return remoteServiceProxy != null
    }

    protected fun isServiceConnected(): Boolean {
        return bindToService()
    }

    private fun release() {
        remoteServiceProxy = null
    }
}

对服务端代理的封装

object TestServiceProxy : BaseServiceConnect() {

    fun bindService() {
        bindToService()
    }

    fun add(value: Int) {
        if (!isServiceConnected()) {
            return
        }
        return remoteServiceProxy!!.add(value, null)
    }

    fun send(msg: String): Int {
        if (!isServiceConnected()) {
            return -1
        }
        return remoteServiceProxy!!.send(msg)
    }

    fun registerReceiverListener(listener: ReceiveListener?) {
        if (!receiveListeners.contains(listener)) {
            receiveListeners.add(listener)
        }
    }
}

客户端注意事项说明

① 使用同步工具类控制 bindService 阻塞执行,也可以使用别的加锁机制。

相对而言,这种方式代码量是最少的,但还是需要进行异常传递、耗时调用在子线程中进行等处理。

Feature

  • 验证 Binder 传输数据大小限制, 二进制数据的长度限制 1MB - 8KB

    /**
         *  1MB 减去 8KB(即 1024KB - 8KB),binder 这个限制是二进制数据的长度限制
         *  1 一个字符 2B ?
         *  通过日志测算一下 字符串 到 二进制 的转换
         *  测试 1024*170=174080   348252
         *  测试 1024*180=184320   368732
         *  计算出来 92 + (string.length*2)
         *  推算能传输的最大 String 长度为 (1024*1024-8*1024-92)/2 =520146
         */
        private fun generateString(length: Int): String {
            val charArray = CharArray(length)
            for (i in 0 until length) {
                charArray[i] = if (i % 2 == 0) 'a' else 'b'
            }
            return String(charArray)
        }
    
  • 这篇写的好凌乱 o(╥﹏╥)o

  • Binder 机制原理,这个留在后续单独写一个 OpenBinder 的 demo,从 c++ 角度写一篇博客

参考文档

原文链接

Android 接口定义语言 (AIDL) (developer.android)

车载Android应用开发与分析 - AIDL实践与封装(上)(很详细)

代码示例 demo-aidl.zip