Add didANRKillOnPreviousExecution() to FirebaseCrashlytics#8112
Conversation
Implements firebase-android-sdk#4201 by exposing a new public API method that detects whether the app was killed by an ANR in the previous run, mirroring the existing didCrashOnPreviousExecution() pattern. On Android API 30+ the method queries ApplicationExitInfo (already used internally for ANR session reporting) against the previous session's start timestamp. On older API levels it always returns false.
… check failures - Add null guard on ActivityManager from getSystemService() before calling getHistoricalProcessExitReasons() to avoid potential NPE. - Log exceptions in checkForPreviousAnr() at verbose level instead of silently ignoring them, to aid diagnosability.
Using Gemini Code AssistThe 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
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 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. |
📝 PRs merging into main branchOur main branch should always be in a releasable state. If you are working on a larger change, or if you don't want this change to see the light of the day just yet, consider using a feature branch first, and only merge into the main branch when the code complete and ready to be released. |
|
|
||
| Boolean result; | ||
| try { | ||
| result = future.get(DEFAULT_MAIN_HANDLER_TIMEOUT_SEC, TimeUnit.SECONDS); |
There was a problem hiding this comment.
Will this block on main thread?
There was a problem hiding this comment.
Yes —future.get() blocks the main thread for up to DEFAULT_MAIN_HANDLER_TIMEOUT_SEC (3 s) while the background executor runs getHistoricalProcessExitReasons. This is intentional and mirrors the existing checkForPreviousCrash() directly above it, which submits to the same executor and blocks with the same timeout. The bound is the same 3 s, and on the timeout path the method returns false safely.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the didANRKillOnPreviousExecution() method to FirebaseCrashlytics, allowing applications to detect if they were terminated by an ANR in the previous run on Android API 30+. The implementation includes changes to CrashlyticsCore to check for ANRs during initialization and updates to CrashlyticsController and SessionReportingCoordinator to process historical exit reasons. Feedback focuses on optimizing initialization performance by combining blocking checks, improving naming consistency for the public API, simplifying code with method references, and enhancing error logging.
| checkForPreviousCrash(); | ||
| checkForPreviousAnr(); |
There was a problem hiding this comment.
Calling checkForPreviousCrash() and checkForPreviousAnr() sequentially results in a cumulative blocking time of up to 6 seconds on the initialization thread (which is often the main thread). Consider combining these checks into a single background task that shares a single DEFAULT_MAIN_HANDLER_TIMEOUT_SEC timeout to ensure app startup is not excessively delayed.
| * | ||
| * @return true if an ANR was recorded during the previous run of the app. | ||
| */ | ||
| public boolean didANRKillOnPreviousExecution() { |
There was a problem hiding this comment.
For consistency with the existing didCrashOnPreviousExecution() method, consider renaming this to didANROnPreviousExecution(). The internal methods in CrashlyticsCore and CrashlyticsController already use this more concise name.
| public boolean didANRKillOnPreviousExecution() { | |
| public boolean didANROnPreviousExecution() { |
| crashlyticsWorkers | ||
| .common | ||
| .getExecutor() | ||
| .submit(() -> controller.didANROnPreviousExecution()); |
| try { | ||
| result = future.get(DEFAULT_MAIN_HANDLER_TIMEOUT_SEC, TimeUnit.SECONDS); | ||
| } catch (Exception ex) { | ||
| Logger.getLogger().v("Error checking for previous ANR: " + ex.getMessage()); |
There was a problem hiding this comment.
Logging only ex.getMessage() can be unhelpful if the message is null (e.g., for a TimeoutException). It is better to pass the exception object directly to the logger to capture the full stack trace and exception type.
| Logger.getLogger().v("Error checking for previous ANR: " + ex.getMessage()); | |
| Logger.getLogger().v("Error checking for previous ANR.", ex); |
| public class FirebaseCrashlytics { | ||
| method public com.google.android.gms.tasks.Task<java.lang.Boolean!> checkForUnsentReports(); | ||
| method public void deleteUnsentReports(); | ||
| method public boolean didANRKillOnPreviousExecution(); |
Add didANRKillOnPreviousExecution() to FirebaseCrashlytics
Implements #4201.
Background
Crashlytics already detects ANRs and files reports for them, and developers have long been able to call
didCrashOnPreviousExecution()to gate logic that should only run after a fatal crash. However, there was no equivalent API for ANRs: developers who want to adapt their app's behavior after an ANR (e.g., disable a heavy feature on the next cold start, log a custom key, or show a recovery dialog) had no supported way to do so.didCrashOnPreviousExecution()works by reading a marker file written at crash time. ANRs cannot use that mechanism because the main thread is frozen and the crash handler never runs. Instead, the new method usesApplicationExitInfo(API 30+), which records process-exit reasons at the OS level — the same source already queried internally bySessionReportingCoordinatorwhen filing ANR reports. The new API delegates to that existing infrastructure rather than introducing a separate detection path.Risk
publicmethod onFirebaseCrashlytics(didANRKillOnPreviousExecution()). No existing method signatures changed.false; noApplicationExitInfoaccess attempted.getHistoricalProcessExitReasonsonce at startup on the background thread with a bounded timeout. This is the sameActivityManagercall already made by the ANR-reporting path; no new system service is introduced.