AIDL使用以及IPC原理分析(进程间通信)

概要

为了大家能够更好的理解android的进程间通信原理,以下将会从以下几个方面讲解跨进程通讯信:
1. 必要了解的概念
2. 为什么要使用aidl进程间通信
3. 可能遇到的问题以及解决办法
4. aidl的使用,通过android提供的aidl实现一个进程间通信
5. 不使用aidl,手动编写Binder实现进程间通信
6. 分析aidl的原理,梳理andriod进程间通信相关知识

1.必要了解的概念

a.IPC

IPC是Inner-Process Communication,就是进程间通信。

b.AIDL

AUDL是Android Interface Define Language 安卓接口语言缩写。

c.Binder

Binder是android中负责进程间通信的驱动类,Binder内部设计十分复杂这里我们暂不做深入研究,这里我们只需要了解它是负责进程间通信的类即可。

d.Proxy代理模式

如果你不是很了解代理模式,可以去这里看看。
Proxy_Pattern

2.WHY?

a. 某些情况下远端的服务更适合运算或者更适合执行耗时操作,这时候我们会使用aidl请求远程服务;
b. android对单个应用的内存限制,当有需求需要突破这个限制的时候我们需要另启进程扩大内存。
实际使用情况还有很多,笔者遇到的情况还不是很多这里就不意义列举了,反正aidl是一种很有效的IPC通信方式。

3.可能遇到的问题

我们都知道在android中一个应用就对应一个linux进程,或者说默认情况下所有的组件都是在同一个进程下的;我们也可以将不同的组件放在不同的进程中,详情请查看我的另外一篇文章Multi_Process_Component,这样应用就不止一个进程了,按照一个进程对应一个虚拟机,也就是说我们应用不止一个虚拟机了。可能出现的问题我们来举个栗子:

假设你的代码里有一个单例,DemoSingletion;虚拟机1启动时创建了这个单例,你在虚拟机中任何一个线程中使用都只有它一个对象,线程同步问题可以添加线程锁解决。那么问题来了,虚拟机2启动的时候还会再创建这个单例吗?如果不创建的话和虚拟机1使用的是同一个单例吗?

实际情况是每个虚拟机启动的时候都会创建各自的单例,他们是不同的对象,在不同的地址空间上。那么问题又来了,两个虚拟机操作的是不同的对象那么这个DemoSingletion怎么同步呢?无论是添加线程锁还是对象锁我们都无法做到同步,究其原因就是操作的是不同的对象。这就是多进程带来的问题之一,接下来我们列举会出现的问题并说明如何解决这些问题。

  1. 单例模式完全失效
  2. 静态变量无法同步

4.AIDL的使用

注:由于基于eclipse的adt过于老旧这里不再讲解操作,请使用android studio完成以下操作。

· 使用AIDL文件

a.新建aidl文件

在你想要创建aidl的包下新建aidl文件(这里我们命名为IDataManager),aidl文件的语法与java类似,默认生成的aidl会有一个demo方法

void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

系统生成的basicTypes这个demo方法告诉我们能够传递那些类型的数据。

b.添加自定义方法

// 无论应用的类是否和aidl文件在同一包下,都需要显示import
import org.github.lion.aidl_demo.Data;
// Declare any non-default types here with import statementsinterface IDataManager {/*** 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);int getDataTypeCount();List<Data> getData();String getUrlContent(String url);
}

List<Data> getData()这个方法中使用了自定义的数据类型,虽然我们在文件开头写了import但是还是无法通过编译,我们需要在sdk的platform下修改framework.aidl,完整路径如下:~/platforms/android-xx/framework.aidl,加入我们自己添加的类名即可:

// user define aidl parcelable data
parcelable org.github.lion.aidl_demo.Data;

这个路径实际上是系统定义的Parcelable类~/platforms/android-xx/framework.aidl,这里我们不建议修改这个文件,另一种方式是aidl文件,定义如下:

// Data.aidl Data 类的完整包名为 org.github.lion.aidl_demo.Data,我们定义的aidl文件如下即可。
package org.github.lion.aidl_demo;
parcelable Data;

Data需实现Parcelable接口。
以下是android studio的默认实现。

/*** Created by lion on 2016/10/11.* 要通过Bundle传递的数据需要实现Parcelable接口,* 一旦你实现了这个接口android studio会提示你帮* 你快速实现带有Parcel的构造函数。*/
public class Data implements Parcelable {...protected Data(Parcel in) {...}public static final Creator<Data> CREATOR = new Creator<Data>() {@Overridepublic Data createFromParcel(Parcel in) {return new Data(in);}@Overridepublic Data[] newArray(int size) {return new Data[size];}};
}

c. 编译aidl文件

到这里aidl的编写就完成了,我们build下工程,编译器会自动生成IDataManager.java文件。
该文件在工程的~/app/build/generated/source/aidl/debug/<package>/IDataManager.java,这里我们先不讲解生成的这个类,先看下如何使用aidl。

d. 添加Service类(远端服务)

添加一个Service命名为DataManagerService我们在DataManagerService中实现一个静态的IDataManager.Stub的类

private static final IDataManager.Stub mBinder = new IDataManager.Stub() {@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {}@Overridepublic int getDataTypeCount() throws RemoteException {// todo return some datareturn 0;}@Overridepublic List<Data> getData() throws RemoteException {// todo return some datareturn null;}@Overridepublic String getUrlContent(String url) throws RemoteException {// todo return some datareturn null;}
};

onBind方法中返回这个Binder,这样当我们调用Activity的bindService方法的时候就能返回这个binder对象了。

@Override
public IBinder onBind(Intent intent) {return mBinder;
}

e.绑定服务并测试夸进程通信

在你需要调用的Activity中添加如下代码:

/*** data manager service 的远程引用*/
private IDataManager dataManagerService = null;/*** 创建Service Connection用于监听service链接与断开链接*/
private ServiceConnection dataServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {dataManagerService = IDataManager.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {dataManagerService = null;}
};

当你的Activity启动时绑定远程服务

@Override
protected void onCreate(Bundle savedInstanceState) {...bindService(new Intent(this, DataManagerService.class), dataServiceConnection,Context.BIND_AUTO_CREATE);
}

接下来我们编写测试代码,在button的回调函数中我们编写如下测试代码:

public void callService(View view) {try {System.out.println(dataManagerService.getDataTypeCount());StringBuilder sb = new StringBuilder();for (Data data : dataManagerService.getData()) {System.out.println(data.toString());sb.append(data.toString()).append("\n");}textData.setText(sb.toString());new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println(dataManagerService.getUrlContent("http://www.baidu.com"));} catch (RemoteException e) {e.printStackTrace();}}}).start();} catch (RemoteException e) {e.printStackTrace();}
}

f.运行查看结果

·自己实现Binder

上面我们展示了如何使用AIDL文件实现进程间通信,为了能够更好的理解进程间通信机制接下来将会展示如何手动编写一个Binder实现IPC。

aidl生成类分析

将Android Studio切换到项目视图,找到如下文件:

我们将这个接口文件简化以下,看看系统多给我们做了些什么。

public interface IDataManager extends android.os.IInterface {/*** Local-side IPC implementation stub class.*/public static abstract class Stub extends android.os.Binder implements org.github.lion.aidl_demo.IDataManager {private static class Proxy implements org.github.lion.aidl_demo.IDataManager {}}
}

IDataManager

这个是我们定义的aidl接口,这个接口里面就要定义我们需要的要成服务能力的接口;

IDataManager.Stub

这个是一个继承自Binder并且实现了IDataManager的抽象类;

IDataManager.Stub.Proxy

这个是一个私有内部类,实现了IDataManager;

我们知道Binder是Android中的IPC通信驱动,从类结构我们就可以看出最终的实际功能类是IDataManager.Stub.Proxy。具体的类方法我们暂时不做分析,接下来我们不使用aidl文件自己实现一个Binder驱动类,写的过程中我们细细来分析各个函数的功能。

5.自己实现Binder驱动IPC通信

定义公共接口

从上面aidl生成的类我们看出需要实现IPC通信需要实现IInterface接口,并且继承Binder类从中间驱动。所以首先我们先定义公共接口继承IInterface接口。

//IDataManager.java
public interface IDataManager2 extends IInterface {// 返回值为基本数据类型,定义接口时不需要做特殊处理int getDataCount() throws RemoteException;// 自定义的返回数据类型需要实现Parcelable接口,进程间通信不能直接共享内存,需要将对象持久化。// 所以自定义的类需要实现Parcelable接口List<Data2> getData() throws RemoteException;
}/*** Data2.java* Created by lion on 2016/10/11.* 要通过Bundle传递的数据需要实现Parcelable接口,* 一旦你实现了这个接口android studio会提示你帮* 你快速实现带有Parcel的构造函数。*/
public class Data2 implements Parcelable {private int id;private String content;public Data2() {}protected Data2(Parcel in) {id = in.readInt();content = in.readString();}public static final Creator<Data2> CREATOR = new Creator<Data2>() {@Overridepublic Data2 createFromParcel(Parcel in) {return new Data2(in);}@Overridepublic Data2[] newArray(int size) {return new Data2[size];}};public int getId() {return id;}public void setId(int id) {this.id = id;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(id);dest.writeString(content);}@Overridepublic String toString() {return "id = " + id + " content = " + content;}
}

继承Binder并实现IDataManager2接口的类作为Binder的本体。
为了让代码逻辑更加清晰,这回我们的Binder类不再写成内部类。

public abstract class DataManagerNative extends Binder implements IDataManager2 {// Binder描述符,唯一标识符private static final String DESCRIPTOR = "com.github.onlynight.aidl_demo2.aidl.IDataManager2";// 每个方法对应的IDprivate static final int TRANSACTION_getDataCount = IBinder.FIRST_CALL_TRANSACTION;private static final int TRANSACTION_getData = IBinder.FIRST_CALL_TRANSACTION + 1;public DataManagerNative() {attachInterface(this, DESCRIPTOR);}/*** 将Binder转化为IInterface接口** @param binder* @return*/public static IDataManager2 asInterface(IBinder binder) {if (binder == null) {return null;}//同一进程内直接返回IInterface iin = binder.queryLocalInterface(DESCRIPTOR);if ((iin != null) && (iin instanceof IDataManager2)) {return (IDataManager2) iin;}//不在同一进程使用代理获取远程服务return new Proxy(binder);}@Overridepublic IBinder asBinder() {return this;}/*** 我们查看Binder的源码就可以看出实际上transact方法真正的执行体* 是这个onTransact方法。** @param code  服务器回掉的方法ID,每一个方法都有一个唯一id,*              这样方法回调时可通过id判断回调的方法。* @param data  输入的参数,传递给服务端的参数* @param reply 输出的参数,服务器返回的数据* @param flags 默认传入0* @return* @throws RemoteException 远端服务器无响应抛出该错误。*/@Overrideprotected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {switch (code) {case TRANSACTION_getDataCount: {data.enforceInterface(DESCRIPTOR);int _result = this.getDataCount();reply.writeNoException();reply.writeInt(_result);return true;}case TRANSACTION_getData: {data.enforceInterface(DESCRIPTOR);List<Data2> _result = this.getData();reply.writeNoException();reply.writeTypedList(_result);return true;}}return super.onTransact(code, data, reply, flags);}/*** 代理类,调用transact方法。*/private static class Proxy implements IDataManager2 {private IBinder remote;Proxy(IBinder remote) {this.remote = remote;}public String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic int getDataCount() throws RemoteException {// 输入参数Parcel _data = Parcel.obtain();//输出参数Parcel _reply = Parcel.obtain();int _result;try {_data.writeInterfaceToken(DESCRIPTOR);remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);_reply.readException();_result = _reply.readInt();} finally {_reply.recycle();_data.recycle();}return _result;}@Overridepublic List<Data2> getData() throws RemoteException {Parcel _data = Parcel.obtain();Parcel _reply = Parcel.obtain();List<Data2> _result;try {_data.writeInterfaceToken(DESCRIPTOR);remote.transact(TRANSACTION_getData, _data, _reply, 0);_reply.readException();_result = _reply.createTypedArrayList(Data2.CREATOR);} finally {_reply.recycle();_data.recycle();}return _result;}@Overridepublic IBinder asBinder() {return remote;}}
}

DataManagerNative.DESCRIPTER

Binder描述符,唯一标识符,服务端和客户端都可以通过该ID定位到Binder实例。

DataManagerNative.TRANSACTION_XXX

自定义的IInterface方法的唯一标识符。

DataManagerNative.asInterface

将Binder转换为IInterface就可以直接调用我们自己定义的方法啦。

DataManagerNative.onTransact

根据不同的TRANSACTION_ID调用调用不同的方法。

DataManagerNative.Proxy

代理远端Binder,对外提供IDataManager2的功能。

DataManagerNative.Proxy.transact

想调用远端Binder的transact方法。

可以看到DataManagerNative是个抽象类,并没有实现IDataManager2中的方法。所以我们需要在实例化这个类的时候实现这些方法,这些操作都放到Service中去完成。

Service中Binder的实现我们和上一次使用同样的代码。

public class DataManagerService extends Service {private static List<Data2> data = new ArrayList<>();static {Data2 data1 = new Data2();data1.setId(1);data1.setContent("data1");data.add(data1);Data2 data2 = new Data2();data2.setId(2);data2.setContent("data2");data.add(data2);Data2 data3 = new Data2();data3.setId(3);data3.setContent("data3");data.add(data3);Data2 data4 = new Data2();data4.setId(4);data4.setContent("data4");data.add(data4);Data2 data5 = new Data2();data5.setId(5);data5.setContent("data5");data.add(data5);}// 可以看到我们在这里实现了这个Binder,这里才是这个Binder的本体。private static DataManagerNative binder = new DataManagerNative() {@Overridepublic int getDataCount() throws RemoteException {return data.size();}@Overridepublic List<Data2> getData() throws RemoteException {return data;}};public DataManagerService() {}@Overridepublic IBinder onBind(Intent intent) {return binder;}
}

实际上我们自己修改过的类和编译器自动生成的类基本上是一样的,这里为了让大家有更加深刻的认识,我们将其手动实现一次。

6.AIDL原理分析

相信经过以上的分析大家应该有个大概的认识了,但是要动起手来应该会有很多地方卡住,接下来我们来个全面的分析,理清楚Binder的机制。

·运行原理图

首先我们先来看下原理图,让大家有个感性的认识:

调用顺序是这样:Client->operate()->transact()->onTransact()->operation()->Server

我们能看到的源码执行顺序就是这样的,由于Binder内部结构很复杂,Binder内部的如何进行数据交换如何定位服务端方法我们这里不再介绍,感兴趣的朋友可以查看Android源码。

有几个比较有趣的地方我们单独拿出来说说。

首先是transact方法

public int getDataCount() throws RemoteException {// 输入参数Parcel _data = Parcel.obtain();//输出参数Parcel _reply = Parcel.obtain();int _result;try {_data.writeInterfaceToken(DESCRIPTOR);remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);_reply.readException();_result = _reply.readInt();} finally {_reply.recycle();_data.recycle();}return _result;
}

其中_data是调用函数传入的参数,_reply是调用函数返回的结果。通过形参的方式返回在java中不常见,这里需要理解下,_reply即是函数执行完将结果赋值到这个引用中。我们只需要按照顺序read其中的结果即可_reply.readInt()

我们在看下transact方法的源码

/*** Default implementation rewinds the parcels and calls onTransact.  On* the remote side, transact calls into the binder to do the IPC.*/
public final boolean transact(int code, Parcel data, Parcel reply,int flags) throws RemoteException {if (false) Log.v("Binder", "Transact: " + code + " to " + this);if (data != null) {data.setDataPosition(0);}boolean r = onTransact(code, data, reply, flags);if (reply != null) {reply.setDataPosition(0);}return r;
}

很明显onTransact才是真正的方法执行体,而onTransact方法调用了实现IDataManager2接口的类的实现方法;注意上面大的例子看起来稍微有些复杂,DataManagerNative是个抽象类它并没有实现IDataManager2中的方法,真正实现这些方法的DataManagerNative的实例在DataManagerService中,再结合上面的原理图,相信你现在已经很了解AIDL的通信机制了吧。

以上都是个人理解进行的分析,如果哪里有问题欢迎指出,最后希望这篇文章能够帮到你。

AIDL使用以及原理分析相关推荐

  1. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式

    1.概述 上一节我们写了一个AIDL的示例,实现了两个应用之间的通信,这一节我们就来一起探讨下AIDL是如何生效的. 2.什么是AIDL AIDL:Android Interface Definiti ...

  2. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用 1...

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用 上一节我们描述了monkey的命令处理入口函数run是如何调用optionPro ...

  3. hidl 原理分析_AIDL原理分析

    季春初始,天气返暖,新冠渐去,正值学习好时机.在Android系统中,AIDL一直在Framework和应用层上扮演着很重要的角色,今日且将其原理简单分析.(文2020.03.30) 一.开篇介绍 1 ...

  4. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

  5. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  6. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  7. 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...

  8. 原理分析_变色近视眼镜原理分析

    随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...

  9. jieba分词_从语言模型原理分析如何jieba更细粒度的分词

    jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...

最新文章

  1. RTP协议之Header结构解析
  2. printf()输出
  3. jQuery——入门(四)JQuery 事件
  4. 【珍藏版】 200个机器学习 NLP Python 免费相关教程
  5. 在 Mac上手动清除应用程序/用户缓存教程
  6. iOS 常见的JS与iOS交互的需求与解决方案
  7. 计算机应用水平excel考什么,全国专业技术人员计算机应用能力考试EXCEL2003中文字处理全真模拟试卷(三)...
  8. Everyone Piano键盘钢琴软件
  9. 看产品经理怎么用360实现Java垃圾回收!
  10. WORD VBA 每一页第一段文字
  11. 如何对 ABAP 数据库表通过 ABAP 代码进行更新和删除操作试读版
  12. PSPNet编译心得
  13. STM32L系列与普通STM32F系列的比较
  14. 妙算2的串口用自己的接线(杜邦线)连接无人机210或者stm32
  15. web 视频演示,MP4小视频免费下载
  16. 《操作系统》考前秘籍
  17. SketchUp2016如何安装插件
  18. MTK交换机PHY 方案Airoha达发(econet)概要
  19. Socket实战——Teardrop代码编程
  20. 橘子学Flink03之Flink的流处理与批处理

热门文章

  1. 从备受质疑到业绩翻盘,这家少女装品牌为何能上演“命运大逆转”?
  2. 【思想】《人生效率手册》
  3. 基于51单片机的智能门禁控制系统(仿真+源码+全套资料)
  4. 基于人工势场法的路径规划
  5. 鼠标移动到图片上实现图片的放大缩小
  6. iOS 监听耳机状态
  7. LinuxC聊天程序:MyChat
  8. windows下设置GPU加速tensorflow运算(GT940M)
  9. Bug heroes虫虫英雄······超详细翻译+基本攻略
  10. Tomcat详细配置(全)