前言
Android 提供了几种进程间通信的方式,除了Socket,基本都是基于binder实现的。为什么要用共享内存来实现呢?因为binder传输数据被限制在1M-8k,在较大的数据交换一般会使用文件,但效率非常的低,因此使用共享内存是很好的方式。在内存中开辟一块空间,通过binder或者其他方式将fd(文件描述符)传递到客户端或服务端进程,从而实现大文件传输。
原理
Android 的 匿名共享内存(Ashmem) 基于 Linux 的共享内存,都是在临时文件系统(tmpfs)上创建虚拟文件,再映射到不同的进程。它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制。相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁。Java 层在使用时需要用到 MemoryFile,它封装了 native 代码。
Java 层使用匿名共享内存的4个点:
- 通过 MemoryFile 开辟内存空间,获得 FileDescriptor;
- 将 FileDescriptor 传递给其他进程;
- 往共享内存写入数据;
- 从共享内存读取数据。
关于 匿名共享内存 和 mmap 的关系
MemoryFile 在底层是调用了 mmap 将内存映射在了一个虚拟文件上。 两个进程通过 MemoryFile 访问这个虚拟文件对应的内存。
举例实现
一,服务端
- 首先在APP内部存储中存放一张图片:路径 data/data/.ashmem_service/files/
- 将文件写入到共享内存,并返回ParcelFileDescriptor
- 通过binder将返回的pfd传递给客户端
aidl文件
//
package ;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
ParcelFileDescriptor getPfd();
}
public class MyService extends Service {
@Override
public void onCreate() {
();
}
myAidlInterface=new () {
@Override
public ParcelFileDescriptor getPfd() throws RemoteException {
return createMemory();
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myAidlInterface;
}
//创建共享内存区域
private ParcelFileDescriptor createMemory() {
MemoryFile memoryFile=null;
byte[] bytes =readFile(new File(getFilesDir().getPath(),""));
try {
//创建内存对象 参数一:name 参数二:开辟的内存大小
memoryFile=new MemoryFile("test_memory",1024*4);
//将数据写入到内存当中
().write(bytes);
//通过反射获取到getFileDescriptor方法,注意
Method method = ("getFileDescriptor");
//得到文件描述符
FileDescriptor fd = (FileDescriptor) (memoryFile);
//将文件描述符返回
return (fd);
} catch (IOException e) {
();
} catch (NoSuchMethodException e) {
();
} catch (IllegalAccessException e) {
();
} catch (InvocationTargetException e) {
();
}
return null;
}
//file文件读取成byte[]
public static byte[] readFile(File file) {
RandomAccessFile rf = null;
byte[] data = null;
try {
rf = new RandomAccessFile(file, "r");
data = new byte[(int) ()];
(data);
} catch (Exception exception) {
();
} finally {
closeQuietly(rf);
}
return data;
}
//关闭读取file
public static void closeQuietly(Closeable closeable) {
try {
if (closeable != null) {
();
}
} catch (Exception exception) {
();
}
}
}
二,客户端
拿到fd写入到文件中即可:
public class MainActivity extends AppCompatActivity implements {
private static final String TAG = "MainActivity";
IMyAidlInterface aidl;
ParcelFileDescriptor pfd = null;
private ServiceConnection connection;
@Override
protected void onCreate(Bundle savedInstanceState) {
(savedInstanceState);
setContentView(.activity_main);
String filePath = ().getPath();
File file = new File(filePath + "/");
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
aidl = (service);
try {
pfd = ();
FileDescriptor fileDescriptor = ();
FileInputStream inputStream=new FileInputStream(fileDescriptor);
();
FileOutputStream fo = new FileOutputStream(file);
int read = ();
while (read != -1) {
(read);
read = ();
}
//关闭流
();
();
();
// (TAG,"接收到的数据:"+new String(content,"UTF-8"));
} catch (RemoteException | IOException e) {
();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
findViewById().setOnClickListener(this);
}
private void bindService() {
Intent intent = new Intent();
(new ComponentName("", ""));
boolean isbind = bindService(intent, connection, Context.BIND_AUTO_CREATE);
(TAG, "bindService: "+isbind);
}
@Override
public void onClick(View v) {
switch (()) {
case :
bindService();
break;
default:
break;
}
}
}