一 简介IPC
1 操作系统进程间通信:
(1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
(3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身。
(4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。
(5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
(6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
(7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
2 套接口(Socket)具体实现
客户端
//1.建立客户端socket连接,指定服务器位置及端口
Socket socket =new Socket("localhost",8800);
//2.得到socket读写流
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
//输入流
InputStream is=socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));
//3.利用流按照一定的操作,对socket进行读写操作
String info="用户名:Tom,用户密码:123456";
pw.write(info);
pw.flush();
socket.shutdownOutput();
//接收服务器的相应
String reply=null;
while(!((reply=br.readLine())==null)){
System.out.println("接收服务器的信息:"+reply);
}
//关闭流和套接字
服务端
//1.建立一个服务器Socket(ServerSocket)绑定指定端口
ServerSocket serverSocket=new ServerSocket(8800);
//2.使用accept()方法阻止等待监听,获得新连接
Socket socket=serverSocket.accept();
//3.获得输入流
InputStream is=socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));
//获得输出流
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
//4.读取用户输入信息
String info=null;
while(!((info=br.readLine())==null)){
System.out.println("我是服务器,用户信息为:"+info);
}
//给客户一个响应
String reply="welcome";
pw.write(reply);
pw.flush();
//关闭流和套接字
socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。
3 Android的多进程
在Android系统中一个应用默认只有一个进程,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源,系统给每个进程分配的内存会有限制。如果一个进程占用内存超过了这个内存限制,就会报OOM的问题,很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时,很容易报OOM的问题,为了彻底地解决应用内存的问题,Android引入了多进程的概念,它允许在同一个应用内,为了分担主进程的压力,将占用内存的某些页面单独开一个进程,比如Flash、视频播放页面,频繁绘制的页面等。Android多进程使用很简单,只需要在AndroidManifest.xml的声明四大组件的标签中增加”android:process”属性即可,process分私有进程和全局进程,以“:”号开头的属于私有进程,其他应用组件不可以和他跑在同一个进程中;不以“:”号开头的属于全局进程,其他应用可以通过ShareUID的方式和他跑在同一个进程中;
由于不同的进程拥有不同的数据空间,所以无论是应用内还是应用间,均无法通过共享内存来实现进程间通信。
Socket 主要网络间的数据交换
ContentProvider 一对多的进程间数据共享
BroadcastReceiver 一对多的低频率单向通信
Messenger 低并发的一对多实时通信,无RPC需求,或者无需要返回结果的RPC需求
文件共享 适用于无关发的情况下,交换简单的数据,对实时性要求不高的场景。
AIDL 一对多通信且有RPC需求
底层是Bundle 只能传输Bundle支持的数据类型,是四大组件间的进程间通信
二、Binder机制原理
1、Binder机制
Binder是Android系统中的一种IPC进程间通信结构。
Binder的整个设计是C/S结构,客户端进程通过获取服务端进程的代理,并通过向这个代理接口方法中读写数据来完成进程间的数据通信。
Android之所以选择Binder,有2个方面的原因。
1是安全,每个进程都会被Android系统分配UID和PID,不像传统的在数据里加入UID,这就让那些恶意进程无法直接和其他进程通信,进程间通信的安全性得到提升。
2是高效,像Socket之类的IPC每次数据拷贝都需要2次,而Binder只要1次,在手机这种资源紧张的情况下很重要。
Binder机制原理图:
1.客户端获取服务端的代理对象(proxy)。我们需要明确的是客户端进程并不能直接操作服务端中的方法,如果要操作服务端中的方法,那么有一个可行的解决方法就是在客户端建立一个服务端进程的代理对象,这个代理对象具备和服务端进程一样的功能,要访问服务端进程中的某个方法,只需要访问代理对象中对应的方法即可;
2.客户端通过调用代理对象向服务端发送请求。
3.代理对象将用户请求通过Binder驱动发送到服务器进程;
4.服务端进程处理客户端发过来的请求,处理完之后通过Binder驱动返回处理结果给客户端的服务端代理对象;
5.代理对象将请求结果进一步返回给客户端进程。
通过以上5个步骤,就完成了一次Binder通信。
2、Binder机制的组成
Binder机制由三部分组成,即:
1.Client;
2.Server;
3.ServiceManager。
三部分组件之间的关系:
1.Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;
2.在Binder通信中,Server进程先注册一些Service到ServiceManager中,ServiceManager负责管理这些Service并向Client提供相关的接口;
3.Client进程要和某一个具体的Service通信,必须先从ServiceManager中获取该Service的相关信息,Client根据得到的Service信息与Service所在的Server进程建立通信,之后Clent就可以与Service进行交互了;
4.Binder驱动程序提供设备文件/dev/binder与用户空间进行交互,Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信;
5.Client、Server、ServiceManager三者之间的交互都是基于Binder通信的,所以通过任意两者这件的关系,都可以解释Binder的机制。
三、AIDL的实现
在Android中有多种实现IPC的方式,各有各的优缺点,我们拿其中一种最常用方式来更深入的了解一下Android中IPC的实现方式,从而彻底理解Binder机制的工作方式; 我们用一个最简单的场景:服务端提供计算的方法计算两个数之和并返回;客户端调用服务端的方法得出结果并显示; 首先是服务端:我们首先创建一个AIDL文件,其实就是定义我们的方法接口
// IMyAdd.aidlpackage test.jiao.com.aidl; interface IMyAdd { int add(int first,int second); }
其中定义了一个两个数相加的方法接下来我们创建一个Service
public class MyAddService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; }
private Binder mBinder = new IMyAdd.Stub() {
@Override
public int add(int first, int second) throws RemoteException {
return first + second;
}
};
}
我们在Service中实现了我们定义的AIDL接口,并且服务端在绑定接口的时候将服务端的IBinder对象返回给客户端;客户端拿到服务端的IBinder对象就可以调用服务端的方法了;
客户端实现如下:
和服务端定义一个一模一样的aidl文件(包名、方法名,参数必须一致)
// IMyAdd.aidlpackage test.jiao.com.aidl; interface IMyAdd { int add(int first,int second); }
绑定服务端的服务
import test.jiao.com.aidl.IMyAdd;
public class MainActivity extends Activity { private Button bt_aidl_add; private IMyAdd mIMyAdd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(MainActivity.this, "绑定服务成功", Toast.LENGTH_SHORT).show();
mIMyAdd = IMyAdd.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private void start() {
bindService();//绑定服务
bt_aidl_add = (Button) findViewById(R.id.bt_aidl_add);
bt_aidl_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIMyAdd != null) {
try {
int result = mIMyAdd.add(5, 5);
Toast.makeText(MainActivity.this, result + "", Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Toast.makeText(MainActivity.this, "计算失败", Toast.LENGTH_SHORT).show();
}
}
});
}
//绑定服务
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.jiao.myaidl.action.MYADD_SERVICE");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(serviceConnection);//解绑服务
super.onDestroy();
}
}
接下来我们来分析一下整个AIDL的执行过程: 1、首先服务端的MyAddService在自己的进程中向Binder驱动申请创建了一个MyAddService的Binder实体;Binder驱动为MyAddService创建了位于内核中的Binder实体节点以及Binder的引用,并将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个MyAddService;
2、SM收到数据包后,从中取出MyAddService名字和引用,填入一张查找表中。在启动服务的时候,SM就会从这张查找表中查找相应的服务;
3、客户端Client申请访问MyAddService,SM就会从请求数据包中获得MyAddService的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给client。之 后,Client就可以利用MyAddService的引用使用MyAddService的服务了。