最近公司想要使用Camera2来替换线上的旧版相机,功能调研过程中发现Camera2在很多机器上都会有奇怪的内存泄漏,比较明显的时候可能有100M+,比较常见的是表现为内存释放不及时(上涨一段时间后会有一次下降),这种现象在旧版Camera时是不存在的,内存图如下。
使用Android studio抓取内存Profile后如下图所示,未释放内存主要是由native方法allocate_camera_metadata所分配,追溯堆栈到java层应该为CameraMetaDataNative.set方法,而CameraMetaDataNative为CaptureResult的成员变量,通过跟踪源码以及打印onCaptureCompleted方法所返回的TotalCaptureResult可知该对象每帧hash都不一样,推测是每帧都在创建一个新的对象然后由垃圾回收器来自动回收。但是由于该java对象占用内存对象较小,所以较长时间都不会触发gc,而由于是在finalize()方法中释放native资源所以导致了native资源的释放不及时。
上面的图文字可能比较小看不清,所以又截了一张详细堆栈图。
解决方案:要想解决该问题需要做的就是在TotalCaptureResult不需要使用后手动调用释放接口。
public class BugFix {
static Field sTargetField;
static Method sTargetMethod;
public static void recycle(TotalCaptureResult tcr) {
try {
if (sTargetField == null) {
sTargetField = tcr.getClass().getSuperclass().getDeclaredField("mResults");
sTargetField.setAccessible(true);
}
if (sTargetMethod == null) {
Class clazz = Class.forName("android.hardware.camera2.impl.CameraMetadataNative");
Method[] methodList = clazz.getDeclaredMethods();
Method close = null;
Method finalize = null;
for(Method m: methodList) {
if (m.getName().contains("close")) { // 本地测试发现部分机器(Redmi k70)无法找到此方法,只能调用finalize
close = m;
break;
} else if (m.getName().contains("finalize")) {
finalize = m;
}
}
sTargetMethod = close == null? finalize: close;
if (sTargetMethod == null) {
return;
}
sTargetMethod.setAccessible(true);
}
sTargetMethod.invoke(sTargetField.get(tcr));
} catch (NoSuchFieldException | ClassNotFoundException | InvocationTargetException |
IllegalAccessException e) {
e.printStackTrace();
}
}
}
效果确认:通过解决前后数据对比,在fps相近情况下修复后在cpu/内存/cpu温度方面都有所优化。