Camera API回传的Image信息,如果不是立即使用,则在JVM中长时间保存的话会大量占用JVM内存,增加OOM风险。而我通过阅读ByteBuffer、DirectByteBuffer和Buffer等源码发现,如果使用Direct方式保存数据,则Buffer类中的long address变量保存的是native内存块的起始地址,并且可以通过isAccessible方法判断是否可读,于是我们可以通过这个地址把native数据通过MMAP工具或者文件读写函数放到外存先,实现Image数据不耗内存的持久化。
关于MMAP工具的编写可以先看看:
《安卓应用开发中通过JNI使用虚拟内存节约物理内存的一个例子》
《一种基于linux mmap特性的应用层虚拟内存工具的编写》
取数据过程的例子:
public Object getField(Object desObj, String fieldName) throws NoSuchFieldException {
if (desObj == null || fieldName == null) {
throw new IllegalArgumentException("parameter can not be null!");
}
Class> desClass = desObj.getClass();
return getFieldStepwise(desObj, desClass, fieldName);
}
private Object getFieldStepwise(Object desObj, Class> rootClass, String fieldName) throws NoSuchFieldException {
Class> desClass = rootClass;
while (desClass != null) {
try {
return getField(desObj, desClass, fieldName);
} catch (NoSuchFieldException ignore) {
}
try {
desClass = desClass.getSuperclass();
} catch (Exception e) {
desClass = null;
}
}
throw new NoSuchFieldException(fieldName);
}
public Object invoke(Object obj, String methodName, Object[] params) throws Exception {
return invoke(obj.getClass(), obj, methodName, params);
}
private Object invoke(Class> objClass, Object obj, String methodName, Object[] params) throws Exception {
Method method;
if (params == null || params.length == 0) {
// Method method = objClass.getMethod(methodName);
synchronized (mMethodMap) {
method = mMethodMap.get(methodName);
if (method == null) {
method = objClass.getMethod(methodName);
method.setAccessible(true);
mMethodMap.put(methodName, method);
}
}
return method.invoke(obj);
} else {
synchronized (mMethodMap) {
Class>[] clazzes = getParamsTypes(params);
String fullName = objClass.getName() + '#' +
methodName +
getParamsTypesString(clazzes) +
"#bestmatch";
method = mMethodMap.get(fullName);
if (method == null) {
method = objClass.getMethod(methodName, clazzes);
method.setAccessible(true);
mMethodMap.put(fullName, method);
}
}
return method.invoke(obj, params);
}
}
private long getByteArrayInVirtualMem(Image yuvImage, int width, int height, int stride, long vMemAdd) {
ByteBuffer yBuf = yuvImage.getPlanes()[0].getBuffer();
ByteBuffer vuBuf = yuvImage.getPlanes()[2].getBuffer();
if (yBuf.isDirect() && vuBuf.isDirect()) {
try {
//long vMemAdd = VirtualMemoryUtil.createVirtualMemory(filePath, vuBuf.remaining() + yBuf.remaining() + 10 * 1024);
long yBufferAddress = (long) ReflectHelper.getField(yBuf, "address");
long vuBufferAddress = (long) ReflectHelper.getField(vuBuf, "address");
int totalSize = yBuf.remaining() + vuBuf.remaining();
long yuvBufAddress = -1;
yuvBufAddress = VirtualMemoryUtil.virtualMemoryMalloc(vMemAdd, totalSize);
if (stride == width) {
boolean canRead = (boolean) ReflectHelper.invoke(yBuf, "isAccessible", null);
if (canRead) {
VirtualMemoryUtil.copyNativeDataBlockToVMem(yuvBufAddress, yBufferAddress, yBuf.remaining());
} else {
yuvImage.close();
return -1;
}
canRead = (boolean) ReflectHelper.invoke(vuBuf, "isAccessible", null);
if (canRead) {
VirtualMemoryUtil.copyNativeDataBlockToVMem(yuvBufAddress + yBuf.remaining(), vuBufferAddress, vuBuf.remaining());
} else {
yuvImage.close();
return -1;
}
yuvImage.close();
return yuvBufAddress;
} else {
for (int i = 0; i < height && canRead; i++) {
VirtualMemoryUtil.copyNativeDataBlockToVMem(yuvBufAddress + width * i,
yBufferAddress + stride * i, width);
canRead = (boolean) ReflectHelper.invoke(yBuf, "isAccessible", null);
}
long area = width * height;
canRead = (boolean) ReflectHelper.invoke(vuBuf, "isAccessible", null);
for (int i = 0; i < height / 2 && canRead; i++) {
VirtualMemoryUtil.copyNativeDataBlockToVMem(yuvBufAddress + area + width * i,
vuBufferAddress + stride * i, width);
canRead = (boolean) ReflectHelper.invoke(vuBuf, "isAccessible", null);
}
yuvImage.close();
return yuvBufAddress;
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
yuvImage.close();
return -1;
}
整个把image数据持久化的过程中除了image数据本身,没有增加一点JVM内存占用。
通过这样的方法,可以把Image中的数据快速转移到外存的一个文件中,并得到外存地址。在需要使用的时候可以通过我的工具类中的方法
VirtualMemoryUtil.copyVMemDataToByteArray
即可通过传入地址把数据重新从外存中写入到byte数组中,更能直接在native中操纵该地址的数据实现不占物理内存的image数据操作,例如加水印等图像处理,但受限于外存速度远远小于内存,请不要在时间要求高的场合下使用该技巧。
另外必须提及一个风险,因为address中的内存内容可能被修改,所以可能读到脏数据或者读写时被free掉的话会引起SEGV_MAPPER,需要慎重使用。