# ZhihuPaper: an account-free Android reader for the Zhihu Daily feed

> ZhihuPaper is a small Java Android client for the Zhihu Daily feed that keeps favourites, notes and highlights on the device and ships no ad, analytics or push SDK. It is well engineered around its constraints, and it depends entirely on an upstream API it does not control.

**cundong/ZhihuPaper** — A lightweight Android daily-news reader focused on private, local-first reading.

- Repository: https://github.com/cundong/ZhihuPaper
- Stars: 935 · Forks: 404
- Language: Java
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/cundong-zhihupaper

## A reader with no login, no ads and no analytics

ZhihuPaper, branded in-app as 新奇日报, is a lightweight Android client for the Zhihu Daily feed. The project description calls it a lightweight, account-free, ad-free reader built around private, local-first reading, and the README repeats the same three promises: no login is required, no ads, statistics or push SDKs are integrated, and favourites, read-later items, tags, short notes, highlights and reading state all live on the device. Android system backup is switched off, so that local library is not silently copied into a cloud backup either.

The audience is specific: someone who follows the daily digest, reads it on a phone, and does not want to hand a reading habit to a third party. The capability list follows from that. You can browse the latest edition and load previous editions by date, restore a reading position, open a table of contents, move continuously between adjacent stories, and search a local library of saved items. There is a dark theme, a text-first mode that reduces mobile data usage, and image preview with zoom and saving to the system gallery.

The engineering description is unusually concrete for a small app, which makes it easy to judge whether the design holds up. It is worth reading the configuration table rather than the feature list, because that is where the trade-offs are visible.

## A constrained WebView over local templates, not a browser

Article bodies are rendered in a constrained WebView. The README states the content is shown through WebViewAssetLoader and local templates, that article images are mapped to controlled HTTPS addresses, and that the private image cache is served from a private cache directory. The network layer uses system certificate validation and contains no logic that trusts all certificates. Plaintext traffic is prohibited.

That is the security core of the project, and it is a different approach from simply opening the upstream article URL in a browser engine. The article HTML is sanitized and stitched into a local template, so the WebView loads content the app itself assembled rather than a remote page with its own scripts. The README states that cleartext network traffic is prohibited, that images are loaded over the HTTPS addresses returned by the article body, and that the WebView is used with WebViewAssetLoader plus local templates so the private image cache can be mapped to controlled HTTPS addresses.

The images are handled with the same care. The README says image downloads validate the response type and the actual content before persistence, which is the difference between storing a picture and storing whatever a redirect served you.

The module layout is small and intentional: :app holds the main application, its unit tests and its instrumentation tests, while :photoview is an in-tree image zoom component migrated to AndroidX. Release builds enable R8 code shrinking and resource shrinking by default and package only the Chinese application resources.

## Building a debug APK from a clean checkout

The build requirements are spelled out: macOS, Linux or Windows, JDK 21, Android SDK Platform 36, and Android SDK Build-Tools for the release signing check. The repository ships a Gradle Wrapper, so there is no need to install Gradle separately. The point the project insists on is local.properties, which must point at the local SDK:

```properties
sdk.dir=/path/to/Android/sdk
```

That file is machine local, is already ignored by Git, and should not be committed. The build JDK is fixed at 21 by the .java-version file, while the source and bytecode stay compatible with Java 8. On macOS you can point the environment at it:

```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
```

Android Studio's Gradle JDK should also be set to 21. The README warns that the Java 8 target warning under JDK 21 is kept deliberately and does not mean the build failed, which is the kind of note that saves an afternoon.

With the SDK path in place the debug build is one Gradle invocation:

```bash
./gradlew --no-daemon :app:assembleDebug
```

The resulting APK lands at app/build/outputs/apk/debug/app-debug.apk, and installing it on a chosen emulator or device uses adb with the device serial:

```bash
adb -s <serial> install -r app/build/outputs/apk/debug/app-debug.apk
```

## Release signing, the 2.75 MiB ceiling and the gate scripts

Release builds need a keystore, and the project keeps it out of the repository. You copy the template and fill in local values:

```bash
cp keystore.properties.example keystore.properties
```

The file can also live outside the repository and be pointed at by the ZHIHUPAPER_KEYSTORE_PROPERTIES environment variable. A relative storeFile path in the configuration is resolved against the directory holding that configuration file. Real keys, passwords, keystore.properties, APKs and AABs are all ignored by Git and must not be committed.

The release build itself is:

```bash
./gradlew --no-daemon clean :app:assembleRelease
```

producing app/build/outputs/apk/release/app-release.apk.

What is more interesting is the gate. The README documents two scripts: scripts/verify-ci.sh for a developer or CI check that needs no signing key, and scripts/verify-release.sh as the full local release gate. The latter builds the release from a clean directory, runs the JVM tests, App Lint and a debug build, and then checks that the release APK exists, that it contains no archives, keys or local configuration, that it does not exceed the 2.75 MiB limit the project sets, and that apksigner can verify its signature. The build itself refuses to package archives, keys, signing configuration or local.properties into assets, so that rule is enforced in two places.

GitHub Actions runs the same entry point on push and pull request: debug build, JVM tests, Debug and Release Lint, and compilation of the instrumentation APK. The README is explicit that it does not publish automatically, and that compiling the instrumentation APK is not the same as running it on a device.

## Offline sync through WorkManager with a capped image cache

Offline behaviour is handled by WorkManager, which schedules the sync work and owns its constraints and lifecycle. When the network fails the app falls back to whatever is already cached on the device, and the body image cache is size limited. The README also notes that data which can be re-downloaded is cleaned up separately from the user's reading library, so a cache eviction never takes a favourite with it.

That separation is the design decision worth copying. Most reader apps treat storage as one bucket and lose saved articles when a cache trim runs. Here the library is SQLite at local database version 9, and the cache has its own lifecycle.

The honest limit is that automatic offline work is not scheduled precisely. The known boundaries section states that automatic offline tasks are affected by Android background scheduling, network constraints and battery saving policies, and that exact execution time is not guaranteed. So the offline mode is a fallback, not a synchronisation contract. On a device with aggressive battery restrictions, the sync may simply not run when you expect.

For development, the data layer is exercised through JVM unit tests and instrumentation tests:

```bash
./gradlew --no-daemon :app:testDebugUnitTest
```

```bash
ANDROID_SERIAL=<serial> ./gradlew --no-daemon :app:connectedDebugAndroidTest
```

The README asks for manual regression on the target Android version whenever UI, network, database or background work changes, covering startup, list, detail, library, continuous reading, offline and sharing flows.

## The upstream API can change without notice

The application fetches lists and article bodies from https://news-at.zhihu.com and loads images from the HTTPS addresses the body returns. The README is direct about the division of responsibility: the availability, response format and content of the upstream interface are not controlled by this project, so when the network drops or the service changes, only the cache already on the device is available.

That is the single biggest risk in adopting the app, and the project states it in its own known boundaries list: the upstream daily API is not maintained by this project and may change or stop without notice. No offline-first reader can remove that dependency, but the difference is whether your reader degrades to cached content or breaks. ZhihuPaper degrades.

The second boundary is deep links. The app handles http(s)://daily.zhihu.com/story/... and zhihudaily://story/... links. It cannot publish an assetlinks.json file on the upstream domain, so the README states plainly that only ordinary deep links are offered and that Android Verified App Links are not claimed. An installer who expects the browser to hand the link straight to the app will see the browser instead.

The remaining limits are smaller but real: the interface and application resources are currently Chinese only, there is no account and no cloud sync, uninstalling the app deletes the local reading data, and sharing goes through the system share sheet, Evernote, or those deep links. Nothing here depends on the user accepting a login, which is the point of the design.

## Apache 2.0 covers the code, not the Zhihu content

The licence position needs two sentences rather than one. The licence field on the repository says Apache-2.0, and the tree carries a LICENSE file alongside PRIVACY.md and THIRD_PARTY_NOTICES.md. But the project also states prominently that it is an independent open source client, not an official Zhihu product and not affiliated with Zhihu, and that the upstream API, articles, images and trademarks remain the property of their respective owners and are not covered by the project's Apache License 2.0.

So the Apache grant covers the source code in this repository and nothing about the content the app displays. THIRD_PARTY_NOTICES.md is where the third party components and content statements live, and it is a required reading for anyone who plans to redistribute a build rather than install it.

On maintenance, the repository is not archived and the last push was on 2026-09-06. There are no GitHub releases for the project at all, so version tracking happens through the repository rather than through a tag list. That absence of tagged releases is worth keeping in mind if you need to pin a specific state of the code: there is no release artefact to point at, only a commit.

## Conclusion

ZhihuPaper suits readers who want the Zhihu Daily feed on Android without an account, without ad or analytics SDKs, and with their library stored on the device. It does not suit anyone who needs verified deep links, cloud sync across devices, or a client that survives the upstream API changing shape, because the app itself lists that API as outside its control. Before adopting, confirm the build works on your toolchain with ./gradlew --no-daemon :app:assembleDebug, read PRIVACY.md and THIRD_PARTY_NOTICES.md for the data and dependency position, and check that the Zhihu Daily endpoint at https://news-at.zhihu.com is reachable from your network.

## FAQ

### Does ZhihuPaper require a Zhihu account?

No. The README states the app requires no login and does not integrate ads, statistics or push SDKs, and that favourites, read-later items, tags, notes, highlights and reading state stay on the device with Android system backup disabled.

### How do I build ZhihuPaper from source?

You need JDK 21 and Android SDK Platform 36, and a local.properties file pointing at the SDK with sdk.dir set. Then run ./gradlew --no-daemon :app:assembleDebug, which produces app/build/outputs/apk/debug/app-debug.apk.

### Does ZhihuPaper work offline?

Yes, with limits. WorkManager schedules offline synchronisation and the app falls back to cached content when the network fails, but the README states that automatic offline tasks depend on Android background scheduling, network constraints and battery saving policies, so exact execution time is not guaranteed.

### Are ZhihuPaper links Verified App Links?

No. The app handles regular deep links such as http(s)://daily.zhihu.com/story/... and zhihudaily://story/..., but because it cannot deploy assetlinks.json on the upstream domain, the README states these links are not advertised as Android Verified App Links.

### What permissions does ZhihuPaper ask for?

The current code declares network and network state permissions, and declares the storage write permission needed to save images only on Android 9 and below, requested on Android 6 to 9 when you tap save. It includes no account, cloud sync, ad, push, location, contacts, camera, microphone or analytics SDK.

## Sources

- [cundong/ZhihuPaper on GitHub](https://github.com/cundong/ZhihuPaper)
- [Issues](https://github.com/cundong/ZhihuPaper/issues)
- [License: Apache-2.0](https://github.com/cundong/ZhihuPaper/blob/master/LICENSE)
- [README](https://github.com/cundong/ZhihuPaper/blob/master/README.md)

---

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