CameraX Camera Leaks

1,081 views
Skip to first unread message
Assigned to leoh...@google.com by wuj...@google.com

Jeff

unread,
Mar 31, 2021, 7:47:04 PM3/31/21
to Android CameraX Discussion Group

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 }



Jeff

unread,
Mar 31, 2021, 9:52:19 PM3/31/21
to Android CameraX Discussion Group, Jeff
Same issue with
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"

Leo Huang

unread,
Apr 1, 2021, 5:27:21 AM4/1/21
to Android CameraX Discussion Group, Jeff
Hi Jeff,
Thanks for reporting the issue. We are investigating this issue now.

Could you also paste the code about how do you operate CameraXCamera.
For example, what CameraXCamera API will you call when launch and destroy activity. For destroy case, is CameraXCamera.pause(), CameraXCamera.stop() or both?

Thanks

Jeff 在 2021年4月1日 星期四上午9:52:19 [UTC+8] 的信中寫道:

Jeff J

unread,
Apr 1, 2021, 2:07:52 PM4/1/21
to Leo Huang, Android CameraX Discussion Group
Sure thing. It looks like the "pause" method is one I don't even use anymore. In the activity:

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()
}

Jeff

unread,
Apr 3, 2021, 2:31:49 AM4/3/21
to Android CameraX Discussion Group, Jeff, Android CameraX Discussion Group, leoh...@google.com
Is there any other info I can provide to help diagnose this? I could be wrong but from that LeakCanary trace it doesn't look like I can do anything to fix this, and without a fix I see my memory steadily creeping up without bound

Leo Huang

unread,
Apr 3, 2021, 5:00:03 AM4/3/21
to Jeff, Android CameraX Discussion Group
Hi Jeff,

Currently I can't reproduce the leak. Please attach the log if possible. It will be helpful if some clues can be found from log.
Could the leak be always reproduced or is it a once issue?
Are there multiple instances of CameraActivity retained by this leak trace?

From the leak trace, I can see CameraActivity is retained by following simplified sequence

ProcessCameraProvider -> Camera2CamaraImpl -> UseCaseAttachState -> SessionConfig -> Preview -> PreviewView -> CameraActivity.

ProcessCameraProvider will not be the cause because it is singleton and never released.
In normal situation, when Preview is unbound, the reference "UseCaseAttachState -> SessionConfig" should be dropped.

I also found an uncommon usage for CameraX, cameraProvider?.shutdown() is called in CameraXCamera.stop() method. The API is currently only suggested for writing unit test (denoted by @RestrictTo(Scope.TESTS)), so it may not be well test. You can have a try to remove it and see if leak remain. Regardless of whether it is related to the leak, it is still recommended not to call shutdown(), because it will impact performance when CameraActivity is restarted.

Jeff <jeffreyf...@gmail.com> 於 2021年4月3日 週六 下午2:31寫道:

Jeff

unread,
Apr 3, 2021, 12:05:06 PM4/3/21
to Android CameraX Discussion Group, leoh...@google.com, Android CameraX Discussion Group, Jeff
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)
And then after this is where Leak Canary detects the retained Activity. 

I removed that experimental cameraProvider?.shutdown() call, same issue.

It does not happen 100% of the time, but it is fairly regularly

Leo Huang

unread,
Apr 4, 2021, 10:44:00 PM4/4/21
to Jeff, Android CameraX Discussion Group
Hi Jeff, 

Because CameraX will automatically rebind UseCases according to lifecycle change, you can create and bind UseCases simply in "onCreate()" instead of "onResume".
e.g. remove below code from onResume()
// necessary to restart camera after launching new activity
    integratedCamera.start(lastOrientation())

if you need to change the orientation of imageAnalysis while onResume(), you can directly call imageAnalysis.setTargetRotation() to the existing imageAnalysis instance instead of create a new one.

Would you mind to try this? Thanks.

Jeff <jeffreyf...@gmail.com> 於 2021年4月4日 週日 上午12:05寫道:
Message has been deleted

Jeff

unread,
Apr 5, 2021, 11:47:42 PM4/5/21
to Android CameraX Discussion Group, leoh...@google.com, Android CameraX Discussion Group, Jeff

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!

Jeff

unread,
Apr 5, 2021, 11:57:48 PM4/5/21
to Android CameraX Discussion Group, Jeff, leoh...@google.com, Android CameraX Discussion Group

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

Leo Huang

unread,
Apr 6, 2021, 4:53:11 AM4/6/21
to Jeff, Android CameraX Discussion Group
Hi Jeff,
There should be a memory leak in CameraX, because the leak trace definitely indicate this. What I can speculate at this moment is that when quickly binding/unbinding useCases, a race condition may cause the leak. I believe that removing the unbinding/binding UseCase from onResume() can prevent the leak, because this greatly reduces the chance of frequently binding/unbinding UseCases. Hope the modification can workaround the problems you are currently experiencing, in addition, it can also improve performance. We will try to fix this memory leak in future release. If you still encounter any problem, please let us know. Thank you.

Jeff <jeffreyf...@gmail.com> 於 2021年4月6日 週二 上午11:57寫道:
Reply all
Reply to author
Forward
0 new messages