Утечка загружаемого шрифта Android от FontsContractCompat в Android Pie (9)
В моем приложении произошла утечка памяти из-за использования функции DownloadableFont. Утечка происходит только в эмуляторе, работающем на Android 9. Похоже, это происходит, когда эмулятору не удается кэшировать шрифт и
FontsContractCompat
должен перейти на сервер Google (или что-то еще), чтобы загрузить шрифт.
Воспроизвести шаги:
- Сотрите данные эмулятора.
- Откройте мое приложение.
- Путешествуйте через приложение. (Я использую инструмент обезьяны для запуска ~9000 событий, чтобы получить утечку).
Утечка случайным образом происходит везде, где используются шрифты.
Вот трассировка утечки из LeakCanary:
Analysis in progress, working on: REPORTING_HEAP_ANALYSIS
2021-03-02 14:26:28.321 4800-6564/my.app.package D/LeakCanary: ====================================
2021-03-02 14:26:28.321 4800-6564/my.app.package D/LeakCanary: HEAP ANALYSIS RESULT
2021-03-02 14:26:28.321 4800-6564/my.app.package D/LeakCanary: ====================================
2021-03-02 14:26:28.321 4800-6564/my.app.package D/LeakCanary: 1 APPLICATION LEAKS
2021-03-02 14:26:28.321 4800-6564/my.app.package D/LeakCanary: References underlined with "~~~" are likely causes.
2021-03-02 14:26:28.321 4800-6564/my.app.package D/LeakCanary: Learn more at https://squ.re/leaks.
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: 500077 bytes retained by leaking objects
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Signature: a57defa6cf388ebf6313be9d96410abfc599a45
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ┬───
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ GC Root: Input or output parameters in native code
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ okio.AsyncTimeout class
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: NO (PathClassLoader↓ is not leaking and a class is never leaking)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ static AsyncTimeout.$class$classLoader
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: NO (FontsContractCompat↓ is not leaking and A ClassLoader is never leaking)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ PathClassLoader.runtimeInternalObjects
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ java.lang.Object[] array
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: NO (FontsContractCompat↓ is not leaking)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ Object[].[21]
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.core.provider.FontsContractCompat class
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: NO (a class is never leaking)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ static FontsContractCompat.sPendingReplies
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~~~~~~~~~~~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.collection.SimpleArrayMap instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ SimpleArrayMap.mArray
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ java.lang.Object[] array
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ Object[].[3]
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ java.util.ArrayList instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ ArrayList.elementData
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~~~~~~~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ java.lang.Object[] array
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ Object[].[0]
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.core.provider.FontsContractCompat$2 instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Anonymous class implementing androidx.core.provider.SelfDestructiveThread$ReplyCallback
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ FontsContractCompat$2.val$fontCallback
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~~~~~~~~~~~~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.appcompat.widget.AppCompatTextHelper$1 instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Anonymous subclass of androidx.core.content.res.ResourcesCompat$FontCallback
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ AppCompatTextHelper$1.this$0
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.appcompat.widget.AppCompatTextHelper instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: UNKNOWN
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ AppCompatTextHelper.mView
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ~~~~~
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ com.google.android.material.button.MaterialButton instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: YES (View detached and has parent)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ mContext instance of android.view.ContextThemeWrapper, wrapping activity my.app.package.MyActivity with mDestroyed = false
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mParent is set
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mAttachInfo is null (view detached)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mID = R.id.signInButton
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mWindowAttachCount = 1
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ MaterialButton.mParent
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.constraintlayout.widget.ConstraintLayout instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: YES (MaterialButton↑ is leaking and View detached and has parent)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ mContext instance of android.view.ContextThemeWrapper, wrapping activity my.app.package.MyActivity with mDestroyed = false
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mParent is set
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mAttachInfo is null (view detached)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mID = R.id.signInLayout
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mWindowAttachCount = 1
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ ConstraintLayout.mParent
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.constraintlayout.widget.ConstraintLayout instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: YES (ConstraintLayout↑ is leaking and View detached and has parent)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ mContext instance of android.view.ContextThemeWrapper, wrapping activity my.app.package.MyActivity with mDestroyed = false
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mParent is set
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mAttachInfo is null (view detached)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mID = R.id.ll_portfolio_auth_content
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mWindowAttachCount = 1
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ ConstraintLayout.mParent
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ├─ androidx.core.widget.NestedScrollView instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ Leaking: YES (ConstraintLayout↑ is leaking and View detached and has parent)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ mContext instance of android.view.ContextThemeWrapper, wrapping activity my.app.package.MyActivity with mDestroyed = false
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mParent is set
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View#mAttachInfo is null (view detached)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ View.mWindowAttachCount = 1
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: │ ↓ NestedScrollView.mParent
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ╰→ my.app.package.MyCustomizedCoordinatorLayout instance
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Leaking: YES (ObjectWatcher was watching this because my.app.package.MyFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: key = f5e3a6d2-3f04-40ed-9e67-2788011fe63b
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: watchDurationMillis = 13097
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: retainedDurationMillis = 8096
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: mContext instance of android.view.ContextThemeWrapper, wrapping activity my.app.package.MyActivity with mDestroyed = false
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: View#mParent is null
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: View#mAttachInfo is null (view detached)
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: View.mID = R.id.portfolio_fragment
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: View.mWindowAttachCount = 1
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ====================================
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: 0 LIBRARY LEAKS
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: ====================================
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: METADATA
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Please include this in bug reports and Stack Overflow questions.
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Build.VERSION.SDK_INT: 28
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Build.MANUFACTURER: Google
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: LeakCanary version: 2.4
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: App process name: my.app.package
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Analysis duration: 7435 ms
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Heap dump file path: /data/user/0/my.app.package/files/leakcanary/2021-03-02_14-26-18_332.hprof
2021-03-02 14:26:28.322 4800-6564/my.app.package D/LeakCanary: Heap dump timestamp: 1614713188314
Вот детали эмулятора:
Name: Nexus_One_API_28
CPU/ABI: Google APIs Intel Atom (x86)
Path: /Users/mycomputer/.android/avd/Nexus_One_API_28.avd
Target: google_apis [Google APIs] (API level 28)
Skin: nexus_one
SD Card: 512 MB
fastboot.chosenSnapshotFile:
runtime.network.speed: full
hw.accelerometer: yes
hw.device.name: Nexus One
hw.lcd.width: 480
hw.initialOrientation: Portrait
image.androidVersion.api: 28
tag.id: google_apis
hw.mainKeys: yes
hw.camera.front: None
avd.ini.displayname: Nexus One API 28
hw.gpu.mode: auto
hw.ramSize: 512
PlayStore.enabled: false
fastboot.forceColdBoot: no
hw.cpu.ncore: 4
hw.keyboard: yes
hw.sensors.proximity: yes
hw.dPad: no
hw.lcd.height: 800
vm.heapSize: 48
skin.dynamic: yes
hw.device.manufacturer: Google
hw.gps: yes
hw.audioInput: yes
image.sysdir.1: system-images/android-28/google_apis/x86/
showDeviceFrame: yes
hw.camera.back: virtualscene
AvdId: Nexus_One_API_28
hw.lcd.density: 240
hw.arc: false
hw.device.hash2: MD5:ef39e456bf2cab397201c2ac251f35fc
fastboot.forceChosenSnapshotBoot: no
fastboot.forceFastBoot: yes
hw.trackBall: yes
hw.battery: yes
hw.sdCard: yes
tag.display: Google APIs
runtime.network.latency: none
disk.dataPartition.size: 800M
hw.sensors.orientation: no
avd.ini.encoding: UTF-8
hw.gpu.enabled: yes
Я использовал эту команду для запуска теста на обезьяну:
adb shell monkey -p my.app.package -s 12345 --throttle 1000 -v --pct-syskeys 0 9999
my.app.package — это мое приложение.
Вот код, который я использую для настройки DownloadableFont.
AndroidManifest.xml
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
preloaded_fonts.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/droid_sans_mono</item>
<item>@font/roboto_bold</item>
<item>@font/roboto_medium</item>
<item>@font/roboto_mono_medium</item>
<item>@font/roboto_regular</item>
<item>@font/roboto_thin</item>
</array>
</resources>
один из пользовательских шрифтов (я могу добавить больше, если вы думаете, что это поможет вам помочь мне), другой выглядит очень похоже.
roboto_bold.xml
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Roboto&weight=700"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>
и использовать их так
тип.xml
<style name="TextAppearance.MyApp.Headline1" parent="TextAppearance.MaterialComponents.Headline1">
<item name="fontFamily">@font/roboto_bold</item>
<item name="android:textStyle">bold</item>
</style>
в любом текстовом представлении, кнопке или EditText
...
android:textAppearance="?attr/textAppearanceHeadline1"
Я добавил эти шрифты в свое приложение с помощью Android Studio.