android AIDL 01 双向通信

本文深入探讨了Android接口定义语言(AIDL)的设计目的、语法特点及其在进程间通信中的应用。解析了AIDL如何简化跨进程数据交换,对比了与其他通信机制如BroadcastReceiver和Messenger的优劣。详细介绍了AIDL的文件类型、数据类型、定向tag及自定义类型定义,通过实例展示了Service和Client的AIDL交互过程。

1、概述

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。是的,首先我们知道的第一点就是:AIDL是一种语言。既然是一种语言,那么相应的就很自然的衍生出了一些问题:

  • 为什么要设计出这么一门语言?
  • 它有哪些语法?
  • 我们应该如何使用它?
  • 再深入一点,我们可以思考,我们是如何通过它来达到我们的目的的?
  • 更深入一点,为什么要这么设计这门语言?会不会有更好的方式来实现我们的目的

 

2、为什么要设计这门语言?


设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。

每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。相对于它们而言,我们就好像造物主一样,我们可以通过AIDL来制定一些规则,规定它们能进行哪些交流——比如,它们可以在我们制定的规则下传输一些特定规格的数据。

总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。

但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用——这种时候就需要使用 AIDL 了。如果想要了解它们更详细的区别的话,可以去另一篇博文看看:Android中的Service:Binder,Messenger,AIDL(2)

 

3,它有哪些语法?

其实AIDL这门语言非常的简单,基本上它的语法和 Java 是一样的,只是在一些细微处有些许差别——毕竟它只是被创造出来简化Android程序员工作的,太复杂不好——所以在这里我就着重的说一下它和 Java 不一样的地方。主要有下面这些点:

文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。
数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包。比如,现在我们编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.lypeer.aidldemo 包下 ,现在我们需要在 .aidl 文件里使用 Book 对象,那么我们就必须在 .aidl 文件里面写上 import com.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一个包下。 
默认支持的数据类型包括: 

  • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
  • String 类型。
  • CharSequence类型。
  • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。
  • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的

 

  • 定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。在我的理解里,定向 tag 是这样的:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。具体的分析大家可以移步我的另一篇博文:你真的理解AIDL中的in,out,inout么?
  • 另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。还有,请注意,请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。
  • 两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。 
  • 注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。
     

1、AIDL支持的简单数据类型

  • Java基本类型,即int、long、char等;
  • String;
  • CharSequence;
  • List
    List中的所有元素都必须是AIDL支持的数据类型、其他AIDL接口或你之前声明的Parcelable实现类。
  • Map
    Map中的所有元素都必须是AIDL支持的数据类型、其他AIDL接口或你之前声明的Parcelable实现类。定义AIDL

2、创建ServiceAIDL


import com.testmodelconnectionapp.MClientAIDL;
import com.testmodelconnectionapp.MsgBean;
interface MServiceAIDL {

            /**
            * 接收来自客户端发来的信息
            * 模拟延时 6秒返回信息
            */
           String getClientInfo(int num1,int num2,in MsgBean msgBean);

           /*
           *接受客户端AIDL
           */
           MsgBean getServerInfo(in MClientAIDL clientAIDL);
}


3、client


import com.testmodelconnectionapp.MsgBean;
interface MClientAIDL {

    //接受服务端的消息
    String getServerInfo (in MsgBean msgBean,String str);

}

4、声明自定义类型

//声明自定义类型

parcelable MsgBean;

4、activity绑定并通过aidl调用service

   private ServiceConnection conn = new ServiceConnection() {

        //绑定上服务的时候
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //拿到了远程的服务
            mServiceAIDL = MServiceAIDL.Stub.asInterface(service);
            MClientAIDL.Stub stub = new MClientAIDL.Stub() {
                @Override
                public String getServerInfo(MsgBean msgBean, String str) throws RemoteException {
                    return null;
                }
            };
            try {
                //向服务端 传递binder
                mServiceAIDL.getServerInfo(stub);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        //断开服务
        @Override
        public void onServiceDisconnected(ComponentName name) {

            //回收资源
            mServiceAIDL = null;
        }
    };

5、server

 private IBinder iBinder = new MServiceAIDL.Stub() {
        @Override
        public String getClientInfo(int num1, int num2, MsgBean msgBean) throws RemoteException {
            Log.i(TAG, "接收到Client的信息: ");
            msgBean.setMsg1("接收到Client的信息");
            mClientAIDL.getServerInfo(msgBean,"msgg Form Server");
            return String.valueOf(num1+num2);
        }

        @Override
        public MsgBean getServerInfo(MClientAIDL clientAIDL) throws RemoteException {
            //收到客户端的binder
            mClientAIDL = clientAIDL;

            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    MsgBean msgBean = new MsgBean();
                    msgBean.setMsg1("我是来自Server的Msg1");
                    try {
                        mClientAIDL.getServerInfo(msgBean,"msgg Form Server");
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            },1000);
            return null;
        }
    };

 

<think>我们正在回答用户的问题:如何在Android中使用AIDL实现双向通信。根据提供的引用,我们知道:引用[1]和[4]提到了AIDL用于远程过程调用(RPC),并且提到了安全注意事项。引用[3]提到了Messenger和Message,但用户的问题是关于AIDL的,所以我们主要关注AIDL双向通信意味着客户端和服务端可以相互调用方法。在AIDL中,通常客户端调用服务端的方法,但若要服务端也能调用客户端的方法,则需要客户端也提供一个接口给服务端。实现步骤:1.定义服务端的AIDL接口(例如IServiceInterface.aidl),其中声明服务端暴露给客户端的方法。2.定义客户端的AIDL接口(例如IClientInterface.aidl),其中声明客户端暴露给服务端的方法。3.在服务端的AIDL接口中,增加一个注册客户端接口的方法(例如registerCallback)和解注册的方法(unregisterCallback)。这样服务端就可以持有客户端接口的引用。4.实现服务端接口,并在其中管理注册的客户端回调(使用RemoteCallbackList来管理,因为它是线程安全的)。5.在客户端,实现客户端接口,并将该实现传递给服务端(通过注册方法)。6.当服务端需要调用客户端的方法时,通过持有的客户端回调接口进行调用。注意事项:由于涉及跨进程通信,所有通过AIDL传递的参数和返回值必须是可序列化的(基本类型、String、List、Map、Parcelable、AIDL接口等)。注意安全:确保从其他进程接收的数据是安全的,避免恶意数据。下面我们将详细说明实现步骤,并给出代码示例。</think> 在 Android 中使用 AIDL 实现双向通信需要**创建双向接口**,让服务端和客户端能够相互调用方法。以下是实现步骤和关键代码: --- ### 一、实现原理 1. **服务端接口**:定义客户端调用的服务方法 2. **回调接口**:定义服务端调用的客户端方法(反向通信核心) 3. **RemoteCallbackList**:管理跨进程回调(自动处理进程死亡) --- ### 二、具体步骤 #### 1. 定义服务端 AIDL 接口 (`IService.aidl`) ```aidl // IService.aidl interface IService { void registerCallback(ICallback callback); void unregisterCallback(ICallback callback); void requestData(); // 示例方法 } ``` #### 2. 定义客户端回调接口 (`ICallback.aidl`) ```aidl // ICallback.aidl interface ICallback { void onDataReceived(in String data); } ``` #### 3. 服务端实现 ```java public class MyService extends Service { private final RemoteCallbackList<ICallback> callbackList = new RemoteCallbackList<>(); private final IService.Stub binder = new IService.Stub() { @Override public void registerCallback(ICallback callback) { callbackList.register(callback); } @Override public void unregisterCallback(ICallback callback) { callbackList.unregister(callback); } @Override public void requestData() { // 处理完数据后回调客户端 final int N = callbackList.beginBroadcast(); for (int i = 0; i < N; i++) { try { callbackList.getBroadcastItem(i).onDataReceived("Server Data"); } catch (RemoteException e) { e.printStackTrace(); } } callbackList.finishBroadcast(); } }; @Override public IBinder onBind(Intent intent) { return binder; } } ``` #### 4. 客户端实现 ```java // 实现回调接口 private ICallback callback = new ICallback.Stub() { @Override public void onDataReceived(String data) { runOnUiThread(() -> { Toast.makeText(this, "收到服务端数据: " + data, Toast.LENGTH_SHORT).show(); }); } }; // 绑定服务 ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IService iService = IService.Stub.asInterface(service); try { iService.registerCallback(callback); // 注册回调 iService.requestData(); // 触发服务端方法 } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { // 解注册回调 } }; ``` --- ### 三、关键注意事项 1. **线程安全**: - 使用 `RemoteCallbackList` 代替普通集合(自动处理跨进程回调) - 服务端回调客户端时需切换 UI 线程(如 `runOnUiThread()`) 2. **生命周期管理**: - 在 `onDestroy()` 中调用 `unregisterCallback()` - 使用 `linkToDeath()` 监控服务端进程状态[^4] 3. **序列化要求**: - AIDL 方法参数必须实现 `Parcelable` - 避免传递大对象(可能触发 `TransactionTooLargeException`) 4. **安全建议**: - 验证客户端来源(使用 `Binder.getCallingUid()`) - 对敏感数据添加权限校验[^4] --- ### 四、与其他方案对比 | 方案 | 适用场景 | 双向通信支持 | |---------------|------------------------|-------------| | **AIDL** | 复杂RPC/高频调用 | ✔️ 原生支持 | | **Messenger** | 简单消息/低频率 | ❌ 需额外实现 | | **广播** | 一对多通知 | 单向 | > 提示:对于简单的双向通信,可通过 Messenger + Handler 组合实现,但 AIDL 是性能最优方案[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值