Skip to content

Explainer

Reducing ANR and Cold Start in an Android Browser

How WizeUp improved Android browser performance: ANR refactor, Moxy-to-ViewModel migration, Rx-to-coroutines rewrite, and push reliability work.

Reducing ANR and Cold Start in an Android Browser preview

Written by the Fulldive product engineering team, based on direct inspection of the repositories listed below.

If you care about Android browser performance, the interesting work is almost never visible in the UI. It is in how the app handles the main thread during cold start, how it keeps long-running tasks off the UI, and how reliably it wakes up to deliver a push. This post walks through four commits in the fulldiveVR/fulldive-android-apps repository — the one that ships as WizeUp — that together cut Application Not Responding (ANR) rates and tidied the cold-start path during the 2026 modernization sprint.

What ANRs are, briefly

Android considers an app unresponsive if the main thread is blocked for long enough that an input or broadcast cannot be dispatched. The system then shows the “App is not responding” dialog. Google documents the exact thresholds and common causes in ANRs on Android Developers. The usual suspects on a browser-class app are:

  • Heavy work on Activity.onCreate or onStart.
  • Synchronous disk or database reads on the main thread.
  • Blocking network calls in broadcast receivers.
  • Long-held locks on singletons touched from multiple threads.

None of these are exotic. They accumulate naturally in a codebase that is a decade old. WizeUp’s history — 18,587 commits since May 2016 — has plenty of room for that accumulation, which is exactly why the 2026 refactor was needed.

The performance refactor (commit 3166bfe28f)

The most directly relevant commit is 3166bfe28f, a performance refactor explicitly aimed at reducing ANRs. In repository terms it touched the browser’s startup path: moving initialization off the main thread where safe, splitting one large synchronous step into coroutine-based stages, and deferring work that does not need to be done before the first frame.

The measurable target was the ANR rate reported by Google Play Vitals, which is the same signal documented in the Android Developers Core Vitals overview. We do not publish absolute Vitals numbers, but the direction of travel — fewer main-thread hogs, more work done on dispatchers — is visible in the diff.

Moxy to ViewModel (commit 1139255e9f)

A major piece of foundational work happened in commit 1139255e9f, which migrated the app from Moxy (an older MVP-style library) to Android’s ViewModel.

Why this helps performance:

  • ViewModel is lifecycle-aware and survives configuration changes without re-running work. That removes a common cause of “cold start after rotation” where the old presenter logic redid network fetches on rebuild.
  • ViewModel pairs naturally with Flow and StateFlow for state exposure. That lets the UI collect state in onStart and cancel in onStop without manual wiring.
  • It reduces the footprint of framework-specific libraries in the dependency graph, which trims method count and, at the margin, cold-start time.

Moxy did a job; ViewModel does the same job with less bookkeeping and less risk that a presenter outlives its screen. The official Google rationale for preferring ViewModel over MVP-style presenters is in the Android app architecture guide. Migrating an 18,000-commit codebase away from its old pattern is not glamorous, but it unlocks every subsequent refactor.

RxJava to Kotlin coroutines (commit d4da24b7ff)

Commit d4da24b7ff migrated async code from RxJava to Kotlin coroutines. Coroutines are not automatically faster than Rx. They are easier to reason about for cancellation and structured concurrency, which is where most real-world performance wins in an Android app come from.

Concretely, coroutines help ANR rates in three ways:

  1. Structured cancellation. When a screen is closed, all its work cancels deterministically. Rx subscriptions often leaked across lifecycles and kept doing work, or re-emitted into detached views.
  2. Fewer thread-hops. With Dispatchers.IO and withContext, the default answer is “push the work off the main thread once and return the result”, rather than chaining four operators each of which might schedule a hop.
  3. Better integration with ViewModelScope. Together with the Moxy→ViewModel migration, coroutine scopes are tied to the lifecycle that owns them. That closes the main cause of “work keeps running after the user left the screen”.

Google covers this pattern in the coroutines on Android guide. The WizeUp change is a large PR, but the principle it implements is the same one Android Developers has recommended for several years.

Push reliability (commit 46c1aea056)

Commit 46c1aea056 is labelled “push reliability”. Push is relevant to cold-start performance because notifications can wake an app into the foreground, and whatever happens in the first hundreds of milliseconds after that wake is the user’s cold-start experience.

The reliability work covers three areas common to Android push pipelines:

  • Handling the receiver path without doing network work synchronously.
  • Deduplicating pushes so the daily digest (covered in personalized feeds, trends, and daily digest) does not fight with personalized pushes for the same trend.
  • Respecting OEM battery constraints (Doze, App Standby) documented by Android in optimizing for Doze.

A browser that reliably delivers one good daily push is more useful than one that unreliably delivers many. Reliability is the feature.

Putting it in version order

These commits did not ship in isolation. They sit inside a year of release work that also includes the 6.2.0 home refactor (11fc50e4cf), the WizeUp rename (26bf5abcdb), and the 6.12.0 release (68782a0368) covered in how Fulldive Browser became WizeUp. Engineering performance work is easier to justify inside a broader product refactor, because you can rewrite the startup path once, rather than in six incremental PRs.

Limits

  • We do not publish absolute ANR or cold-start numbers. Google Play Vitals is the source of truth, and it is device-distribution dependent. Reporting a single “X% faster” number would be misleading. The direction is clear; the magnitude depends on your device.
  • A faster browser is not a safer browser. None of this performance work changes the claim-review limits covered in what an AI fake news checker can and cannot do.
  • Coroutines and ViewModel are not magic. They make good patterns easier and bad patterns harder. They do not remove the need for measurement on real devices.
  • Push is still best-effort on Android. OEM battery savers can drop notifications entirely. 46c1aea056 mitigates this; it does not eliminate it.

Why this matters for users

If you have been using Fulldive Browser or WizeUp for a long time, the cumulative effect of 3166bfe28f, 1139255e9f, d4da24b7ff, and 46c1aea056 is that the app wakes up faster after a push, rotates without redoing work, and is less likely to throw an ANR on a slow device. The AI, trends, and feed features covered in personalized feeds, trends, and daily digest need that reliability to be worth anything.

You can install WizeUp from /project/fulldive-browser/. For an overview of how Fulldive approaches long-maintained Android apps across the catalog, the Fulldive company story and projects list are the best entry points. Support is at support@fulldive.com.

Sources

Last updated: 2026-04-16. Commit hashes and version numbers are drawn from Fulldive repositories inspected on 2026-04-13.