Я использую 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;
}
}