androidmemory-leaksandroid-vision

Android Memory Leak When Passing A Fragment


I have this code and Iam using the Barcode Google API Vision. When i open the Fragment and rotate the device many times 6 or more, i see in the Dump Heap that many instances remain in memory (see pic.) Even after i do a forced Garbage Collection they stay the same. In my code below i dont see any memory leaks.

Image is after GC enter image description here

The weird part is that some devices only show 1 instance of the classes after GC which is normal.

Emulator API 27  : NO MEMORY LEAKS
Samsung j500FN   : NO MEMORY LEAKS
Xiaomi mi8       : Memory Leak
Galaxy Tablet E  : Memory Leak

MainActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            Fragment sf = getSupportFragmentManager().findFragmentByTag("Scanner");
                transaction.add(R.id.root, new Scanner(), "Scanner");
                transaction.addToBackStack(null);
                transaction.commit(); 

        }
    });
}

Scanner

public class Scanner extends Fragment{

public SurfaceView cameraView;
public BarcodeDetector barcode;
public CameraSource cameraSource;
private SurfaceHolder.Callback cameraCallback;
private ActivityScanBinding mbinding;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    Log.d("ActivityScan","onCreate");
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

    Log.d("ActivityScan","onCreateView");

    mbinding = DataBindingUtil.inflate(inflater, R.layout.activity_scan, container, false);


    mbinding.getRoot().setOnTouchListener(new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            return true;
        }

    });
    cameraView = mbinding.getRoot().findViewById(R.id.cameraView);

    return mbinding.getRoot();
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    Scan();
}

@Override
public void onDestroy() {

    Log.d("ActivityScan","Destroyed");
    if(barcode!=null) {
        barcode.release();
        Log.d("barcode","Released");
    }
    if(cameraSource!=null) {
        cameraSource.release();
        Log.d("cameraSource ","Released");
    }
    if(cameraView!=null) {
        removeCameraViewCallback();
    }

    super.onDestroy();
}

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
}



public void Scan(){

    cameraView.setZOrderMediaOverlay(true);

    barcode = new BarcodeDetector.Builder(getActivity())
            .setBarcodeFormats(Barcode.QR_CODE)
            .build();

        if(!barcode.isOperational()){
            return;
        }


    cameraSource = new CameraSource.Builder(getActivity(), barcode)
            .setFacing(CameraSource.CAMERA_FACING_FRONT)
            .setRequestedFps(24)
            .setAutoFocusEnabled(true)
            .setRequestedPreviewSize(1920,1080)
            .build();

    cameraCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {

                if(ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
                    cameraSource.start(cameraView.getHolder());
                }
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            cameraSource.stop();
        }
    };

    cameraView.getHolder().addCallback(cameraCallback);

    barcode.setProcessor(new Detector.Processor<Barcode>() {

        @Override
        public void release() {}

        @Override
        public void receiveDetections(Detector.Detections<Barcode> detections) {
            final SparseArray<Barcode> barcodes =  detections.getDetectedItems();
            if(barcodes.size() > 0){

            }
        }
    });


}

public void removeCameraViewCallback(){
    cameraView.getHolder().removeCallback(cameraCallback);
}
}

Please see my code and let me know if there is a memory leak.

Leak Canary shows this: enter image description here enter image description here


Solution

  • Why you're releasing barcode and cameraSource in onDestroy method? According to this onDestroy() method could be skipped and not called. Maybe onStop() is more proper place to release resources? And acquire them in onStart() respectively.

    @Override
    public void onStop() {
    
        Log.d("ActivityScan","Destroyed");
        if(barcode!=null) {
            barcode.release();
            Log.d("barcode","Released");
        }
        if(cameraSource!=null) {
            cameraSource.release();
            Log.d("cameraSource ","Released");
        }
        if(cameraView!=null) {
            removeCameraViewCallback();
        }
    
        super.onStop();
    }
    

    Also, do not pass Activity when creating BarcodeDetector and CameraSource, and pass ApplicationContext if possible.