镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(crashlytics,android): read the ELF build ID off the main thread, once per process - #18835

Draft
Lyokone wants to merge 2 commits into
mainfrom
fix/crashlytics-android-build-id-off-main
Draft

Lyokone wants to merge 2 commits into
mainfrom
fix/crashlytics-android-build-id-off-main

Conversation

@Lyokone

@Lyokone Lyokone commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Description

FlutterFirebaseCrashlyticsPlugin.onAttachedToEngine read the ELF build ID of libapp.so (ElfBuildIdReader.readBuildId) on the main thread, every time an engine attached. With minSdk >= 24 the native libraries are not extracted (extractNativeLibs=false is the AGP default), so nativeLibraryDir/libapp.so does not exist and the reader opens base.apk, then each split, as a ZipFile to look for lib/<abi>/libapp.so. That is disk I/O during app startup on the main thread, a StrictMode violation, and it is repeated for every background engine (messaging, workmanager, ...).

Reproduced on the Pixel 9a emulator with the crashlytics example (debug build, minSdk 24) and a local, uncommitted StrictMode.ThreadPolicy (detectDiskReads().penaltyLog()) in MainActivity.onCreate:

StrictMode disk-read violations on the main thread at launch
main 10, every one from FlutterFirebaseCrashlyticsPlugin.onAttachedToEngine -> ElfBuildIdReader.readBuildId -> readBuildIdFromApk -> ZipFile (access, open, fstat, lseek, read, mmap of the central directory)
this PR 0

The build ID is only used by recordError, which already runs on cachedThreadPool. It is now read there on first use, by ElfBuildIdReader.cachedBuildId, and cached for the process: libapp.so cannot change while the process runs, so every engine shares one read. recordError still prefers the ELF build ID over the Dart snapshot one, as before.

Tests

JUnit, run by android-unit-tests (:firebase_crashlytics:testDebugUnitTest), new FlutterFirebaseCrashlyticsPluginTest:

  • onAttachedToEngine_doesNotReadLibappOnTheMainThread: attaches the plugin to two engines with a context that records which threads look up applicationInfo (where every read starts). Fails on main (read on the attaching thread), passes here.
  • cachedBuildId_readsLibappOncePerProcess: three lookups from two engines read libapp.so once and return its build ID. Fails if the cache is removed (3 reads).

The existing ElfBuildIdReaderTest helpers that build an ELF image are now package-private so the new test can reuse them.

Related Issues

None found.

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (melos run analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

  • No, this is not a breaking change.

…once per process

onAttachedToEngine read libapp.so's build ID on the main thread for every
engine. With minSdk >= 24 the native libraries are not extracted, so that
opened base.apk and its splits as zip files during startup, which is jank
and a StrictMode disk-read violation, repeated for each background engine.

Read it on first use instead, in recordError, which already runs on the
cached thread pool, and share the result across engines.
@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Reading it on the first `recordError` meant the read could happen while the
app is short of memory or file descriptors, often the reason it reports an
error, and a failed read was cached for the whole process. It is now read once
when the plugin attaches, on the thread pool, so the first report doesn't
wait for it either.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant