I’m using LeakCanary to scan for memory leaks in my app, and it appears the CameraX camera is leaking memory, specifically the ProcessCameraProvider. I do quite heavy image analysis, so launching and destroying my activity that runs the camera a couple times really chews through a lot of memory, that seems to never get released. Is there any fix for this planned?
these are my library versions
implementation "androidx.camera:camera-camera2:1.0.0-rc02"
implementation "androidx.camera:camera-lifecycle:1.0.0-rc02"
implementation "androidx.camera:camera-view:1.0.0-alpha21"
Here is the report from leak canary
GC Root: Local variable in native code
│
├─ android.os.HandlerThread instance
│ Leaking: NO (PathClassLoader↓ is not leaking)
│ Thread name: 'LeakCanary-Heap-Dump'
│ ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (ProcessCameraProvider↓ is not leaking and A ClassLoader is never leaking)
│ ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (ProcessCameraProvider↓ is not leaking)
│ ↓ Object[].[842]
├─ androidx.camera.lifecycle.ProcessCameraProvider class
│ Leaking: NO (a class is never leaking)
│ ↓ static ProcessCameraProvider.sAppInstance
│ ~~~~~~~~~~~~
├─ androidx.camera.lifecycle.ProcessCameraProvider instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 4188 objects
│ ↓ ProcessCameraProvider.mCameraX
│ ~~~~~~~~
├─ androidx.camera.core.CameraX instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 4176 objects
│ mAppContext instance of android.app.Application
│ ↓ CameraX.mCameraFactory
│ ~~~~~~~~~~~~~~
├─ androidx.camera.camera2.internal.Camera2CameraFactory instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3737 objects
│ ↓ Camera2CameraFactory.mCameraInfos
│ ~~~~~~~~~~~~
├─ java.util.HashMap instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3721 objects
│ ↓ HashMap.table
│ ~~~~~
├─ java.util.HashMap$Node[] array
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3720 objects
│ ↓ HashMap$Node[].[1]
│ ~~~
├─ java.util.HashMap$Node instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3506 objects
│ ↓ HashMap$Node.value
│ ~~~~~
├─ androidx.camera.camera2.internal.Camera2CameraInfoImpl instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3505 objects
│ ↓ Camera2CameraInfoImpl.mCamera2CameraControlImpl
│ ~~~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.camera.camera2.internal.Camera2CameraControlImpl instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3500 objects
│ ↓ Camera2CameraControlImpl.mControlUpdateCallback
│ ~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.camera.camera2.internal.Camera2CameraImpl$ControlUpdateListenerInternal instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3359 objects
│ ↓ Camera2CameraImpl$ControlUpdateListenerInternal.this$0
│ ~~~~~~
├─ androidx.camera.camera2.internal.Camera2CameraImpl instance
│ Leaking: UNKNOWN
│ Retaining 1.9 MB in 3358 objects
│ ↓ Camera2CameraImpl.mUseCaseAttachState
│ ~~~~~~~~~~~~~~~~~~~
├─ androidx.camera.core.impl.UseCaseAttachState instance
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 3262 objects
│ ↓ UseCaseAttachState.mAttachedUseCasesToInfoMap
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ java.util.HashMap instance
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 3261 objects
│ ↓ HashMap.table
│ ~~~~~
├─ java.util.HashMap$Node[] array
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 3259 objects
│ ↓ HashMap$Node[].[0]
│ ~~~
├─ java.util.HashMap$Node instance
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 2976 objects
│ ↓ HashMap$Node.value
│ ~~~~~
├─ androidx.camera.core.impl.UseCaseAttachState$UseCaseAttachInfo instance
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 2974 objects
│ ↓ UseCaseAttachState$UseCaseAttachInfo.mSessionConfig
│ ~~~~~~~~~~~~~~
├─ androidx.camera.core.impl.SessionConfig instance
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 2973 objects
│ ↓ SessionConfig.mErrorListeners
│ ~~~~~~~~~~~~~~~
├─ java.util.Collections$UnmodifiableRandomAccessList instance
│ Leaking: UNKNOWN
│ Retaining 2.9 kB in 122 objects
│ ↓ Collections$UnmodifiableCollection.c
│ ~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ Retaining 2.9 kB in 121 objects
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 2.9 kB in 120 objects
│ ↓ Object[].[0]
│ ~~~
├─ androidx.camera.core.-$$Lambda$Preview$WtQW2Zy9cGuDwxes3k4z9U6d1nc instance
│ Leaking: UNKNOWN
│ Retaining 2.9 kB in 119 objects
│ ↓ -$$Lambda$Preview$WtQW2Zy9cGuDwxes3k4z9U6d1nc.f$0
│ ~~~
├─ androidx.camera.core.Preview instance
│ Leaking: UNKNOWN
│ Retaining 841 B in 34 objects
│ ↓ Preview.mSurfaceProvider
│ ~~~~~~~~~~~~~~~~
├─ androidx.camera.view.PreviewView$1 instance
│ Leaking: UNKNOWN
│ Retaining 1.8 MB in 2790 objects
│ Anonymous class implementing androidx.camera.core.Preview$SurfaceProvider
│ ↓ PreviewView$1.this$0
│ ~~~~~~
├─ androidx.camera.view.PreviewView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 1.8 MB in 2789 objects
│ View is part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.cameraPreview
│ View.mWindowAttachCount = 1
│ mContext instance of com.mycompany.capture.presentation.camera.CameraActivity with mDestroyed = true
│ ↓ View.mContext
╰→ com.mycompany.capture.presentation.camera.CameraActivity instance
Leaking: YES (ObjectWatcher was watching this because com.idmission.capture.presentation.camera.CameraActivity
received Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 1.7 MB in 1305 objects
key = 0c1e1e7f-42d8-4617-8308-37342107f3d8
watchDurationMillis = 5129
retainedDurationMillis = 127
mApplication instance of android.app.Application
mBase instance of androidx.appcompat.view.ContextThemeWrapper
Here is my camera code:
internal class CameraXCamera(
override var cameraFrameAnalyzer: IntegratedCamera.CameraFrameAnalyzer<ImageProxy>? = null,
private val context: Context,
private val lifecycleOwner: LifecycleOwner,
private val cameraViewProvider: () -> PreviewView?,
) : IntegratedCamera<ImageProxy> {
private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private var imageCapture: ImageCapture? = null
private var preview: Preview? = null
private var imageAnalysis: ImageAnalysis? = null
private var cameraProvider: ProcessCameraProvider? = null
private var camera: Camera? = null
private var currentFacing = CameraSelector.DEFAULT_BACK_CAMERA
override fun aspectRatio() = Rational(4, 3)
override fun start(
startOrientation: DeviceRotationHelper.Orientation?,
mode: IntegratedCamera.Mode
) {
// important to use application context here! otherwise, leaks
val cameraProviderFuture = ProcessCameraProvider.getInstance(context.applicationContext)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
this.cameraProvider = cameraProvider
val cameraPreviewOrientation = cameraViewProvider()?.display?.rotation ?: 0
val preview = Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetRotation(cameraPreviewOrientation)
.build().apply {
val surfaceProvider = cameraViewProvider()?.surfaceProvider
logE("No surface provider found", NullPointerException())
setSurfaceProvider(surfaceProvider)
}
this.preview = preview
val cameraXStartOrientation = startOrientation?.toCameraXOrientation()
val captureType = if (DetectionConfig.maximizeCaptureQuality) {
CAPTURE_MODE_MAXIMIZE_QUALITY
} else {
CAPTURE_MODE_MINIMIZE_LATENCY
}
imageCapture = ImageCapture.Builder()
.setCaptureMode(captureType)
.setTargetRotation(cameraXStartOrientation ?: cameraPreviewOrientation)
.build()
val imageAnalyzer = cameraFrameAnalyzer?.let { analyzer ->
ImageAnalysis.Builder()
.setTargetResolution(Size(1600, 1200))
.setTargetRotation(cameraXStartOrientation ?: cameraPreviewOrientation)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build().apply {
setAnalyzer(cameraExecutor, GenericAnalyzer(analyzer))
}
}
this.imageAnalysis?.clearAnalyzer()
this.imageAnalysis = imageAnalyzer
val cameraSelector = currentFacing
try {
cameraProvider.unbindAll()
camera = when {
imageAnalyzer == null -> {
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview, imageCapture
)
}
mode == IntegratedCamera.Mode.MAX_ANALYSIS -> {
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview, imageAnalyzer
)
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageCapture)
}
else -> {
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview, imageCapture, imageAnalyzer
)
}
}
} catch (exc: Exception) {
logE("Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(context))
}
override fun pause() {
try {
cameraProvider?.unbind(preview, imageAnalysis)
} catch (e: Exception) {
logE("Unbinding failed")
}
}
@SuppressLint("RestrictedApi")
override fun stop() {
imageAnalysis?.clearAnalyzer()
cameraExecutor.shutdown()
try {
cameraProvider?.unbindAll()
cameraProvider?.shutdown()
} catch (e: Exception) {
logE("Unbinding failed")
}
}
override suspend fun focus(point: Point?) {
val previewView = cameraViewProvider() ?: return
val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
previewView.width.toFloat(), previewView.height.toFloat()
)
val centerWidth = previewView.width.toFloat() / 2
val centerHeight = previewView.height.toFloat() / 2
//create a point on the center of the view
val autoFocusPoint = if (point == null) {
factory.createPoint(centerWidth, centerHeight)
} else {
factory.createPoint(point.x.toFloat(), point.y.toFloat())
}
try {
val safeCamera = camera ?: return
safeCamera.cameraControl.startFocusAndMetering(
FocusMeteringAction.Builder(
autoFocusPoint,
FocusMeteringAction.FLAG_AF
).build()
).await()
} catch (e: CameraInfoUnavailableException) {
logW("Can't access camera info", e)
}
}
override suspend fun takePhoto(outputFile: File): Result<File, Exception> {
val timeout = IntegratedCamera.CAPTURE_TIMEOUT_MILLIS
return withTimeoutOrNull(timeout) {
takePhotoInternal(outputFile)
} ?: Result.Failure(
TimeoutException("Capture exceeded capture time limit of $timeout millis")
)
}
private suspend fun takePhotoInternal(outputFile: File): Result<File, Exception> {
// take a photo
}
private class GenericAnalyzer(
private val cameraFrameAnalyzer: IntegratedCamera.CameraFrameAnalyzer<ImageProxy>
) : ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) {
cameraFrameAnalyzer.analyze(image)
image.close()
}
}
}
And here is how my camera is initialized from an activity (this is an Activity in the below sample)
analysisRouter = AnalysisRouter(this).apply {
setLiveFaceFolder(
photoFileHelper.getCurrentFolder(
this@CameraActivity,
cameraViewModel.folderName
)
)
}
integratedCamera = CameraXCamera(analysisRouter, this, this) { cameraPreview }
implementation "androidx.camera:camera-camera2:1.0.0-rc04"
implementation "androidx.camera:camera-lifecycle:1.0.0-rc04"
implementation "androidx.camera:camera-view:1.0.0-alpha23"
override fun onCreate(savedInstanceState: Bundle?) {
...
if (allPermissionsGranted()) {
integratedCamera.start(startOrientation)
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
...
}
override fun onResume() {
...
// necessary to restart camera after launching new activity
integratedCamera.start(lastOrientation())
...
}
override fun onDestroy() {
integratedCamera.stop()
}
2021-04-03 09:56:42.863 27547-27547/com.mycompany.mycompanycapture D/SurfaceViewImpl: Surface destroyed.
2021-04-03 09:56:42.865 27547-27547/com.mycompany.mycompanycapture D/SurfaceViewImpl: Surface invalidated androidx.camera.core.SurfaceRequest@9811a02
2021-04-03 09:56:42.866 27547-27547/com.mycompany.mycompanycapture D/DeferrableSurface: surface closed, useCount=1 closed=true androidx.camera.core.SurfaceRequest$2@170df13
2021-04-03 09:56:42.932 27547-28246/com.mycompany.mycompanycapture D/UseCaseAttachState: Active and attached use case: [androidx.camera.core.ImageAnalysis-22eb4b27-75be-41cb-ab39-08d9301680a1249474166, androidx.camera.core.ImageCapture-c4aa3746-f446-4b9c-9d21-64ee542a972f132645649, androidx.camera.core.Preview-16a1c5d5-3fcc-4462-9d65-49050b2b7ed8186283832] for camera: 1
2021-04-03 09:56:42.938 27547-27547/com.mycompany.mycompanycapture D/DeferrableSurface: surface closed, useCount=1 closed=true androidx.camera.core.impl.ImmediateSurface@bead44e
2021-04-03 09:56:42.940 27547-27547/com.mycompany.mycompanycapture D/DeferrableSurface: surface closed, useCount=1 closed=true androidx.camera.core.impl.ImmediateSurface@20b8b05
2021-04-03 09:56:42.943 27547-27547/com.mycompany.mycompanycapture D/LeakCanary: Watching instance of androidx.fragment.app.FragmentManagerViewModel (androidx.fragment.app.FragmentManagerViewModel received ViewModel#onCleared() callback) with key 9d13724f-50c2-47e0-81af-6a5804cdfa64
2021-04-03 09:56:42.944 27547-27547/com.mycompany.mycompanycapture D/LeakCanary: Watching instance of com.mycompany.capture.presentation.camera.viewmodels.CameraViewModel (com.mycompany.capture.presentation.camera.viewmodels.CameraViewModel received ViewModel#onCleared() callback) with key 547c52de-8d90-47b2-b032-45d6ac6082e3
2021-04-03 09:56:42.944 27547-27547/com.mycompany.mycompanycapture D/LeakCanary: Watching instance of leakcanary.internal.ViewModelClearedWatcher (leakcanary.internal.ViewModelClearedWatcher received ViewModel#onCleared() callback) with key ce3485b6-3f3e-45ed-ad12-58dc31648a0c
2021-04-03 09:56:42.945 27547-27547/com.mycompany.mycompanycapture D/LeakCanary: Watching instance of androidx.lifecycle.ReportFragment (androidx.lifecycle.ReportFragment received Fragment#onDestroy() callback) with key ced930e3-1c87-4519-8034-960ef2a89564
2021-04-03 09:56:42.946 27547-27547/com.mycompany.mycompanycapture D/LeakCanary: Watching instance of com.mycompany.capture.presentation.camera.CameraActivity (com.mycompany.capture.presentation.camera.CameraActivity received Activity#onDestroy() callback) with key fe9ac719-fba7-4f16-9f33-9d463112d926
2021-04-03 09:56:42.948 27547-28246/com.mycompany.mycompanycapture D/CaptureSession: Attempting to submit CaptureRequest after setting
2021-04-03 09:56:42.949 27547-28246/com.mycompany.mycompanycapture D/CaptureSession: Issuing request for session.
2021-04-03 09:56:42.959 27547-28238/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@fba7539[id=0]} Transitioning camera internal state: INITIALIZED --> RELEASING
2021-04-03 09:56:42.977 27547-27547/com.mycompany.mycompanycapture D/LeakCanary: Watching instance of com.android.internal.policy.DecorView (com.android.internal.policy.DecorView received View#onDetachedFromWindow() callback) with key a10cfa36-35cf-47be-8a66-f4ddcbb90c0f
2021-04-03 09:56:42.984 27547-28238/com.mycompany.mycompanycapture D/CameraStateRegistry: Recalculating open cameras:
Camera State
-------------------------------------------------------------------
Camera@fba7539[id=0] RELEASING
Camera@9e98d8a[id=1] OPEN
-------------------------------------------------------------------
Open count: 2 (Max allowed: 1)
2021-04-03 09:56:42.993 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Use cases [Preview:androidx.camera.core.Preview-16a1c5d5-3fcc-4462-9d65-49050b2b7ed8, ImageCapture:androidx.camera.core.ImageCapture-c4aa3746-f446-4b9c-9d21-64ee542a972f, ImageAnalysis:androidx.camera.core.ImageAnalysis-22eb4b27-75be-41cb-ab39-08d9301680a1] now DETACHED for camera
2021-04-03 09:56:42.993 27547-28246/com.mycompany.mycompanycapture D/UseCaseAttachState: All use case: [] for camera: 1
2021-04-03 09:56:42.994 27547-28238/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@fba7539[id=0]} Transitioning camera internal state: RELEASING --> RELEASED
2021-04-03 09:56:42.996 27547-28238/com.mycompany.mycompanycapture D/CameraStateRegistry: Recalculating open cameras:
Camera State
-------------------------------------------------------------------
Camera@9e98d8a[id=1] OPEN
-------------------------------------------------------------------
Open count: 1 (Max allowed: 1)
2021-04-03 09:56:42.997 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Resetting Capture Session
2021-04-03 09:56:43.002 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Releasing session in state OPENED
2021-04-03 09:56:43.005 27547-28246/com.mycompany.mycompanycapture D/UseCaseAttachState: Active and attached use case: [] for camera: 1
2021-04-03 09:56:43.008 27547-28246/com.mycompany.mycompanycapture D/UseCaseAttachState: Active and attached use case: [] for camera: 1
2021-04-03 09:56:43.009 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Closing camera.
2021-04-03 09:56:43.010 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Transitioning camera internal state: OPENED --> CLOSING
2021-04-03 09:56:43.012 27547-28246/com.mycompany.mycompanycapture D/CameraStateRegistry: Recalculating open cameras:
Camera State
-------------------------------------------------------------------
Camera@9e98d8a[id=1] CLOSING
-------------------------------------------------------------------
Open count: 1 (Max allowed: 1)
2021-04-03 09:56:43.012 27547-27547/com.mycompany.mycompanycapture D/StreamStateObserver: Update Preview stream state to IDLE
2021-04-03 09:56:43.012 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Resetting Capture Session
2021-04-03 09:56:43.013 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Releasing session in state CLOSING
2021-04-03 09:56:43.015 27547-28246/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Transitioning camera internal state: CLOSING --> RELEASING
2021-04-03 09:56:43.017 27547-28246/com.mycompany.mycompanycapture D/CameraStateRegistry: Recalculating open cameras:
Camera State
-------------------------------------------------------------------
Camera@9e98d8a[id=1] RELEASING
-------------------------------------------------------------------
Open count: 1 (Max allowed: 1)
2021-04-03 09:56:43.134 27547-28238/com.mycompany.mycompanycapture D/CaptureSession: CameraCaptureSession.onClosed()
2021-04-03 09:56:43.134 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: use count-1, useCount=0 closed=true androidx.camera.core.SurfaceRequest$2@170df13
2021-04-03 09:56:43.134 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: Surface no longer in use[total_surfaces=3, used_surfaces=2](androidx.camera.core.SurfaceRequest$2@170df13}
2021-04-03 09:56:43.135 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: Surface terminated[total_surfaces=2, used_surfaces=2](androidx.camera.core.SurfaceRequest$2@170df13}
2021-04-03 09:56:43.136 27547-27547/com.mycompany.mycompanycapture D/SurfaceViewImpl: Safe to release surface.
2021-04-03 09:56:43.137 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: use count-1, useCount=0 closed=true androidx.camera.core.impl.ImmediateSurface@bead44e
2021-04-03 09:56:43.137 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: Surface no longer in use[total_surfaces=2, used_surfaces=1](androidx.camera.core.impl.ImmediateSurface@bead44e}
2021-04-03 09:56:43.137 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: Surface terminated[total_surfaces=1, used_surfaces=1](androidx.camera.core.impl.ImmediateSurface@bead44e}
2021-04-03 09:56:43.138 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: use count-1, useCount=0 closed=true androidx.camera.core.impl.ImmediateSurface@20b8b05
2021-04-03 09:56:43.139 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: Surface no longer in use[total_surfaces=1, used_surfaces=0](androidx.camera.core.impl.ImmediateSurface@20b8b05}
2021-04-03 09:56:43.139 27547-28238/com.mycompany.mycompanycapture D/DeferrableSurface: Surface terminated[total_surfaces=0, used_surfaces=0](androidx.camera.core.impl.ImmediateSurface@20b8b05}
2021-04-03 09:56:43.150 27547-27578/com.mycompany.mycompanycapture E/BufferQueueProducer: [ImageReader-1600x1200f23m4-27547-3](id:6b9b00000003,api:4,p:1172,c:27547) queueBuffer: BufferQueue has been abandoned
2021-04-03 09:56:43.151 27547-27578/com.mycompany.mycompanycapture E/BufferQueueProducer: [ImageReader-1600x1200f23m4-27547-3](id:6b9b00000003,api:4,p:1172,c:27547) cancelBuffer: BufferQueue has been abandoned
2021-04-03 09:56:43.404 27547-28238/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} CameraDevice.onClosed()
2021-04-03 09:56:43.408 27547-28238/com.mycompany.mycompanycapture D/Camera2CameraImpl: {Camera@9e98d8a[id=1]} Transitioning camera internal state: RELEASING --> RELEASED
2021-04-03 09:56:43.411 27547-28238/com.mycompany.mycompanycapture D/CameraStateRegistry: Recalculating open cameras:
Camera State
-------------------------------------------------------------------
-------------------------------------------------------------------
Open count: 0 (Max allowed: 1)
2021-04-03 09:56:48.282 27547-27602/com.mycompany.mycompanycapture I/dmissioncaptur: Explicit concurrent copying GC freed 1022481(18MB) AllocSpace objects, 16(8288KB) LOS objects, 75% free, 9565KB/37MB, paused 327us total 331.464ms
2021-04-03 09:56:48.293 27547-28383/com.mycompany.mycompanycapture I/BpBinder: onLastStrongRef automatically unlinking death recipients: <uncached descriptor>
2021-04-03 09:56:48.293 27547-28383/com.mycompany.mycompanycapture I/BpBinder: onLastStrongRef automatically unlinking death recipients: <uncached descriptor>
2021-04-03 09:56:48.384 27547-27602/com.mycompany.mycompanycapture D/LeakCanary: Found 5 objects retained, dumping heap now (app is visible & >=5 threshold)
Hey Leoh-
I think it was indirectly related to that call. I think the specific cause of my issue is this:
If you call ProcessCameraProvider.getInstance(context.applicationContext)
twice in quick succession, and the second call to get the provider is invoked before the future for the first call invokes it’s listener, I see a leak. Based on the logs I put in, there is a 100% correlation between this happening, and a memory leak (as well as 100% correlation on the inverse).
So my call in onResume
would certainly cause that, though there could be other causes. Thanks for your help!
I guess I should add that my use case for restarting the camera is to change the ImageAnalysis resolution, or the camera facing. So this could happen even if you don’t make a mistake like I did in my onResume
method