在安卓开发中把Image的ByteBuffer数据通过虚拟内存工具进行复制,以节约实际内存的做法

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,需要慎重使用。

你可能感兴趣的:(安卓开发)