# Median Browser: a 215 KB WebView browser whose licence reserves every right

> Median is a Java Android browser built on the system WebView, with local ad blocking, a Keystore-backed password vault, user scripts and no account or sync server. The catch is the licence, which the project itself describes as source-visible rather than open source.

**bi-box/Median** — 一个功能密度极高的，安装包仅200kb的极致轻量化浏览器

- Repository: https://github.com/bi-box/Median
- Stars: 534 · Forks: 24
- Language: Java
- License: NOASSERTION
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/bi-box-median

## The licence is source-visible, and the project says so itself

Median's source is public and readable, but the project is unusually direct about what that does not mean. Its own quick answers state that the code is visible under an all-rights-reserved licence and that this is not an open source agreement in the MIT or GPL sense, and they point readers at the LICENSE file before any redistribution. GitHub's own licence detection returns NOASSERTION for the repository, which matches: there is no recognised licence identifier to report.

That distinction matters more here than it would for a typical utility, because a browser is exactly the kind of software people fork and ship. The repository also carries THIRD_PARTY_NOTICES.md, SECURITY.md, PRIVACY_POLICY.md and PLAY_DATA_SAFETY.md, which reads like a project preparing for distribution and review rather than one avoiding it. Reading the LICENSE file is the step to take before assuming you may modify and redistribute any part of this codebase.

## The 215 KB package is a direct consequence of riding the system WebView

Median renders with the Android System WebView rather than bundling a Chromium fork, and it keeps a compatibility layer of its own under app/src/main/java/androidx/webkit for the platform APIs it needs. Everything else follows from that choice. The project's own comparison puts the install at roughly 215 KB against the tens to hundreds of megabytes a self-contained browser carries, and states plainly that the rendering engine follows system updates.

The cost of that saving is the same fact stated from the other side: the engine is whatever the device ships, so rendering behaviour can change without any change to Median. The presence of WEBKIT_COMPATIBILITY.md and CHANGELOG.md at the top level is the project acknowledging that boundary. The build targets Android 8.0 (API 26) and above, compiles and targets SDK 36, and is written in Java 17 on Gradle with AGP 8.13.2. The namespace is com.xinyv.median while the shipped applicationId is com.xinyv.median.compat.

## Ad blocking runs on ABP rules, and filter subscriptions are the one outbound call

AdBlockEngine is a filtering engine built on ABP and hosts rules, adapted to the request callbacks a WebView exposes rather than to a full browser's networking stack. It does element hiding, strips tracking parameters from requests, and can be toggled per site. FilterSubscriptionStore manages online filter list subscriptions, with custom sources that the user adds and updates.

Those subscriptions are the honest caveat in an application that otherwise runs no backend of its own. A filter list is fetched from a third party, so the project does make outbound requests that its own privacy policy does not cover, and the same applies to whichever search engine the user picks and to whatever sites they visit. The privacy claim is about the absence of a developer-operated server, not about the absence of network traffic. Everything else in the module table runs with no permission beyond the ordinary ones.

## The password vault is Keystore plus AES-GCM, and autofill crosses Shadow DOM

PasswordVault stores credentials locally, encrypted with a key held in the Android Keystore and sealed with AES-GCM. HTTPS autofill is on by default, and the vault prompts to save after a login completes. CredentialAutofill generates the injection script that fills those credentials, and it is built to handle the cases that break naive implementations: multiple accounts on one site, multi-step login flows, Shadow DOM elements, and same-origin iframes.

PortableBackupCodec handles export, encrypting the backup with a password the user chooses and deriving its key with PBKDF2, so a backup file is not readable without that password and no server is involved. This is the part of the design worth crediting: the encryption boundaries sit in the platform keystore rather than in application code, and the one artefact that leaves the device is separately encrypted rather than merely protected by device-level storage.

## The private window announces itself when isolation cannot be guaranteed

PrivateActivity is a separate session fully isolated from ordinary tabs, running across its own Activity process boundary, with its contents cleared when it exits. What makes it worth reading is the stated rule attached to it: when reliable isolation is not available, the browser does not present itself as an incognito mode and leave the user to assume otherwise. It tells them the guarantee it cannot currently make.

Most privacy features in consumer browsers fail quietly, because the label is a promise and the implementation is best-effort. Refusing to show the label is the difference between a feature and a claim. The same honesty shows up elsewhere: TlsInspector probes certificates only when the user asks, showing the chain, cipher suites, issuer and validity dates, and performs no background listening at all.

## Downloads survive leaving the app because a foreground service keeps them alive

DownloadCenterActivity manages segmented downloads with pause, resume, breakpoint continuation and an adaptive retry policy. AdaptiveDownloadService is a foreground service, which is what keeps a large transfer alive after the user switches away from the browser. The download store has its own content provider, so installed packages and other files can be managed as files rather than only as browser history entries.

That foreground service is also the one place in the module table where Median asks for a permission beyond the ordinary set: the ongoing notification permission Android requires to run a foreground service. The notification is not decoration, it is the mechanism, and dismissing it ends the guarantee that a download will survive the app being backgrounded. Offline archiving follows the same local-only rule: pages are stored as MHTML, with the page bytes never leaving the device, alongside reader mode, text to speech, translation and find-in-page.

## A read-only media indexer parses manifests without touching the stream

MediaResourceSniffer builds a bounded, read-only index of media resources, parsing HLS, DASH and Smooth Streaming manifests and reporting tracks, resolution, bitrate, encryption and whether an item is live or on demand. It indexes metadata; it does not download or decode the stream, which is what keeps the feature affordable on a browser that is already carrying a page.

Alongside it sit three more inspection tools. PerformanceMonitor measures frame time and dropped frames in real time, RendererWatchdog detects an unresponsive renderer process and recovers it, and three render strategies are switchable between performance, standard and power saving. EdgeNavigationController maps edge swipes to back and forward, and SiteSettingsStore holds per-site permissions for JavaScript, images and location. Taken together these are the parts of the codebase that treat a WebView host as a hostile environment rather than a given.

## A signed release needs four environment variables before Gradle will run

Building requires Android Studio on its latest stable channel, JDK 17 or newer, and an Android SDK with compileSdk 36. Clone and install as usual:

```bash
git clone https://github.com/bi-box/Median.git
cd Median
```

A debug build is one Gradle invocation:

```bash
./gradlew assembleDebug
```

A release build needs the signing material supplied through the environment first, as MEDIAN_KEYSTORE, MEDIAN_STOREPASS, MEDIAN_KEY_ALIAS and MEDIAN_KEYPASS, and then the same wrapper:

```bash
./gradlew assembleRelease
```

Both land in app/build/outputs/apk/. R8 runs with minifyEnabled and shrinkResources, which is part of how the package stays small, and the repository root also carries build.sh and build-update.sh alongside the Gradle wrapper for scripted builds. A CHANGELOG.md, RELEASE_CHECKLIST.md and GOOGLE_PLAY_LISTING.md sit at the top level, and the published tags are irregular: 1.4.0 on 2026-07-14, 2.1.9 on 2026-07-17, and 2.3.0 on 2026-08-14.

## Conclusion

Median is a credible choice for an Android phone where install size, ad blocking and local data storage matter more than cross-device sync, and the engineering is real: a Keystore-backed vault, a document-start script bridge, a read-only media indexer and a renderer watchdog, all in a package the project sizes at roughly 215 KB. Two things have to be settled before you build on it. Read the LICENSE file, because public source and a redistributable licence are different claims and the project is explicit that this is not the second kind. And confirm that whatever WebView your users' devices ship behaves the way the code assumes, because that engine is the one component the project does not control.

## FAQ

### Is Median Browser open source?

Not in the MIT or GPL sense. The project describes its own licence as source-visible with all rights reserved and tells readers to consult the LICENSE file before redistributing. GitHub reports the repository's licence as NOASSERTION.

### How large is the Median Browser install?

The project's own comparison puts it at roughly 215 KB, against tens to hundreds of megabytes for a browser that bundles its own Chromium fork. The size comes from rendering with the Android System WebView instead.

### Does Median Browser send data to its developers?

There is no developer account, sync server, ad network, analytics or telemetry backend. Sites you visit, the search engine you pick and the ad filter subscription lists you subscribe to all receive requests you initiated, because that is how a browser works.

### Which Android versions does Median Browser support?

Android 8.0 (API 26) and above. The project compiles and targets SDK 36, is written in Java 17, and builds on Gradle with AGP 8.13.2.

### How do I build a signed Median Browser release?

Set MEDIAN_KEYSTORE, MEDIAN_STOREPASS, MEDIAN_KEY_ALIAS and MEDIAN_KEYPASS in the environment, then run ./gradlew assembleRelease. Debug builds need no signing material. Both outputs land in app/build/outputs/apk/.

### Does Median Browser have a private browsing window?

Yes, a PrivateActivity running behind its own Activity process boundary and cleared on exit. The project states that when reliable isolation is unavailable it says so instead of labelling the window as incognito anyway.

## Sources

- [bi-box/Median on GitHub](https://github.com/bi-box/Median)
- [Issues](https://github.com/bi-box/Median/issues)
- [README](https://github.com/bi-box/Median/blob/main/README.md)
- [Releases](https://github.com/bi-box/Median/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bi-box-median
