Fixed
Status Update
Comments
dm...@gmail.com <dm...@gmail.com> #2
If you remove if (savedInstanceState == null) check, a different leak appears:
┬
├─ android.app.ActivityThread
│ Leaking: NO (ActivityThread↓ is not leaking and a class is never leaking)
│ GC Root: System class
│ ↓ static ActivityThread.sCurrentActivityThread
├─ android.app.ActivityThread
│ Leaking: NO (ArrayMap↓ is not leaking)
│ ↓ ActivityThread.mActivities
├─ android.util.ArrayMap
│ Leaking: NO (Object[]↓ is not leaking)
│ ↓ ArrayMap.mArray
├─ java.lang.Object[]
│ Leaking: NO (ActivityThread$ActivityClientRecord↓ is not leaking)
│ ↓ array Object[].[1]
├─ android.app.ActivityThread$ActivityClientRecord
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ActivityThread$ActivityClientRecord.activity
├─ com.eightbitlab.biometricbugs.MainActivity
│ Leaking: NO (Activity#mDestroyed is false)
│ ↓ MainActivity.mLifecycleRegistry
│ ~~~~~~~~~~~~~~~~~~
├─ androidx.lifecycle.LifecycleRegistry
│ Leaking: UNKNOWN
│ ↓ LifecycleRegistry.mObserverMap
│ ~~~~~~~~~~~~
├─ androidx.arch.core.internal.FastSafeIterableMap
│ Leaking: UNKNOWN
│ ↓ FastSafeIterableMap.mEnd
│ ~~~~
├─ androidx.arch.core.internal.SafeIterableMap$Entry
│ Leaking: UNKNOWN
│ ↓ SafeIterableMap$Entry.mKey
│ ~~~~
├─ androidx.biometric.BiometricPrompt$2
│ Leaking: UNKNOWN
│ Anonymous class implementing androidx.lifecycle.LifecycleObserver
│ ↓ BiometricPrompt$2.this$0
│ ~~~~~~
├─ androidx.biometric.BiometricPrompt
│ Leaking: UNKNOWN
│ ↓ BiometricPrompt.mFingerprintDialogFragment
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~
╰→ androidx.biometric.FingerprintDialogFragment
Leaking: YES (Fragment#mFragmentManager is null and ObjectWatcher was watching this)
key = 5906cc33-6bea-4500-8d0b-5c112c9a7668
watchDurationMillis = 15482
retainedDurationMillis = 10480
====================================
┬
├─ android.app.ActivityThread
│ Leaking: NO (ActivityThread↓ is not leaking and a class is never leaking)
│ GC Root: System class
│ ↓ static ActivityThread.sCurrentActivityThread
├─ android.app.ActivityThread
│ Leaking: NO (ArrayMap↓ is not leaking)
│ ↓ ActivityThread.mActivities
├─ android.util.ArrayMap
│ Leaking: NO (Object[]↓ is not leaking)
│ ↓ ArrayMap.mArray
├─ java.lang.Object[]
│ Leaking: NO (ActivityThread$ActivityClientRecord↓ is not leaking)
│ ↓ array Object[].[1]
├─ android.app.ActivityThread$ActivityClientRecord
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ActivityThread$ActivityClientRecord.activity
├─ com.eightbitlab.biometricbugs.MainActivity
│ Leaking: NO (Activity#mDestroyed is false)
│ ↓ MainActivity.mLifecycleRegistry
│ ~~~~~~~~~~~~~~~~~~
├─ androidx.lifecycle.LifecycleRegistry
│ Leaking: UNKNOWN
│ ↓ LifecycleRegistry.mObserverMap
│ ~~~~~~~~~~~~
├─ androidx.arch.core.internal.FastSafeIterableMap
│ Leaking: UNKNOWN
│ ↓ FastSafeIterableMap.mEnd
│ ~~~~
├─ androidx.arch.core.internal.SafeIterableMap$Entry
│ Leaking: UNKNOWN
│ ↓ SafeIterableMap$Entry.mKey
│ ~~~~
├─ androidx.biometric.BiometricPrompt$2
│ Leaking: UNKNOWN
│ Anonymous class implementing androidx.lifecycle.LifecycleObserver
│ ↓ BiometricPrompt$2.this$0
│ ~~~~~~
├─ androidx.biometric.BiometricPrompt
│ Leaking: UNKNOWN
│ ↓ BiometricPrompt.mFingerprintDialogFragment
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~
╰→ androidx.biometric.FingerprintDialogFragment
Leaking: YES (Fragment#mFragmentManager is null and ObjectWatcher was watching this)
key = 5906cc33-6bea-4500-8d0b-5c112c9a7668
watchDurationMillis = 15482
retainedDurationMillis = 10480
====================================
[Deleted User] <[Deleted User]> #3
The BiometricFragment class is a leak minefield.
Looking at the source code, you call setRetainInstanceState(true) but you keep references to the activity in many ways without ever clearing them.
mBiometricPrompt is set in onCreateView using activity context but it's not cleared in onDestroyView
mContext is set onAttach but it's not cleared in onDetach
mClientAuthenticationCallback stores the callbacks that we pass in when creating the biometric prompt so there's a big chance they are tied to the calling activity as well and never cleared (as is the case in the sample codehttps://developer.android.com/training/sign-in/biometric-auth )
And if you look at the source code of android.hardware.biometrics.BiometricPrompt in Android 10, you'll see that they keep the context you pass in, maybe that's fixable by using application context.
Overall, really poor implementation for a stable release.
Looking at the source code, you call setRetainInstanceState(true) but you keep references to the activity in many ways without ever clearing them.
mBiometricPrompt is set in onCreateView using activity context but it's not cleared in onDestroyView
mContext is set onAttach but it's not cleared in onDetach
mClientAuthenticationCallback stores the callbacks that we pass in when creating the biometric prompt so there's a big chance they are tied to the calling activity as well and never cleared (as is the case in the sample code
And if you look at the source code of android.hardware.biometrics.BiometricPrompt in Android 10, you'll see that they keep the context you pass in, maybe that's fixable by using application context.
Overall, really poor implementation for a stable release.
[Deleted User] <[Deleted User]> #4
Then there's also this leak on Android 10 but I'm not sure if you can fix it without changing the platform code.
ApplicationLeak(className=androidx.biometric.BiometricFragment, leakTrace=
┬
├─ android.hardware.biometrics.BiometricPrompt$1
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.biometrics.IBiometricServiceReceiver$Stub
│ GC Root: Global variable in native code
│ ↓ BiometricPrompt$1.this$0
│ ~~~~~~
├─ android.hardware.biometrics.BiometricPrompt
│ Leaking: UNKNOWN
│ ↓ BiometricPrompt.mAuthenticationCallback
│ ~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.biometric.BiometricFragment$2
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.biometrics.BiometricPrompt$AuthenticationCallback
│ ↓ BiometricFragment$2.this$0
│ ~~~~~~
╰→ androidx.biometric.BiometricFragment
Leaking: YES (Fragment#mFragmentManager is null and ObjectWatcher was watching this)
key = e83e567c-4012-4201-9a4b-33fc2a8fd98d
watchDurationMillis = 42513
retainedDurationMillis = 8077
, retainedHeapByteSize=1588)
ApplicationLeak(className=androidx.biometric.BiometricFragment, leakTrace=
┬
├─ android.hardware.biometrics.BiometricPrompt$1
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.biometrics.IBiometricServiceReceiver$Stub
│ GC Root: Global variable in native code
│ ↓ BiometricPrompt$1.this$0
│ ~~~~~~
├─ android.hardware.biometrics.BiometricPrompt
│ Leaking: UNKNOWN
│ ↓ BiometricPrompt.mAuthenticationCallback
│ ~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.biometric.BiometricFragment$2
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.biometrics.BiometricPrompt$AuthenticationCallback
│ ↓ BiometricFragment$2.this$0
│ ~~~~~~
╰→ androidx.biometric.BiometricFragment
Leaking: YES (Fragment#mFragmentManager is null and ObjectWatcher was watching this)
key = e83e567c-4012-4201-9a4b-33fc2a8fd98d
watchDurationMillis = 42513
retainedDurationMillis = 8077
, retainedHeapByteSize=1588)
mo...@gmail.com <mo...@gmail.com> #5
Hello I found 2 other leaks related to BiometricFragment: calling finish() after the activity has called biometricprompt.authenticate() causes the Activity and BiometricFragment to leak. These leaks happen before or after the user has successfully authenticated.
Notes:
Fingerprints enrolled
Artifact used: androidx.biometric:biometric:1.0.1
Emulator: Pixel 3A XL API 29 (although the leaks appear on all other devices too).
Here is a link to the StackOverflow question I posted with code:
https://stackoverflow.com/questions/59833360/biometricprompt-biometricfragment-and-activity-memory-leak
Notes:
Fingerprints enrolled
Artifact used: androidx.biometric:biometric:1.0.1
Emulator: Pixel 3A XL API 29 (although the leaks appear on all other devices too).
Here is a link to the StackOverflow question I posted with code:
cu...@google.com <cu...@google.com>
ap...@google.com <ap...@google.com> #6
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 8e4b7413db92c31d302f19c6f78db181ba4c97ff
Author: Curtis Belmonte <curtislb@google.com>
Date: Mon Feb 03 14:01:31 2020
Remove context reference from fingerprint fragments
Having FingerprintDialogFragment and FingerprintHelperFragment retain a
Context reference can cause us to leak the Context. This commit updates
both fragments to ditch the mContext field and instead call getContext
as needed.
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest
Test: Manually, using the support demo app on Pixel 3, API 27
Bug: 144919472
Change-Id: I79d1704407456c73b5be73a1408ff748b9f03005
M biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
M biometric/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
https://android-review.googlesource.com/1225322
Branch: androidx-master-dev
commit 8e4b7413db92c31d302f19c6f78db181ba4c97ff
Author: Curtis Belmonte <curtislb@google.com>
Date: Mon Feb 03 14:01:31 2020
Remove context reference from fingerprint fragments
Having FingerprintDialogFragment and FingerprintHelperFragment retain a
Context reference can cause us to leak the Context. This commit updates
both fragments to ditch the mContext field and instead call getContext
as needed.
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest
Test: Manually, using the support demo app on Pixel 3, API 27
Bug: 144919472
Change-Id: I79d1704407456c73b5be73a1408ff748b9f03005
M biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
M biometric/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
ap...@google.com <ap...@google.com> #7
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 688b10235137dd8123a5faddb75441c4500655a9
Author: Curtis Belmonte <curtislb@google.com>
Date: Mon Feb 03 11:32:29 2020
Make fingerprint dialog handler static to avoid leaks
Updates the Handler class within FingerprintDialogFragment to be static
with a WeakReference to the fragment, in order to avoid potentially
leaking the fragment instance.
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest
Test: Manually with biometric demo app on Pixel 3, API 27
Bug: 144919472
Change-Id: I715db3c0b4a606fe10da7c984d8f8fcf4e226d0a
M biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
M biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
https://android-review.googlesource.com/1225259
Branch: androidx-master-dev
commit 688b10235137dd8123a5faddb75441c4500655a9
Author: Curtis Belmonte <curtislb@google.com>
Date: Mon Feb 03 11:32:29 2020
Make fingerprint dialog handler static to avoid leaks
Updates the Handler class within FingerprintDialogFragment to be static
with a WeakReference to the fragment, in order to avoid potentially
leaking the fragment instance.
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest
Test: Manually with biometric demo app on Pixel 3, API 27
Bug: 144919472
Change-Id: I715db3c0b4a606fe10da7c984d8f8fcf4e226d0a
M biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
M biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
dk...@fundrise.com <dk...@fundrise.com> #8
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more athttps://squ.re/leaks .
660848 bytes retained by leaking objects
Signature: e7edf85aa4445657a23a32aa6a189a8194a
┬───
│ GC Root: Global variable in native code
│
├─ android.hardware.fingerprint.FingerprintManager$2 instance
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.fingerprint.IFingerprintServiceReceiver$Stub
│ ↓ FingerprintManager$2.this$0
│ ~~~~~~
├─ android.hardware.fingerprint.FingerprintManager instance
│ Leaking: UNKNOWN
│ ↓ FingerprintManager.mContext
│ ~~~~~~~~
╰→ ....SplashScreenActivity instance
Leaking: YES (ObjectWatcher was watching this because ....SplashScreenActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = 5baec59c-9597-4595-b669-8266062b4afb
watchDurationMillis = 5289
retainedDurationMillis = 280
1877 bytes retained by leaking objects
Signature: 6ada7743617e3ee898c9f63a9cfedd67f4758171
┬───
│ GC Root: Global variable in native code
│
├─ android.hardware.fingerprint.FingerprintManager$2 instance
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.fingerprint.IFingerprintServiceReceiver$Stub
│ ↓ FingerprintManager$2.this$0
│ ~~~~~~
├─ android.hardware.fingerprint.FingerprintManager instance
│ Leaking: UNKNOWN
│ ↓ FingerprintManager.mAuthenticationCallback
│ ~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.biometric.BiometricFragment$2 instance
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.biometrics.BiometricPrompt$AuthenticationCallback
│ ↓ BiometricFragment$2.this$0
│ ~~~~~~
╰→ androidx.biometric.BiometricFragment instance
Leaking: YES (ObjectWatcher was watching this because androidx.biometric.BiometricFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
key = 5ed5df48-d1ed-4365-a8fc-a638ec29ac43
watchDurationMillis = 5288
retainedDurationMillis = 279
key = 61f26804-4b2a-4eef-b4ea-ac06ed762f35
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at
660848 bytes retained by leaking objects
Signature: e7edf85aa4445657a23a32aa6a189a8194a
┬───
│ GC Root: Global variable in native code
│
├─ android.hardware.fingerprint.FingerprintManager$2 instance
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.fingerprint.IFingerprintServiceReceiver$Stub
│ ↓ FingerprintManager$2.this$0
│ ~~~~~~
├─ android.hardware.fingerprint.FingerprintManager instance
│ Leaking: UNKNOWN
│ ↓ FingerprintManager.mContext
│ ~~~~~~~~
╰→ ....SplashScreenActivity instance
Leaking: YES (ObjectWatcher was watching this because ....SplashScreenActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = 5baec59c-9597-4595-b669-8266062b4afb
watchDurationMillis = 5289
retainedDurationMillis = 280
1877 bytes retained by leaking objects
Signature: 6ada7743617e3ee898c9f63a9cfedd67f4758171
┬───
│ GC Root: Global variable in native code
│
├─ android.hardware.fingerprint.FingerprintManager$2 instance
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.fingerprint.IFingerprintServiceReceiver$Stub
│ ↓ FingerprintManager$2.this$0
│ ~~~~~~
├─ android.hardware.fingerprint.FingerprintManager instance
│ Leaking: UNKNOWN
│ ↓ FingerprintManager.mAuthenticationCallback
│ ~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.biometric.BiometricFragment$2 instance
│ Leaking: UNKNOWN
│ Anonymous subclass of android.hardware.biometrics.BiometricPrompt$AuthenticationCallback
│ ↓ BiometricFragment$2.this$0
│ ~~~~~~
╰→ androidx.biometric.BiometricFragment instance
Leaking: YES (ObjectWatcher was watching this because androidx.biometric.BiometricFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
key = 5ed5df48-d1ed-4365-a8fc-a638ec29ac43
watchDurationMillis = 5288
retainedDurationMillis = 279
key = 61f26804-4b2a-4eef-b4ea-ac06ed762f35
====================================
lo...@gmail.com <lo...@gmail.com> #9
I see code merged. When will it release and which version will have fixed code :o
ap...@google.com <ap...@google.com> #10
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 6e031f7cad58675cf1522260727e3894122f9107
Author: Curtis Belmonte <curtislb@google.com>
Date: Thu Jul 30 13:33:30 2020
Fix memory leaks in androidx.biometric library
Addresses several memory leaks reported by LeakCanary for the following
classes:
- The BiometricFragment internal library class
- The BiometricViewModel internal library class
- The host activity from the client application
Test: Biometric integration test app on API 27-30
Test: LeakCanary e2e test added in a follow-up commit
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest
Bug: 143929280
Bug: 144919472
Bug: 149344544
Change-Id: Ia055ffd6b97e3f3b0ba85c2cd665c94fe467bab6
M biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
M biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
https://android-review.googlesource.com/1382564
Branch: androidx-master-dev
commit 6e031f7cad58675cf1522260727e3894122f9107
Author: Curtis Belmonte <curtislb@google.com>
Date: Thu Jul 30 13:33:30 2020
Fix memory leaks in androidx.biometric library
Addresses several memory leaks reported by LeakCanary for the following
classes:
- The BiometricFragment internal library class
- The BiometricViewModel internal library class
- The host activity from the client application
Test: Biometric integration test app on API 27-30
Test: LeakCanary e2e test added in a follow-up commit
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest
Bug: 143929280
Bug: 144919472
Bug: 149344544
Change-Id: Ia055ffd6b97e3f3b0ba85c2cd665c94fe467bab6
M biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
M biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
cu...@google.com <cu...@google.com> #11
This should be addressed as of version 1.1.0-alpha02. We've also added e2e tests with LeakCanary to avoid future regressions.
mi...@gmail.com <mi...@gmail.com> #12
I can confirm that this is fixed on 1.1.0-alpha02
na...@gmail.com <na...@gmail.com> #13
I still have this error in 1.1.0-alpha3 version
Description
Devices/Android versions reproduced on:
Emulator 25 API, X86 (behaves the same on all versions)
Android version 7.1.1
Code:
Steps:
Have a stored fingerprint on the device.
Launch the example app, see the fingerprint dialog.
Rotate the screen.
See 2 leaks in LeakCanary.
Frequency:
Each time on rotation.
Leak #1:
androidx.biometric.DeviceCredentialHandlerBridge
│ Leaking: NO (a class is never leaking)
│ GC Root: System class
│ ↓ static DeviceCredentialHandlerBridge.sInstance
│ ~~~~~~~~~
├─ androidx.biometric.DeviceCredentialHandlerBridge
│ Leaking: UNKNOWN
│ ↓ DeviceCredentialHandlerBridge.mAuthenticationCallback
│ ~~~~~~~~~~~~~~~~~~~~~~~
├─ com.eightbitlab.biometricbugs.MainActivity$onCreate$1
│ Leaking: UNKNOWN
│ Anonymous subclass of androidx.biometric.BiometricPrompt$AuthenticationCallback
│ ↓ MainActivity$onCreate$1.this$0
Leak #2:
┬
├─ androidx.biometric.DeviceCredentialHandlerBridge
│ Leaking: NO (a class is never leaking)
│ GC Root: System class
│ ↓ static DeviceCredentialHandlerBridge.sInstance
│ ~~~~~~~~~
├─ androidx.biometric.DeviceCredentialHandlerBridge
│ Leaking: UNKNOWN
│ ↓ DeviceCredentialHandlerBridge.mOnClickListener
│ ~~~~~~~~~~~~~~~~
├─ androidx.biometric.BiometricPrompt$1
│ Leaking: UNKNOWN
│ Anonymous class implementing android.content.DialogInterface$OnClickListener
│ ↓ BiometricPrompt$1.this$0
│ ~~~~~~
├─ androidx.biometric.BiometricPrompt
│ Leaking: UNKNOWN
│ ↓ BiometricPrompt.mFingerprintDialogFragment
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~
╰→ androidx.biometric.FingerprintDialogFragment
Leaking: YES (Fragment#mFragmentManager is null and ObjectWatcher was watching this)
key = 42dbc3ac-2321-40a0-bfd4-4f74044ca615
watchDurationMillis = 2137697
retainedDurationMillis = 2132696
Another leak, that is not reported by LeakCanary right now is a non-static child of Handler in FingerprintDialogFragment.
Lint gives you a warning:
This Handler class should be static or leaks might occur.