Ошибка CameraX: уменьшение счетчика использования происходит чаще, чем увеличение - PullRequest
0 голосов
/ 07 апреля 2020

Я использую CameraX 1.0.0-beta01, чтобы делать снимки из службы. Я использую ImageCapture и ImageAnalysis - то есть без предварительного просмотра.

Я использую LifeCycleOwner и звоню

mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.STARTED);

, когда я запускаю свой сервис, и mLifecycleRegistry.markState(Lifecycle.State.DESTROYED), когда я заканчиваю sh, принимая pictures.

Я получаю от пользователей следующую ошибку, не указав c для какой-либо android версии или устройства.

Fatal Exception: java.lang.IllegalStateException: Decrementing use count occurs more times than incrementing
       at androidx.camera.core.impl.DeferrableSurface.decrementUseCount(DeferrableSurface.java:256)
       at androidx.camera.core.impl.DeferrableSurfaces.decrementAll(DeferrableSurfaces.java:173)
       at androidx.camera.camera2.internal.CaptureSession.clearConfiguredSurfaces(CaptureSession.java:539)
       at androidx.camera.camera2.internal.CaptureSession$StateCallback.onClosed(CaptureSession.java:928)
       at androidx.camera.camera2.internal.CaptureSession.forceClose(CaptureSession.java:533)
       at androidx.camera.camera2.internal.Camera2CameraImpl$StateCallback.onDisconnected(Camera2CameraImpl.java:1210)
       at androidx.camera.camera2.internal.CameraDeviceStateCallbacks$ComboDeviceStateCallback.onDisconnected(CameraDeviceStateCallbacks.java:112)
       at androidx.camera.camera2.internal.compat.CameraDeviceCompat$StateCallbackExecutorWrapper$2.run(CameraDeviceCompat.java:121)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.os.HandlerThread.run(HandlerThread.java:61)

Я не смог воспроизвести ошибку самостоятельно. Что может вызвать это?


Обновление:

Я нашел источник для DeferrableSurface. java, это происходит здесь:


    /**
     * Decrements the use count.
     *
     * <p>If this causes the use count to go to zero and the surface has been closed, this will
     * complete the future returned by {@link #getTerminationFuture()}.
     */
    public void decrementUseCount() {
        // If this gets set, then the surface will terminate
        CallbackToFutureAdapter.Completer<Void> terminationCompleter = null;
        synchronized (mLock) {
            if (mUseCount == 0) {
                throw new IllegalStateException("Decrementing use count occurs more times than "
                        + "incrementing");
            }

            mUseCount--;
            if (mUseCount == 0 && mClosed) {
                terminationCompleter = mTerminationCompleter;
                mTerminationCompleter = null;
            }

            // ...

, который вызывается из DeferrableSurfaces. java:


    /**
     * Decrements the usage counts of every surface in the provided list.
     *
     * @param surfaceList The list of surfaces whose usage count should be decremented.
     */
    public static void decrementAll(@NonNull List<DeferrableSurface> surfaceList) {
        for (DeferrableSurface surface : surfaceList) {
            surface.decrementUseCount();
        }
    }

, который, как я полагаю, вызывается, когда я отмечаю текущее состояние как DESTROYED. Соблазн сказать, что это похоже на ошибку. Мысли?


Обновление 2: Мой код урезан до того, что релевантно:

public class MyService extends Service implements LifecycleOwner{

    private LifecycleRegistry mLifecycleRegistry;
    public static boolean serviceRunning = false;
    private boolean isCameraOff = true;
    private ImageCapture imageCapture;
    private int pendingImages = 0;

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }

    /*  called from activity when we should take a picture.
        can be called several times, and we therefore increment an
        int, keeping track of how many pictures should be taken. */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        serviceRunning = true;

        // Increment pending pictures
        pendingImages++;

        // if camera is not in use, setup camera and take picture, otherwise incrementing pendingImages var is enough
        if(isCameraOff){
            isCameraOff = false;
            cameraSetup(); // takePicture called from the end of setup
        }

        return START_NOT_STICKY;
    }

    private void cameraSetup(){

        // lifecycle for camera
        mLifecycleRegistry = new LifecycleRegistry(this);
        mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);

        ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this);
        cameraProviderFuture.addListener(() -> {

            try {

                // Camera provider is now guaranteed to be available
                ProcessCameraProvider cameraProvider = (ProcessCameraProvider)cameraProviderFuture.get();

                // Dummy Analyzer
                ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                        .build();

                // empty analyzer, for the time
                imageAnalysis.setAnalyzer(executor, ImageProxy::close);

                // ImageCapture
                imageCapture = new ImageCapture.Builder()
                        .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                        .setTargetResolution(new Size(900,1200))
                        .build();

                // Select front facing camera
                CameraSelector cameraSelector = new CameraSelector.Builder()
                        .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
                        .build();

                // Bind Camera to lifecycle
                cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, imageAnalysis);

                // Give camera some time to open, 500 ms should do, before proceeding
                Utils.sleepThread(500);

                // take picture
                takePicture();

            } catch (InterruptedException | ExecutionException e) {
                // Currently no exceptions thrown. cameraProviderFuture.get() should
                // not block since the listener is being called, so no need to
                // handle InterruptedException.
            }
        }, ContextCompat.getMainExecutor(this));
    }

    private Executor executor = Executors.newSingleThreadExecutor();
    private void takePicture(){

        // Create new file
        File file = new File(Utils.createFilePath(context, getApplication()));

        // Create Meta data
        ImageCapture.Metadata meta = new ImageCapture.Metadata();
        meta.setReversedHorizontal(false); // don't mirror photos

        // Collect file options
        ImageCapture.OutputFileOptions outputFileOptions =
                new ImageCapture.OutputFileOptions.Builder(file)
                        .setMetadata(meta)
                        .build();

        // Take picture
        imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback(){

            @Override
            public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {

                // got picture

                // Decrement the number of pictures we need to take
                pendingImages--;

                // take another image if pending
                if(pendingImages > 0){
                    takePicture();
                }else{
                    closeSession();
                }

            }

            @Override
            public void onError(@NonNull ImageCaptureException exception) {
                exception.printStackTrace();
                closeSession();
            }
        });
    }

    private void closeSession(){

        // tell lifecycle and thus camera to close (has to be called from main thread)
        // closeSession is called from the camera callback and is not on main thread.
        new Handler(context.getMainLooper()).post(() -> {
            mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
        });

        // mark camera as off
        isCameraOff = true;

        stopSelf(); // stop service
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        serviceRunning = false;
    }

    // Service Stuff

    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        public MyService getService() {
            return MyService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

}
...