onPictureTaken 方法中的位图不是垃圾收集的

Posted

技术标签:

【中文标题】onPictureTaken 方法中的位图不是垃圾收集的【英文标题】:Bitmap in onPictureTaken method is not garbage collected 【发布时间】:2018-04-11 13:14:24 【问题描述】:

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

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

这里有一些关于回收位图的线程,但它们都不起作用。

    Recycling Bitmap does not free memory Memory usage does not decrease even I recycle bitmaps

这是我与 SurfaceView 一起使用的代码,其图像为 4160,高度为 3120,它返回一个大约 50mb 的位图。

相机活动

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.00 秒手动 GC。我让应用程序保持打开状态,7 分钟后位图仍未被垃圾收集。我还检查了CameraKit app 和CameraView,在拍照后它们也没有被 GC'ed。有没有办法手动从Bitmap申请内存。

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

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

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

【问题讨论】:

【参考方案1】:

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

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

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

【讨论】:

以上是关于onPictureTaken 方法中的位图不是垃圾收集的的主要内容,如果未能解决你的问题,请参考以下文章

从 onPictureTaken 结果裁剪圆形图像

在 onPictureTaken 之后旋转 JPEG 的字节数组

onPictureTaken byte[] 某些设备中的小尺寸

Android camera , onPictureTaken(byte[] imgData, Camera camera) 方法和 PictureCallback 从未调用

Android:三星设备自动旋转图像

onPictureTaken 和 onPreviewFrame 的数据字节数组大小不同