查看压测曲线图,其实内存的情况整体看还好。
但是不能看total,要看实际的走势, 1 5 9 代表测试第几轮,可以发现在前几轮测试中一直在增长, 突然下去是因为进程被kill了
根据压测步骤挨个筛选泄漏的地方
结果:
操作步骤: 打开 camera, 切换到前置虚化模式, 静止, 查看内存一直在增长且无回落
lilei@rj-lilei0 MINGW64 /
$ adb shell dumpsys meminfo com.sec.android.app.camera | grep "Graphics\|Java\|Native\|TOTAL\|EGL mtrack"
Native Heap 49010 48988 0 38 87248 84507 2740
EGL mtrack 11332 11332 0 0
TOTAL 187808 170151 12748 62 99741 90856 8884
Java Heap: 8544
Native Heap: 48988
Graphics: 31771
TOTAL: 187808 TOTAL SWAP PSS: 62
lilei@rj-lilei0 MINGW64 /
$ adb shell dumpsys meminfo com.sec.android.app.camera | grep "Graphics\|Java\|Native\|TOTAL\|EGL mtrack"
Native Heap 52458 52436 0 38 90464 87711 2752
EGL mtrack 11696 11696 0 0
TOTAL 192300 174635 12748 62 103555 94658 8896
Java Heap: 9204
Native Heap: 52436
Graphics: 32135
TOTAL: 192300 TOTAL SWAP PSS: 62
lilei@rj-lilei0 MINGW64 /
$ adb shell dumpsys meminfo com.sec.android.app.camera | grep "Graphics\|Java\|Native\|TOTAL\|EGL mtrack"
Native Heap 54434 54412 0 38 92400 89375 3024
EGL mtrack 11471 11471 0 0
TOTAL 194271 176602 12748 62 105834 96665 9168
Java Heap: 9424
Native Heap: 54412
Graphics: 31910
TOTAL: 194271 TOTAL SWAP PSS: 62
lilei@rj-lilei0 MINGW64 /
$ adb shell dumpsys meminfo com.sec.android.app.camera | grep "Graphics\|Java\|Native\|TOTAL\|EGL mtrack"
Native Heap 57518 57496 0 38 95392 92570 2821
EGL mtrack 11701 11701 0 0
TOTAL 198324 180649 12748 62 109442 100476 8965
Java Heap: 10144
Native Heap: 57496
Graphics: 32145
TOTAL: 198324 TOTAL SWAP PSS: 62
可以看到的是, java 和 native 一起增长,但是 java增长较小, native增长较快(每次2M)
1. 因为native增长较快的关系,所以觉得是算法泄漏, 就直接从 detect 算法入手,
private final ImageReader.OnImageAvailableListener mPreviewOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
将检测算法全部注释掉,查看是否还增长
其实中间是有个乌龙的,
本来注释掉, 发现不增长了,
最后弄了一早上挨个排查, 最后发现又增长。
原因是: 最后排查泄漏的地方在 faceView中的逻辑, 如果检测到人脸就不会增长了,没有检测到人脸就会一直增长。
所以测试手法和场景很重要.
}
2. 预览一直增加的话,既然 detect 算法没有一直增加,那还能有什么地方用到算法呢?
一直检测的就剩下人脸的逻辑(里面都是java逻辑),说是试试?结果果然是这里
@Override
public void onCaptureCompleted(CameraCaptureSession session,
CaptureRequest request, TotalCaptureResult result) {
int id = (int) result.getRequest().getTag();
if (id == getMainCameraId()) {
updateFocusStateChange(result);
Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
//------- 结果注释掉下面的 faceView 的逻辑,内存果然正常了 --------
if (faces != null && isBsgcDetecionOn()) {
updateFaceView(faces, getBsgcInfo(result, faces.length));
} else {
updateFaceView(faces, null);
}
就很奇怪, 这里都是java的一些逻辑,为何会让native 增加??
根据FaceView的逻辑代码排查, 发现只有这个模式, 会显示一个 toast提示(太近,太远,效果正常,find a face 等)
3. 定位到出问题的点: setBlurToastShow()这个方法导致的
public void setBlurToastShow() {
...
mRotateTextTips = RotateTextSingleBokehTips.makeText(mActivity, R.string.pref_camera2_show_singleBokeh_title,Toast.LENGTH_SHORT,true);
mRotateTextTips.showSingleBokeh();
}
Public class RotateTextSingleBokehTips {
private static HashSet<RotateLayout> mToasts = new HashSet<RotateLayout>();
private RotateTextSingleBokehTips(Activity activity, int duration) {
mLayoutRoot = (ViewGroup) activity.getWindow().getDecorView();
LayoutInflater inflater = activity.getLayoutInflater();
View v = inflater.inflate(R.layout.rotate_text_singlebokeh_tips, mLayoutRoot);
mToast = (RotateLayout) v.findViewById(R.id.rotate_singlebokeh_text);
mToast.setOrientation(mOrientation, false);
mHandler = new Handler();
mDuration = duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
}
public static RotateTextSingleBokehTips makeText(Activity activity, int textResourceId, int duration, boolean showAtBottom) {
RotateTextSingleBokehTips rt = new RotateTextSingleBokehTips(activity, textResourceId, duration);
rt.setLayoutBootm(showAtBottom);
return rt;
}
.......
.......
public void showSingleBokeh() {
setSingleBokehTextViewLayout(mToast);
mToast.setTag(SHOW_AT_BOTTOM);
mToasts.add(mToast);
mToast.setVisibility(View.VISIBLE);
}
public void removeSingleBokeh(){
CameraUtil.fadeOut(mToast);
mLayoutRoot.removeView(mToast);
mToasts.remove(mToast);
mToast = null;
}
}
仔细看一下 RotateTextSingleBokehTips 这个类的逻辑
里面有一个 静态的 HashSet mToast , 因为每次 RotateTextSingleBokehTips.makeText 的逻辑是每次new 一个新的View
然后add 进 mToast 中, 所以就会一直增加。
但是这个也就是个 java对象泄漏吧, 为啥会导致native 泄漏?
解决的方案2选1:
1. 每次调用 showSingleBokeh 之前, 都掉用 removeSingleBokeh , 确保清空了 HashSet 对每次new的View的持有,导致无法释放
(这个方案就是我为什么之前发现一会增加,一会不增加, 原来检测到人脸的时候, 弹出的toast 会先remove掉,再show)
public void setFaces(Face[] faces, ExtendedFace[] extendedFaces) {
if (mPause) return;
...
mFaces = faces;
mExFaces = extendedFaces;
//有人脸时
if (!mBlocked && (mFaces != null) && (mFaces.length > 0) && mCameraBound != null) {
invalidate();
if(mCurrentModule instanceof SingleBokehCaptureModule){
SingleBokehCaptureModule module = (SingleBokehCaptureModule)mCurrentModule;
//-------------------------------所以这种情况下不会泄漏-----------------------------
//会先 remove之前的View
module.setBlurToastDispear();
//再显示自定的toast
module.setBlurShow(true);
if(mRectForSingleBokeh != null && mRectForSingleBokeh.width() > 0) {
int width = (int) mRectForSingleBokeh.width();
int height = (int) mRectForSingleBokeh.height();
if (width < 100 || height < 100) {
module.showBokehStatusMessage(SingleBokehCaptureModule.MSG_TOO_FAR);
} else if (width > 500 || height > 500) {
module.showBokehStatusMessage(SingleBokehCaptureModule.MSG_TOO_NEAR);
} else {
module.showBokehStatusMessage(SingleBokehCaptureModule.MSG_DEPTH_EFFECT_SUCCESS);
}
}
}
//无人脸时
}else {
if (mCurrentModule instanceof SingleBokehCaptureModule){
SingleBokehCaptureModule module = (SingleBokehCaptureModule)mCurrentModule;
//-------------------------------会泄漏-----------------------------
//显示自定的toast
module.setBlurToastShow();
module.setBlurShow(false);
module.showBokehStatusMessage(SingleBokehCaptureModule.MSG_SUBJECT_NOT_FOUND);
}
}
}
2. 不重复创建,只创建一个,切换模式时 remove
public void setBlurToastShow() {
...
if(mRotateTextTips == null){
mRotateTextTips = RotateTextSingleBokehTips.makeText(mActivity, R.string.pref_camera2_show_singleBokeh_title,Toast.LENGTH_SHORT,true);
}
mRotateTextTips.showSingleBokeh();
}
最后切换模式的地方 onDestory ,再清楚 HashSet
4. 最后最后, native 的增加的问题, 为何 java对象会导致 native持续增加, 并且很快
dump了hprof文件,进行查看
可以看到2个泄漏的地方:
1. mToast 持有过多的 mToast(RotateLayout)
2. mToast(RotateLayout) 持有 decorView 导致 inputMethodManager 的 native泄漏
原因:mLayoutRoot = (ViewGroup) activity.getWindow().getDecorView();
//将 DecorView 传了进去, 持有 decorView 导致 inputMethodManager 的 native泄漏
View v = inflater.inflate(R.layout.rotate_text_singlebokeh_tips, mLayoutRoot)
//如果传 null, 经过试验, 则不会导致 native泄漏
View v = inflater.inflate(R.layout.rotate_text_singlebokeh_tips, null)