# AppUpdate: one version-code switch, a no-op Play artifact, and tags that stop at 4.2.2

> The Kotlin library treats apkVersionCode as the switch between showing an update dialog and behaving as a plain downloader, ships a second module with no implementation for Google Play flavored builds, and pins a coordinate the visible release list does not contain.

**azhon/AppUpdate** — Android App update library.  Android版本更新库，简单、轻量、可随意定制

- Repository: https://github.com/azhon/AppUpdate
- Stars: 2,467 · Forks: 358
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/azhon-appupdate

## One field decides whether a dialog appears or only a download runs

Call `apkVersionCode(...)` and the library works out on its own whether a person needs to update. Leave it out and the same object behaves like a plain downloader that fetches the package and installs it. Both builder samples in the docs set the same five values before the optional ones, and the three after the version code are marked as required together with it:

```java
val manager = DownloadManager.Builder(this).run {
    apkUrl("your apk url")
    apkName("appupdate.apk")
    smallIcon(R.mipmap.ic_launcher)
    //设置了此参数，那么内部会自动判断是否需要显示更新对话框，否则需要自己判断是否需要更新
    apkVersionCode(2)
    //同时下面三个参数也必须要设置
    apkVersionName('v4.2.2')
    apkSize("7.7MB")
    apkDescription("更新描述信息(取服务端返回数据)")
    //省略一些非必须参数...
    build()
}
manager?.download()
```

Two entries in that block matter more than the rest. `apkUrl("your apk url")` is a literal placeholder, so the sample compiles and runs without ever fetching anything. And `apkSize("7.7MB")` next to `apkDescription(...)` are strings the caller passes in, not figures read back from the transfer, which is why the trailing comment says the description comes from what the server returned. The size printed to a user is whatever the app decided to write there. Setting `forcedUpgrade(true)` is what adds a progress bar inside the dialog; it does not change what gets fetched or who gets it.

## The Play flavored build resolves to a module with no implementation

The documentation states the store restriction instead of hiding it. In app updates conflict with Google Play policy, so the project publishes two modules and the sample wires two product flavors:

```groovy
android {
    //...
    productFlavors {
        other {}
        googlePlay {}
    }
}

dependencies {
    otherImplementation 'io.github.azhon:appupdate:latest-version'
    googlePlayImplementation 'io.github.azhon:appupdate-no-op:latest-version'
}
```

Both `appupdate/` and `appupdate-no-op/` exist as source directories at the top level of the tree, so the split is real and not just a naming convention. What no sample shows is what the empty module does at runtime. No error, no log line and no build failure are described for it, so a Play flavored build that still calls the same builder is the case with no documented outcome. Both flavor coordinates end in `latest-version`, a dynamic version string, and it is the only place in the setup where the resolved artifact can change without a code edit. The two flavors resolve independently, so a Play build and a non Play build taken on different days are not guaranteed to line up.

## The Android 13 permission prompt arrives after the download has started

With `showNotification(true)` set, the runtime permission request is raised when the person taps the upgrade button inside the update dialog, and the download continues whether that permission is granted or refused. The core logic note states this as fixed behavior rather than as something the caller configures, and the feature list repeats it as support for the notification progress bar adapted up to Android 13. The same note records that a forced update dialog carries the progress bar, so the two settings touch the same screen in different ways.

What the sample does not show is any callback carrying the permission outcome. That leaves the ordinary questions unanswered for a reader: whether a refusal is surfaced to the user, whether the progress notification is simply absent, and whether a refusal changes anything about the file already on disk. One detail is clear from the surrounding text: the request happens while the transfer is under way, not before it, so the prompt is not a gate on the fetch. Android 13 is only the release where the platform asks at runtime at all.

## The completion notice is the one notification the caller cannot switch off

Android 10 restricts starting an activity from the background, so a finished download cannot quietly bring up the installer. The project's answer is a notification in the shade, and the docs say it is posted regardless of the value of `showNotification`, with the user needing to allow notifications for it to arrive. That is the opposite rule from the previous section, where the same setting does control behavior, and it is easy to read the two as one switch when they are not.

Cancel behavior points the same way. A cancelled download removes the notification if one was posted, which means the shade entry is a live object tied to the transfer rather than a static message. Two paths write to the shade in this library, the first one optional and the second one mandatory. A caller who wants a quiet update path has to account for a message the library posts on its own, and for the platform permission that message now depends on.

## The pinned coordinate sits above the newest tag in the release list

Setup asks for `implementation 'io.github.azhon:appupdate:4.3.6'`, and the changelog dates v4.3.6 to 2024/10/22 with one entry under it, a change to the access permission of the `release()` function. The releases visible for this repository are `demo` from 2024-08-23, `V4.2.2` from 2022-08-05 and `V4.2.0` from 2022-07-05. The newest versioned tag on that list is therefore a 4.2.x release from three years before the changelog date, and 4.3.6 is not among them.

The demo tag is a separate line of its own and it predates the changelog entry beside it. The newest commit on the default branch is dated 2026-05-09, later than both. The tree carries publishing scaffolding next to the demo app, `maven.gradle`, `maven_publish.sh` and `gradle.properties`, plus a wrapper for both platforms and an `img/` directory for documentation assets. None of this says how a given coordinate was distributed, and the two snippets in the docs are the only versions written down anywhere in the file.

## The keep rules cover every Activity and Service in the application

The third setup step is two ProGuard lines, described as everything needed to keep `Activity` and `Service` from being obfuscated:

```groovy
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
```

Neither pattern carries a package qualifier, so both apply to every class in the application extending those framework types, not only to the components the library registers for itself. No narrower rule is given for the update dialog, the download service, or any receiver the library depends on, and no note says which of them actually needs to survive shrinking. An application that wants a tight keep list has to work out its own.

The same tree that carries those instructions carries `app.jks` at the top level, beside the `app/` demo module, and the docs never mention the file. The name follows the convention for a Java keystore, and it sits alongside `.gitignore`, `LICENSE`, `build.gradle` and the two library modules with nothing explaining its part. Anyone reading this tree should not assume such a file is unused, or safe to reach for when signing another application.

## Cleanup is a filename lookup, and duplicate fetches are gated on an MD5

Three small mechanisms sit around the transfer. Repeat downloads are avoided by setting the package MD5 on the builder, so the library can compare what it already has against what is being asked for. Cleaning up afterwards goes through a static helper with an explicit path:

```java
//旧版本apk的文件保存地址
val result = ApkUtil.deleteOldApk(this, "${externalCacheDir?.path}/appupdate.apk")
```

That path is the external cache directory plus the file name from the builder sample. Change `apkName` and the cleanup call stops pointing at the file that was downloaded, which is a coupling the sample never states. Replacing the transfer entirely means subclassing and leaving the body empty:

```java
class MyDownload : BaseHttpDownloadManager() {}
```

A landscape bug gets a one line answer in the troubleshooting section: when the app is fixed to landscape the download finishes but the installer never comes up, and the fix is a manifest attribute on the relevant activity.

```xml
 android:configChanges="orientation|screenSize|keyboardHidden"
```

## Chinese first, English in a second file, contacts in the body

The primary file is written in Chinese and the English version is a separate file at the repository root, linked from the first line of the Chinese one. The first substantive line of that text sends Flutter developers to `flutter_app_update`, a different repository, so the Kotlin library and its cross platform wrapper are maintained as separate projects with separate release histories.

Support contacts sit in the body of the document rather than in a tracker section: a WeChat handle with a note to mention AppUpdate, and two QQ group numbers, one of them marked full. A separate block points at `upgrade.toolsetlink.com` for anyone who needs the hosted UpgradeLink service, and the donation section closes with a QR code and a wiki page of past supporters. The screenshot heading is followed by three indented blank lines with no image references in the visible text, even though the tree carries an `img/` directory. Two developer.android.google.cn links are included as background reading on background activity starts and on notification handling.

## Conclusion

AppUpdate stays small on purpose: the transfer runs on HttpURLConnection with no third party framework pulled in, and the Android 4.1 floor, the notification rules and the manifest fix are all written down. That makes it readable in an hour for any app that already serves its own package URL. Before adopting it, resolve the coordinate you actually get, because the setup line, the changelog date and the visible tags do not agree, and treat the empty Play flavored artifact as something to verify inside your own build rather than something the documentation explains. Flutter projects have a separate repository for this.

## FAQ

### What does azhon AppUpdate actually do on Android?

It is a Kotlin library that downloads an APK over HttpURLConnection and hands it to the system installer. Set apkVersionCode on the builder and it also decides whether to show the update dialog; leave it out and it only downloads and installs.

### Does AppUpdate need storage permission to download an APK?

The feature list states that no storage permission is needed, and that the download runs on HttpURLConnection with no third party framework integrated.

### Which Android versions does AppUpdate support?

The checklist says Android 4.1 and above. Notification handling is adapted for Android 13 runtime permission, and a completion notice is posted because Android 10 limits starting an activity from the background.

### What has to be kept when building AppUpdate with code shrinking?

The third setup step gives two ProGuard lines, one keeping public classes that extend android.app.Activity and one keeping those that extend android.app.Service, with no package qualifier on either.

### Is there an AppUpdate release for Flutter projects?

Not in this repository. The Chinese documentation tells Flutter developers to use flutter_app_update, which is a separate project maintained on its own release history.

## Sources

- [azhon/AppUpdate on GitHub](https://github.com/azhon/AppUpdate)
- [Issues](https://github.com/azhon/AppUpdate/issues)
- [License: Apache-2.0](https://github.com/azhon/AppUpdate/blob/main/LICENSE)
- [README](https://github.com/azhon/AppUpdate/blob/main/README.md)
- [Releases](https://github.com/azhon/AppUpdate/releases)

---

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