一、Linux知识点
- Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。
- 进程隔离:是一个进程不能直接操作或访问另一个进程
- 内核空间(Kernel Space)是Linux内核运行空间,用户空间(User Space)是用户程序运行空间;内核空间的数据是共享的,而用户空间则不可以数据共享。
- 系统调用:用户空间需要访问内核空间,就需要借助系统调用来实现,这是用户空间访问内核空间的唯一方式,提升了系统安全性和稳定性。
- 用户进程交互:通过系统函数和内核空间进行交互,在内核中开辟一块缓存区,进程1把数据从用户空间拷贝到内核缓存区,进程2再从内核缓存区把数据读取,这就是一次IPC,需要拷贝两次。
copy_from_user 将用户空间的数据拷贝到内核空间的内核缓存区
copy_to_user 将内核空间的内核缓存区的数据拷贝到用户空间
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSPZRkT6VleOlXRU10dVNDTwYVbiVHNHpleO1GTulzRilWO5xkNNh0YwIFSh9Fd4VGdsATMfd3bkFGazxyaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SOwEDOxkTM3IjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
缺点:接收数据的缓存是要由接收方提供,但接收方不知道需要多大的缓存
方案:
- 开辟尽量大的,浪费空间,
- 先获取消息大小,浪费时间
- Linux提供了多种进程间通信机制,这些都是需要内核空间做支持,主要有管道(pipe),信号(sinal),消息队列(Message),共享内存(Share Memory),套接字(socket)。
- 进程A和进程B可以通过系统函数和内核空间进行交互,得copy两次,效率低下,接收数据的缓存是要由接收方提供,但接收方不知道需要多大的缓存(解决方式1:开辟尽量大的,浪费空间,方式2:先获取消息大小,浪费时间)
copy_from_user 将用户空间的数据拷贝到内核空间的内核缓存区
copy_to_user 将内核空间的内核缓存区的数据拷贝到用户空间
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSPZRkT6VleOlXRU10dVNDTwYVbiVHNHpleO1GTulzRilWO5xkNNh0YwIFSh9Fd4VGdsATMfd3bkFGazxyaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SOwEDOxkTM3IjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
- 内存映射
内存映射(Memory-mapped)使一个磁盘文件与存储空间中的一个缓存区相映射,当从缓存区中取数据,就相当于读文件中的相应字节,于此类似,将数据存入缓存区,则相应的字节就自动写入文件。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中,这个映射工作通过mmap函数来实现。
二、Binder
1、Binder驱动
Binder不是Linux系统内核的一部分,要实现IPC通行,得益于Linux的动态内核可加载模块(LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行,它在运行时被链接到内核作为内核的一部分运行。Android系统通过动态添加一个内核模块运行在内核空间,这个内核模块就是Binder驱动。
2、Binder IPC底层通信原理
通信步骤如下:
- Binder驱动在内核空间创建一个数据接收缓存区
- 建立内核缓存区与数据接收缓存区(在内核中)之间的映射关系,以及数据接收缓存区和接收进程用户空间地址的映射关系
- 发送方进程通过系统调用copy_from_user()函数将数据copy到内核中的内核缓存区中,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,整个过程只用了1次拷贝,就完成了一次单行通行。
3、Binder通信过程
- 一个进程通过Binder驱动将自己注册成为ServiceManager
- servicer通过驱动向ServiceManager中注册Binder(Service的binder实体),驱动为这个Binder创建实体的引用,并在ServiceManager中加入到查找表。
- client通过名字,在Binder驱动下从ServiceManager中获取对Binder实体的引用,通过这个引用就能实现和Server进程的通信。
4、Binder通信中的代理模式
A进程想获取B进程的object时,驱动并不会真的把object返回给A,而是返回了一个object看起来一模一样的代理对象objectProxy,在A进程中调用objectProxy的方法,会通知Binder驱动,Binder查询自己维护的表单,发现是B进程object的代理对象,于是通知B进程调用该方法,并要求B进程把返回结构发给自己。当驱动拿到B进程返回的结果后会转发给A进程,一次通信就完成了。
三、进程间通信
(1)进程间通信方式对比
名称 | 优点 | 缺点 | 适用场景 |
intent | 简单易用 | 数据类型为了Bundle支持的 | 四大组件的进程间通信 |
文件共享 | 简单易用 | 不适合高并发 | 数据共享 |
AIDL | 功能强大,支持一对多并发实时通信 | 线程同步 | 复杂的进程间通信,最常用 |
Messenger | 比aidl简单 | 串行 | 简单的进程间通信 |
ContentProvider | 数据共享 | ||
RemoteViews | |||
Socket | 跨主机,通信范围广 | 传输原始的字节流 | 网络通信 |
(2)开启多进程
在Androidmenifest中给四大组件指定android:process开启多进程模式
(3)多进程造成的问题(各个进程都是独立的虚拟机)
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性下降:因为sp不支持两个进程并发进行读写,有一定几率导致数据丢失
- Application会多次创建
四、序列化
(1)Bundle传递对象为啥需要序列化
因为Bundle传递数据时只支持基本数据类型,所以在传递对象时需要序列化转换成可存储或可传输的本质状态(字节流)。
(2)序列化的两种方式
Serializable接口 | Parcelable接口 | |
平台 | java提供的 | android提供的 |
序列化原理 | 将一个对象装换成可存储或可存储的状态 | 将一个对象进程分解,分解后的每一部分都是传递可支持的数据类型 |
优缺点 | 简单,效率低,开销大,大量I/O操作 | 高效,使用较麻烦 |
使用场景 | 适合将对象序列化到存储设备或将对象系列化通过网络设备传输 | 主要用在内存的序列化 |
(3)代码示例
public class Student implements Serializable {
private static final long serialVersionUID = -4211449776975163975L;
private String name;
}
public class Student implements Parcelable {
private String name;
protected Student(Parcel in) {
name = in.readString();
}
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
//反序列化
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
}
五、Messenger
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,它是以串行的方式来处理客户端发来的消息,因此在服务端我们不需要考虑线程同步的问题。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
实现事例:
服务端:onbind方法返回自己封装好的Messenger,msg获取的replyTo属性是客户端封装的messenger
public class MessengerService extends Service {
private static class Messengerhandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
//接受客户端用msg键发送的消息
msg.getData().getString("msg")
//给客户端发消息
Messenger client=msg.replyTo;
client.send(message);
break;
}
super.handleMessage(msg);
}
}
private final Messenger mMessenger = new Messenger(new Messengerhandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
客户端:onserviceConnected获取服务端返回的messenger,发送消息时给msg.replyTo赋值为自己封装的messenger
private Messenger mService;
void bindMyMessengerService(){
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
sendData();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
private static class ClientMessenger extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
//服务端发来的消息
msg.getData().getString("reply"));
break;
}
void sendData(){
Message msg = Message.obtain(null,1);
msg.replyTo = mCilentMessenger;//设置客户端接受消息的Messenger
Bundle data = new Bundle();
data.putString("msg","客户端的消息");
msg.setData(data);
try{
mService.send(msg);
}catch (RemoteException e) {
e.printStackTrace();
}
}
六、AIDL
(1)AIDL的定义
全称Android Interface Definition Language,Android接口定义语言;如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。
本质是系统提供了一套可快速实现BInder的工具。
(2)AIDL文件支持的数据类型:
- 基本数据类型(int long char boolean double等)
- String和CharSequence
- List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
- Map:值支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- Parcelable :所有实现了Parcelable接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用, 其中自定义的Parcelable对象和AIDL对象必须显示import进来,并且还要创建同名的.aidl文件
(3)aidl运用
服务端:
(1)新建一个aidl类型的文件(里面定义了一个接口),文件最好在main文件夹下新建一个文件夹aidl,不要在java文件夹下
package aidl;
// Declare any non-default types here with import statements
interface MyAIDLService {
int getNum(int num1,int num2);
}
然后build下项目,我们可以在build-->genetated-->aidl-->debug下生一个同名的aidl文件,说明aidl文件便宜成功。如下图:
(2)新建Service,并创建中间类,中间类要实现上面定义的aidl.Stub,实例代码如下
package shop.lantian.com.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import aidl.MyAIDLService;
public class AidlService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyServiceBind();
}
class MyServiceBind extends MyAIDLService.Stub {
@Override
public int getNum(int num1, int num2) throws RemoteException {
return num1 + num2;
}
}
}
(3)配置AndroidManifest.xml,实例如下,
<service android:name=".AidlService">
<intent-filter>
<action android:name="shop.lantian.com.service.AidlService"/>
</intent-filter>
</service>
客户端:
(1)复制服务端的aidl文件原封不动的复制到客户端(注意,包名路径要一模一样)
(2)创建ServiceConnection接口的实例,覆写onServiceConnected方法和onServiceDisConnected,重点在OnServiceConnection方法
private MyAIDLService mBinder;
public class MainServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = MyAIDLService.Stub.asInterface(service);
if (mBinder != null) {
try {
int end = mBinder.getNum(1, 2);
Log.e("aaa", String.valueOf(end));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
(3)调用service
MainServiceConnection connection = new MainServiceConnection();
Intent intent = new Intent();
intent.setAction("shop.lantian.com.service.AidlService");
//从 Android 5.0开始 隐式Intent绑定服务的方式已不能使用,所以这里需要设置Service所在服务端的包名
intent.setPackage("shop.lantian.com.service");
bindService(intent, connection, BIND_AUTO_CREATE);
七、AIDL源码分析
Binder是Android的一个类,它实现了IBinder的接口,是Android中的一种跨进程通信方式。
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.xsx.ipcdemo.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.xsx.ipcdemo.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.xsx.ipcdemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.xsx.ipcdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.xsx.ipcdemo.IBookManager))) {
return ((com.xsx.ipcdemo.IBookManager) iin);
}
return new com.xsx.ipcdemo.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.xsx.ipcdemo.Booker> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.xsx.ipcdemo.Booker _arg0;
if ((0 != data.readInt())) {
_arg0 = com.xsx.ipcdemo.Booker.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.xsx.ipcdemo.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.xsx.ipcdemo.Booker> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.xsx.ipcdemo.Booker.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((booker != null)) {
_data.writeInt(1);
booker.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException;
public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException;
}
- 实现AIDL的接口都得实现android.os.IInterface接口
- 内部类Stub类,BInder的实现类,服务端听过这个类来提供服务。
- DESCRIPTOR----唯一标识,标识的是哪个Stub,一般用当前Binder的类名的完整路径表示,
- asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换为客户端所需要的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub本身,否则返回的是系统封装后的Stub.proxy对象。
- asBinder() 根据当前调用情况返回代理Proxy的Binder对象
- onTransact()
运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理
public Boolean onTransact(int code,android.os.parcel data,android.os.Parcel reply,int flags)
返回值为false标识客户端的请求会失败,可以利用这个特性来做权限验证;code是标识客户端调用的方法,data是目标方法所需要的参数,reply是目标方法执行完毕后,向reply中写入返回值。
- transact()
运行在客户端,当客户端发起远程请求的同时将当前线程挂起,之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行
- Proxy#客户端定义的method
当客户端调用此方法时,首先创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象,然后把该方法的参数信息写入data中,接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起,然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出返回的结果。
注:当客户端发起请求时,由于当前线程会被挂起直至服务器进程返回数据,所以如果一个远程方法是很耗时的,那么不能再UI线程中发起远程请求;其次由于服务端的Binder方法运行在BInder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。