onPictureTaken 方法中的位图未被垃圾回收

Bitmap in onPictureTaken method is not garbage collected

我正在使用 Camera1 Api 来测试 SurfaceView、TextureView 的一些功能。

使用 bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); 创建的位图永远不会被回收,即使调用了 bitmap.recycle()System.gc() 也永远不会收回内存。

这里有一些关于回收位图的线程,但其中 none 个有效。

  1. Recycling Bitmap does not free memory

这是我在 SurfaceView 中使用的代码,图像大小为 4160,高度为 3120,returns 一个大约 50mb 的位图。

相机Activity

public class CameraActivity extends Activity {
    private static final String SAVE_DIR = "Folder";
    private Camera mCamera;
    private FrameLayout preview;
    private CameraPreview mPreview;

    public static final int MEDIA_TYPE_IMAGE = 1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create an instance of Camera
        if (checkCameraHardware(this)) {
            mCamera = getCameraInstance();
        }

        if (mCamera == null) {
            Toast.makeText(this, "Camera null ", Toast.LENGTH_SHORT).show();
            return;
        }
        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);

        Button captureButton = (Button) findViewById(R.id.button_capture);
        captureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // get an image from the camera
                mCamera.takePicture(null, null, mPicture);

            }
        });
    }

    /**
     * Check if this device has a camera
     */
    private boolean checkCameraHardware(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }

    /**
     * A safe way to get an instance of the Camera object.
     */
    public Camera getCameraInstance() {
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
            setCameraDisplayOrientation(this, CameraInfo.CAMERA_FACING_BACK, c);
        } catch (Exception e) {
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }

    public void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) {
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int orientation = getResources().getConfiguration().orientation;

        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360; // compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
        Camera.Parameters params = camera.getParameters();
        params.setRotation(90);
        camera.setParameters(params);
    }

    private PictureCallback mPicture = new PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            long startTime = System.currentTimeMillis();

            File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
            FileOutputStream fos = null;
            Bitmap bitmap = null;

            if (pictureFile == null) {
                return;
            }

            try {
                fos = new FileOutputStream(pictureFile);
                bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                bitmap.compress(CompressFormat.JPEG, 100, fos);

            } catch (FileNotFoundException e) {
                System.out.println("CameraActivityonPictureTaken() File not found: " + e.getMessage());
            } finally {
                try {
                    if (fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                fos = null;
                pictureFile = null;

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    System.out.println("CameraActivity onPictureTaken() Bitmap recycled: " + bitmap.isRecycled() + ", size: " + bitmap.getAllocationByteCount() / (1024) + "kb");
                }
                bitmap.recycle();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    System.out.println("CameraSurfaceTextureListener onPictureTaken() Bitmap recycled: " + bitmap.isRecycled() + ", size: " + bitmap.getAllocationByteCount() / (1024) + "kb");
                }


                bitmap = null;
                System.gc();

                long finishTime = System.currentTimeMillis();
                System.out.println("CameraActivity onPictureTaken() TIME: " + (finishTime - startTime) + "ms");
                mPreview.refreshPreview();
            }
        }
    };

    /**
     * Create a File for saving an image or video
     */
    private File getOutputMediaFile(int type) {
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.

        File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
        } else {
            return null;
        }

        return mediaFile;
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseCamera(); // release the camera immediately on pause event
        if (preview != null && mPreview != null) {
            preview.removeView(mPreview);
            mPreview = null;
        }
    }

    @Override
    protected void onResume() {
        super.onResume();


        // Create an instance of Camera
        if (checkCameraHardware(this)) {
            if (mCamera == null) {
                mCamera = getCameraInstance();
                System.out.println("onResume() mCamera: " + mCamera);
                if (mPreview == null) {
                    // Create our Preview view and set it as the content of our
                    // activity.
                    mPreview = new CameraPreview(this, mCamera);
                    System.out.println("onResume() preview child count: " + preview.getChildCount());
                    preview.removeAllViews();
                    preview.addView(mPreview);
                } else {
                    mPreview.refreshPreview();
                }
            }
        }
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.release(); // release the camera for other applications
            mCamera = null;
        }
    }
}

这是图像保存过程的内存分析器。应用在 运行 相机预览时使用 16 mb RAM。当我触摸按钮保存图像时,它在保存时上升到 110mb,它在大约 25.00 秒后开始,我没有使用线程来直观地检查它,保存时应用程序冻结,然后它减少到 75mb 并保持在这个水平如果我不使用主页按钮手动 GC 或暂停应用程序。我在 43.00s 手动进行了 GC。我让应用程序保持打开状态,但 Bitmap 在 7 分钟后仍未被垃圾回收。我还检查了 CameraKit app and CameraView,它们在拍照后也没有被 GC。有没有办法手动从Bitmap中申请内存。

如何使用新的 Memory Profiler 检查 Activity 是否泄漏并创建 .hprof 文件?

我也测试了 Camera2 Api 代码。这是此代码的内存配置文件。

虚线是具有锯齿模式的对象分配,对象在边缘被 GC,但所有内存都是稳定的并且不遵循对象分配模式。这怎么可能?

我之前也发现了这个问题,我不知道为什么会这样,但我认为你不应该真的担心它。

正如recycle方法的文档所说,不保证调用此方法后立即释放位图资源。

为了解释为什么您不应该担心,当您尝试在内存中分配新图像时,内存将被释放。尝试拍摄一张新照片并检查内存,不会添加内存。或者更好的是,尝试拍摄 5 张图片,您会发现它不会占用 5 张图像的内存,因为创建新位图时会释放内存。