#### 深入理解共享内存
##### 前言
> Ashmem即Android Shared Memory, 是Android提供的一种内存共享的机制。
>
> java层借助MemoryFile或者SharedMemory
>
> Native层借助MemoryHeapBase或者MemoryBase,
>
> Native层调用libc的ashmem_create_region和mmap,即native层都是基于Linux层的内存共享机制
共享内存看似高深,其实在android进程通信非常常见,比如startActivity传的Intent/Bundle,为什么能从A进程把Intent传递到AMS的SystemServer进程?Intent传递的大小为什么最大为1M?要传输大的对象比如bitmap要怎么做?(本文基于andorid13)
##### Intent传递的本质
先看Intent/Bundle的定义
```java
public class Intent implements Parcelable, Cloneable {}
public final class Bundle extends BaseBundle implements Cloneable, Parcelable {}
```
Intent和Bundle都是实现Parcelable接口,其实就是通过序列化来实现通信的。
> 序列化就是把对象转换成二进制流的过程,这样就可以在网络或者进程间进行二进制流的传输。
>
> 反序列化即把二进制流还原成对象的过程。
>
> Parcelable与Serializable对比
>
> Serializable是java提供的一个标准的序列化接口,使用简单,可能会有频繁的I/O操作,并且用到反射机制,资源消耗较大。
>
> Parcelable是android提供的序列化的接口,也是google工程师引以为傲的,使用效率据说比Serializable快10倍。使用较麻烦,读和写的字段必须一一对应。
>
> 内存序列化上选择Parcelable,网络或者存储到设备上用Serializable。
Parcelable的底层使用了`Parcel`机制。数据传递实际使用了 `bindler`机制,bindler机制会将parcel序列化的数据即字节流写入一个共享内存中,读取时也是binder从共享内存中读取字节流,再通过parcel机制反序列化后使用。这就是Intent/Bundle能在进程间通信的原理。
binder使用的这块共享内存就叫`Binder transaction buffer`,这块内存有大小限制(即binder映射内存的限制),谷歌默认是1M,而且是公用的,也就是说假如同时通过Intent传递,就算每个都不超过1M,但是几个加起来超过1M也会报错
```c++
/system/libhwbinder/ProcessState.cpp
## 实际binder最大可用的共享内存大小为(1M-8kb)
#define DEFAULT_BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
```
假如要传大于1M的数据要怎么做呢?
1. 把对象先存到本地,再把地址传给另一进程,另一进程根据地址再取,本质还是地址的字符串传递
2. 共享内存。真正做到了0拷贝,效率最高。
##### 共享内存使用
可以参照项目[MyMemoryShareService](https://github.com/beyond667/PaulTest/blob/master/server/src/main/java/com/paul/test/server/ShareMemoryUtils.java),共享内存使用需结合binder
定义IMemoryFileApi.aidl
```java
interface IMemoryFileApi {
ParcelFileDescriptor getParcelFileDescriptor(String name);
oneway void releaseParcelFileDescriptor(String type);
}
```
服务端service:MyMemoryShareService.java
```java
public class MyMemoryShareService extends Service {
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private final IMemoryFileApi.Stub binder = new IMemoryFileApi.Stub() {
private ParcelFileDescriptor pfd;
private MemoryFile memoryFile;
@Override
public ParcelFileDescriptor getParcelFileDescriptor(String name) throws RemoteException {
//1 先获取共享文件的byte数组
byte[] bytes = getBytes();
//2 生成共享文件MemoryFile
memoryFile =ShareMemoryUtils.getMemoryFile(name,bytes);
//3 根据共享文件获取其文件描述
pfd = ShareMemoryUtils.getPfdFromMemoryFile(memoryFile);
return pfd;
}
@Override
public void releaseParcelFileDescriptor(String type) throws RemoteException {
//4 需在共享文件用完后close资源
ShareMemoryUtils.closeMemoryFile(memoryFile,pfd);
}
};
}
//ShareMemoryUtils.java
public class ShareMemoryUtils {
public static MemoryFile getMemoryFile(final String name, final byte[] bytes) {
MemoryFile memoryFile = new MemoryFile(name, bytes.length);
memoryFile.allowPurging(true);
memoryFile.writeBytes(bytes, 0, 0, bytes.length);
return memoryFile;
}
public static ParcelFileDescriptor getPfdFromMemoryFile(MemoryFile memoryFile) {
Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
method.setAccessible(true);
FileDescriptor fd = (FileDescriptor) method.invoke(memoryFile);
return ParcelFileDescriptor.dup(fd);
}
public static void closeMemoryFile(MemoryFile memoryFile, ParcelFileDescriptor pfd) {
if (pfd != null) pfd.close();
if (memoryFile != null) memoryFile.close();
}
}
```
服务端定义binder来返回共享内存的文件描述符,生成此描述符需以下3步:
+ 注释1先获取要共享的大文件的byte数组,此demo是从本地取了个14M的txt文件
+ 注释2把byte数组通过new MemoryFile来生成共享文件
+ 注释3通过反射调用memoryFile.getFileDescriptor方法获取FileDescriptor,再通过ParcelFileDescriptor.dup对其封装即是支持binder通信的ParcelFileDescriptor
+ 注释4 提供释放资源的方法,待客户端用完后需通知关闭共享内存
客户端获取此ParcelFileDescriptor
```java
public class ClientMainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {