Android cameraSource.stop() causing app to freeze

2020-08-13 08:01发布

问题:

I am building an app that has a qr scanner using the google vision api. I am having trouble stopping the camera after the qr code is read. the flow is MainActivity -> QrActivity once the qr-code received a detection the app should return to the main activity.

If i do not call cameraSource.release() it works fine but the device heats up a lot and has a significant impact on battery drain. however if i release the camera source the mainActivity becomes un-responsive and the app will crash.

Why is it becoming unresponsive? and where is the correct place to release the camera source?

QrActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_qr);
    cancelBtn = (Button) findViewById(R.id.cancel_button);
    cancelBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
    });

    new QrReader(this);
}

QrReader Class

public class QrReader {

    private static final String TAG = "QrReader";

    private SurfaceView cameraView;
    private TextView barcodeInfo;
    private BarcodeDetector barcodeDetector;
    private CameraSource cameraSource;
    private Activity mActivity;
    private AccessPointCredentials barCodeData;

    public QrReader(Activity activity) {
        this.mActivity = activity;

        cameraView = (SurfaceView) mActivity.findViewById(R.id.camera_view);
        barcodeInfo = (TextView) mActivity.findViewById(R.id.code_info);

        barcodeDetector =
                new BarcodeDetector.Builder(mActivity)
                        .setBarcodeFormats(Barcode.QR_CODE)
                        .build();

        cameraSource = new CameraSource
                .Builder(mActivity, barcodeDetector)
                .setAutoFocusEnabled(true)
                .build();

        cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {

            @Override
            public void surfaceCreated(SurfaceHolder holder) {

                cameraSource = new CameraSource
                        .Builder(mActivity, barcodeDetector)
                        .setAutoFocusEnabled(true)
                        .setFacing(0)
                        .build();
                try {                  
                    cameraSource.start(cameraView.getHolder());

                } catch (Exception ioe) {
                    ioe.printStackTrace();
                }
            }

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

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                  // Log.i(TAG, "surfaceDestroyed: stopping camera Source");

                  // cameraSource.release();
            }
        });

        barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
            @Override
            public void release() {
                Log.i(TAG, "release: ");
            }

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

                if (barCodes.size() != 0) {

                    Log.i(TAG, "received a Barcode");
                    barcodeInfo.post(new Runnable() {    // Use the post method of the TextView
                        public void run() {
                            barcodeInfo.setText(barCodes.valueAt(0).displayValue);

                        }
                    });
                    Gson g = new Gson();
                    try {
                        barCodeData = g.fromJson(barCodes.valueAt(0).rawValue, AccessPointCredentials.class);
                    } catch (Exception e) {
                        barCodeData = new AccessPointCredentials();
                        barCodeData.setSsid(barCodes.valueAt(0).rawValue);
                        barCodeData.setPass(null);
                        e.printStackTrace();
                    }

                    connectToWifi(barCodeData);

                    // CameraSource.release causes app to freeze

                    // cameraSource.release();
                }
            }
        });
    }

    private void connectToWifi(final AccessPointCredentials credentials) {

                //wificonnect code

    }

}

回答1:

it's been 3 months but I just stumbled on the same problem and figure it out. The code inside the receiveDetections method is running on a different thread so if you want to do something that needs the ui thread you need to post it from a handler:

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
     @Override
     public void run() {
         cameraSource.release();
     }
});


回答2:

heyo, any update? I just encountered a similar issue, I try to deal with those release methods, but don't work. I only found a quick fix/ workaround. Would like to have a elegant solution.

My case is, an app user can scan a QR-Code and then make payment.

MainActivity

  1. Drawer menu (https://github.com/mikepenz/MaterialDrawer)
  2. Fragment container(change fragments when users click on the drawer menu)

All my other views are Fragment/ListFragment

ScanMainController

  1. BarcodeDetector
  2. CameraSource
  3. SurfaceView

My scenario is like this, once I scanned the QR code, I push to another page for use to make payment. But I need to find a way to stop the QRCode scanner as I only want that receiveDetections to be called once only. In iOS, I used to set the delegate to nil which does the same work as the cameraSource.release(); in Android. But then, I just cant find a proper position to call the release() method.

If I proactively call the release(), then when uses want to navigate to other page through clicking the drawer menu, my FragmentTransaction commit will be fired by the whole app freezes. And then Android system will pop up tell me the app is not responding.

I quick fixed by adding a 'boolean' flag inside the 'receiveDetections' to guarantee I only push the view once. It works. But I am looking for an more elegant way and 'proper' way to do it.

Thanks