IPC的原理
引用Gityuan大佬的一小段话 从进程角度来看IPC
每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。
对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的,Client端与Server端进程往往采用ioctl等方法跟内核空间的驱动进行交互。
为什么采用Binder进行通信:
- 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。相比socket只需要拷贝一次即可,传输效率就高了。 从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程, 由于共享内存操作复杂,综合来看,Binder的传输效率是最好的。
- 实现C/S架构方便:Linux的众IPC方式除了Socket以外都不是基于C/S架构,而Socket主要用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。
- 安全性高:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。
编写AIDL文件
编写Aidl文件时,需要注意下面几点:
- 接口名和aidl文件名相同。
- 接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。
- Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、 CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作 为参数或返回值,自定义类型必须实现Parcelable接口。
- 自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。
- 在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。
- Java原始类型默认的标记为in,不能为其它标记。
还需要注意:客户端调用远程服务方法,被调用方法运行在服务端binder线程池中,客户单线程会被挂起,这时候如果服务端线程比较耗时,那么客户单就会长期阻塞在这里,如果客户端运行在UI线程,那么就会出现ANR,所以需要开一个子线程让客户端调用远程服务方法。
在Project视图下选中包名,右键New->AIDL->AIDL File,默认文件名即可。随即生成该文件
AIDL 是什么
AIDL(Android 接口定义语言) 是 Android 提供的一种进程间通信 (IPC) 机制。
我们可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。
在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。
编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。
通过这种机制,我们只需要写好 aidl 接口文件,编译时系统会帮我们生成 Binder 接口。
IPC:进程间通信或跨进程通信,指的是两个进程之间进行数据交互
多进程之间通信是IPC一个使用场景
使用多进程,只需给四大组件指定android:process属性;
多进程会造成几个问题:
- 静态变量或单列模式完全失效;
- 线程无法进行同步,同步机制失效;
- sharepreference可靠性下降;
- Application会多次创建;
Android 会为每一个应用或进程分配一个独立的虚拟机,每一个虚拟机在内存分配上有不同的地址空间。当一个组件跑在一个新的进程中,就重新启动了应用,多次创建Application.
AIDL 支持的数据类型
共 4 种:
- Java 的基本数据类型
- List 和 Map
- 元素必须是 AIDL 支持的数据类型
- Server 端具体的类里则必须是 ArrayList 或者 HashMap
- 其他 AIDL 生成的接口
- 实现 Parcelable 的实体
AIDL 如何编写
AIDL 的编写主要为以下三部分:
- 创建 AIDL
- 创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化
- 新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件
- Make project ,生成 Binder 的 Java 文件
- 服务端
- 创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法
- 在 onBind() 中返回
- 客户端
- 实现 ServiceConnection 接口,在其中拿到 AIDL 类
- bindService()
- 调用 AIDL 类中定义好的操作请求
AIDL 实例
下面以实例代码演示一个 AIDL 的编写。
创建 AIDL
创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化
代码语言:javascript复制package com.ztz.androidhighroad;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private int bookId;
private String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
/**
* 序列化对象
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
/**
* 反序列化对象
*/
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>(){
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in){
bookId = in.readInt();
bookName = in.readString();
}
@Override
public String toString() {
return "Book{
" "bookId=" bookId ", bookName='" bookName ''' '}'
;
}
}
实现 Parcelable 接口是为了后序跨进程通信时使用。
注意 实体类所在的包名。
新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件
在 main 文件夹下新建 aidl 文件夹,使用的包名要和 java 文件夹的包名一致:
代码语言:javascript复制// Book.aidl
package com.ztz.androidhighroad;
// Declare any non-default types here with import statements
//和声明的实体类在一个包类
parcelable Book;
在其中声明映射的实体类名称与类型
注意,这个 Book.aidl 的包名要和实体类包名一致。
然后创建接口 aidl 文件
代码语言:javascript复制// IBookManager.aidl
package com.ztz.androidhighroad;
import com.ztz.androidhighroad.Book;
// Declare any non-default types here with import statements
interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book> getBookList();
void add(in Book book);
}
在接口 aidl 文件中定义将来要在跨进程进行的操作,上面的接口中定义了两个操作:
- addBook: 添加Book
- getBookList:获取 Book列表
需要注意的是:
- 非基本类型的数据需要导入,比如上面的Book,需要导入它的全路径。
- 这里的 Book是 Book.aidl,然后通过Book.aidl 又找到真正的实体 Book类。
- 方法参数中,除了基本数据类型,其他类型的参数都需要标上方向类型。
- in(输入), out(输出), inout(输入输出)。
然后Make Project ,生成 Binder 的 Java 文件
AIDL 真正的强大之处就在这里,通过简单的定义 aidl 接口,然后编译,就会为我们生成复杂的 Java 文件。
点击 Build -> Make Project,然后等待构建完成。最后显示生成的文件如下:
代码语言:javascript复制/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\test\AndroidHighRoad\app\src\main\aidl\com\ztz\androidhighroad\IBookManager.aidl
*/
package com.ztz.androidhighroad;
// Declare any non-default types here with import statements
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.ztz.androidhighroad.IBookManager {
//Binder的唯一标识,一般用于当前类名
private static final java.lang.String DESCRIPTOR = "com.ztz.androidhighroad.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* 将服务端Binder对象转换成客户端所需要的AIDL接口对象,如果客户端与服务端位于同一个进程,就返回服务端Stub对象本身,否则返回Stub.Proxy对象
* Cast an IBinder object into an com.ztz.androidhighroad.IBookManager interface,
* generating a proxy if needed.
*/
public static com.ztz.androidhighroad.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.ztz.androidhighroad.IBookManager))) {
return ((com.ztz.androidhighroad.IBookManager) iin);
}
return new com.ztz.androidhighroad.IBookManager.Stub.Proxy(obj);
}
/**
* 返回当前binder对象
*/
@Override
public android.os.IBinder asBinder() {
return this;
}
这个方法运行在服务端Binder线程池中,但客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理,服务端通过code获取请求方法,然后从data中取出目标方法所需的参数, 执行目标方法完毕后,通过reply写入返回值,返回数据并唤醒客户端。如果onTransact方法返回false,那么客户端就请求失败,可以利用这个做权限验证,让不希望的进程无法远程调用我们的服务
代码语言:javascript复制@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<com.ztz.androidhighroad.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(descriptor);
com.ztz.androidhighroad.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.ztz.androidhighroad.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.add(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.ztz.androidhighroad.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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public java.util.List<com.ztz.androidhighroad.Book> 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.ztz.androidhighroad.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.ztz.androidhighroad.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void add(com.ztz.androidhighroad.Book book) 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 ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
//声明两个整型的id来表示接口中定义的方法
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION 0);
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION 1);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public java.util.List<com.ztz.androidhighroad.Book> getBookList() throws android.os.RemoteException;
public void add(com.ztz.androidhighroad.Book book) throws android.os.RemoteException;
}
编写服务端类
代码语言:javascript复制/**
* 服务端类
* @author Yif
*/
public class BinderService extends Service {
private final String TAG = this.getClass().getSimpleName();
private List<Book> mBooks;
private final IBookManager.Stub mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBooks;
}
@Override
public void add(Book book) throws RemoteException {
mBooks.add(book);
}
};
/**
* 客户端与服务端绑定成功后就可以回调,返回mBinder对象,客户端就可以远程调用服务端方法,实现通讯
* @param intent
* @return
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
mBooks = new ArrayList<>();
return mBinder;
}
}
需要在AndroidManifest中注册服务
代码语言:javascript复制<service android:name=".service.BinderService"
android:process=":remote"/>
编写客户端类
代码语言:javascript复制/**
* 客户端类
*
* @author Yif
*/
public class BinderActivity extends AppCompatActivity {
private IBookManager iBookManager;
private TextView tv;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//将服务端Binder对象转换成客户端所需要的AIDL接口类型,如果客户端与服务端在同一个进程,返回就是服务端Stub对象本身,否则返回是系统
// 封装后的Stub.proxy对象
iBookManager = IBookManager.Stub.asInterface(service);
try {
//给binder设置死亡代理
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iBookManager = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder);
tv = findViewById(R.id.tv_book);
Button acceptData = findViewById(R.id.btn);
bindService();
acceptData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Random random = new Random();
Book book = new Book(random.nextInt(5), "yif" random.nextInt(3));
try {
iBookManager.add(book);
tv.setText(iBookManager.getBookList().toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
Binder设置死亡代理
给Binder设置死亡代理,当服务端异常终止时,客户端收到通知,进行重新绑定远程服务,首先声明DeathRecipient对象,它是一个接口 这样就会回调binderDied对象,移除之前绑定的binder代理,并重新绑定远程服务。
代码语言:javascript复制 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (iBookManager == null) {
return;
}
//flag为标记位
iBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
iBookManager = null;
//重新绑定远程服务
bindService();
}
};
private void bindService() {
Intent intent = new Intent(this, BinderService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
}
RemoteCallbackList接口
代码语言:javascript复制/**
*
* RemoteCallbackList是系统专门用来删除跨进程listener接口
*
* /
private RemoteCallbackList<IONewBookArrivedListener> mListener = new RemoteCallbackList<>();
@Override
public void registerListener(IONewBookArrivedListener listener) throws RemoteException {
mListener.register(listener);
}
@Override
public void unregisterListener(IONewBookArrivedListener listener) throws RemoteException {
mListener.unregister(listener);
}
private void onNewBookArrived(Book book)throws RemoteException{ mBookList.add(book);//必须配对使用
final int N = mListener.beginBroadcast();
for(int i=0;i<N;i ){
IONewBookArrivedListener listener = mListener.getBroadcastItem(i); Log.i(TAG, "onNewBookArrived: notify listener");
//当服务端有新书来,调用该方法,将新书对象通过参数传递给客户端 listener.onNewBookArrived(book);
}
mListener.finishBroadcast();
}