azhon/AppUpdate: an in-app APK updater for Android that skips the storage permission
Android App update library. Android版本更新库,简单、轻量、可随意定制
At a glance
- What is it?
- AppUpdate is a Kotlin Android library that downloads an APK over HttpURLConnection and drives the install flow, with an optional built-in version-check dialog. It fits teams shipping outside Google Play or in a non-Play flavor, and it is not an app store.
- Who is it for?
- Adopt AppUpdate if you distribute an APK outside Google Play, or you keep a non-Play product flavor, and you want the download, notification and install flow handled without pulling in a third-party HTTP stack. Do not adopt it if your only channel is Google Play, where the README notes in-app updates are against policy, or if you need a server-side release console; this is a client library and the README points at UpgradeLink as a separate service for that.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 146 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem AppUpdate solves, and who it is for
An Android app that is not distributed through Google Play has no update channel. The user installed an APK from your site or a partner store, and nothing on the device will tell them a newer build exists. The usual answer is to write the same code again in every project: fetch a version manifest, compare version codes, show a dialog, download the file, fire a notification, launch the package installer, clean up the leftover APK.
AppUpdate packages that loop. The README describes it as an Android version update library that is simple, lightweight and freely customizable, and the feature list backs the claim in a narrow way: it handles background download, forced updates, a notification progress bar adapted through Android 13, Chinese, Traditional Chinese and English strings, and a cancel path. The download itself uses HttpURLConnection, so the library does not drag in OkHttp or another HTTP client.
The audience is specific. You are shipping an APK on a channel you control, you have a server endpoint that returns the latest version code, name, size and description, and you want that data turned into a working update prompt. The README's own note about Google Play policy, which forbids in-app updates, is the clearest statement of who this is not for. Teams that publish to Play only should stop reading here.
Two modes: automatic version check or plain downloader
The core behaviour hinges on one builder call. The README states that if you call apkVersionCode(), the library compares that value internally and decides on its own whether to show the dialog, download and install. If you never call apkVersionCode(), the library stops being an update checker and becomes a downloader: it downloads and installs, and you decide when.
That split is the most useful design decision in the project. The second mode is what you want when your version check lives somewhere else, for example inside an existing settings screen or a push-triggered flow, and you only need the download-and-install half.
Three parameters pair with apkVersionCode() when you use the automatic path. The README marks apkVersionName, apkSize and apkDescription as required alongside it, which makes sense because the built-in dialog has to render a version label, a file size and release notes. If your backend returns those fields, the mapping is direct. If it returns something else, you are either formatting on the client or supplying your own dialog.
Two flags change the flow further. showNotification(true) produces a notification progress bar, and the README notes that on Android 13 the upgrade button also requests notification permission, continuing the download whether or not the user grants it. forcedUpgrade(true) makes the dialog show a download progress bar instead of a simple prompt.
Installing AppUpdate and wiring a first update check
The dependency comes from Maven Central. The README shows the coordinate with version 4.3.6, which matches the badge at the top of the page.
implementation 'io.github.azhon:appupdate:4.3.6'Then build a DownloadManager. The README's Kotlin example uses the Builder with a receiver block, sets the APK URL, the local file name, the small icon, and the version fields that trigger the automatic check.
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()What the reader should see: after download() runs, the library compares the supplied version code against the installed one and, when the supplied code is higher, presents the update dialog. Tapping the upgrade button starts the download, and a notification appears in the tray.
One ProGuard detail is documented and worth copying literally. The README says to keep Activity and Service subclasses and gives exactly two rules.
-keep public class * extends android.app.Activity
-keep public class * extends android.app.ServiceThe README points to MainActivity.kt in the demo module for the fuller set of options, and there is a downloadable demo APK attached to the demo release tag.
Android 10 background Activity limits and the notification you cannot skip
Android 10 restricts an app from starting an Activity while it is in the background, and this breaks the naive install flow: the download finishes, the app calls the installer, and nothing appears. The README is explicit that because of this restriction, the library sends a notification to the tray when the download completes, and that this happens regardless of the showNotification value, provided notifications are permitted.
That is a constraint, not a feature. You cannot design an update flow that installs silently in the background on modern Android. The user will see a notification and will have to act on it. If your product plan assumed otherwise, AppUpdate will not deliver it, and neither will any other library, because the limit is in the platform.
The notification also means the user can ignore it. There is no documented escalation if they never tap. Forced updates are handled at the dialog level, which is a prompt the user sees while the app is in the foreground, not a mechanism that survives the app being closed.
A second documented edge case concerns orientation. The README's FAQ says that when the app is set to landscape, the install cannot be pulled up after download completes, and the fix is a Manifest attribute on the relevant Activity.
android:configChanges="orientation|screenSize|keyboardHidden"That is a configuration workaround rather than a library fix, and it is the kind of detail that only shows up on real devices.
Custom downloads, MD5 checks and cleaning up the old APK
The library is meant to be replaceable in pieces. The README says that to implement your own download process you extend BaseHttpDownloadManager, and shows the class declaration as the starting point.
class MyDownload : BaseHttpDownloadManager() {}That matters if you need a different transport, custom retry logic, or a download that reports progress somewhere other than the built-in dialog. The default path uses HttpURLConnection, which keeps the dependency graph small but also means you inherit whatever that client does, not the connection pooling and interceptor stack of a modern HTTP library.
Two smaller conveniences are documented. You can set the APK's MD5 on the Builder so the library verifies the package and avoids downloading the same file twice. And after the new version opens, you can delete the old installer file with a utility call the README shows with the library's default cache path.
val result = ApkUtil.deleteOldApk(this, "${externalCacheDir?.path}/appupdate.apk")Text is overridable too. The README states that the library ships internationalized strings and that you can override any of them by declaring the same name in your own string.xml. That is a low-friction way to change dialog wording without forking.
The googlePlay-no-op flavor and what it actually removes
The repository contains a second module, appupdate-no-op, alongside the main appupdate module. The README explains the reason: Google Play policy prohibits in-app updates, so projects that ship to Play and to other channels can use product flavors and swap the dependency.
android {
productFlavors {
other {}
googlePlay {}
}
}
dependencies {
otherImplementation 'io.github.azhon:appupdate:latest-version'
googlePlayImplementation 'io.github.azhon:appupdate-no-op:latest-version'
}The no-op artifact is the interesting part of the design. It lets the same source tree compile for both flavors, so the update calls in your code do not need to be wrapped in build flags or reflection. The README describes it as a version with no implementation. The trade-off is that the Play build silently does nothing at the update call site, which is what you want for policy compliance but also means a bug in that branch will not surface in your Play testing.
Note the version placeholder. The README writes latest-version in the flavor example while the install step pins 4.3.6. Pin the same version in both places.
Where AppUpdate is the wrong tool, and what to use instead
AppUpdate is a client-side library. It has no server, no release console, no staged rollout, no analytics on who updated. If you need to know what fraction of users are on each version, or to roll a build back, you need a backend and this library is only the last mile.
The README itself points at one such service, UpgradeLink, with a link to its Android integration example. That is the honest comparison: AppUpdate gives you the on-device mechanism, and a hosted release service gives you the version manifest, targeting and reporting around it. Choosing between them is choosing whether you want to run that endpoint yourself. If you already have an API that returns the latest version code, AppUpdate is the smaller piece.
The other alternative is the platform. Google Play's own in-app update API does the same job for Play-distributed apps, and the README's policy note is precisely why this library exists as a separate thing. If your app is on Play only, use Play's mechanism; AppUpdate cannot help you there and the no-op flavor exists to make sure it does not try.
Finally, treat the demo release date with some care. The most recent tagged release in the list is the demo APK from 2024-08-23, while the versioned releases 4.2.2 and 4.2.0 date from 2022. The README's changelog covers 4.3.6 and links to the wiki for the rest, so the release list on the repository page does not tell the whole version story. The last push to the default branch was on 2026-05-09, so the code is being touched, but the tagged release cadence is not the thing to judge it by.
Editorial conclusion
Adopt AppUpdate if you distribute an APK outside Google Play, or you keep a non-Play product flavor, and you want the download, notification and install flow handled without pulling in a third-party HTTP stack. Do not adopt it if your only channel is Google Play, where the README notes in-app updates are against policy, or if you need a server-side release console; this is a client library and the README points at UpgradeLink as a separate service for that. Verify first that your minSdk is at least 16, that your target device set behaves with the Android 10 background-Activity restriction the README calls out, and that your obfuscation config keeps Activity and Service subclasses, since the README gives those two keep rules as the whole ProGuard story.
Frequently asked questions
How do I force an update in AppUpdate?
Set forcedUpgrade(true) on the DownloadManager.Builder. The README states that when forced upgrade is enabled, the dialog that appears shows a download progress bar. The dialog itself is what the user sees, so the prompt still depends on the user acting on it.
How do I update an APK file with AppUpdate?
Build a DownloadManager with apkUrl, apkName and smallIcon, then call download(). If you also call apkVersionCode(), the library compares versions and decides whether to show the dialog, download and install; without it, the library only downloads and installs.
Does AppUpdate need the storage permission?
The README lists no storage permission requirement among the features. It does note that on Android 13, when showNotification(true) is set, tapping the upgrade button requests notification permission and the download continues whether or not it is granted.
Can I use AppUpdate if my app is on Google Play?
The README states that Google Play policy prohibits in-app updates, which is why the project ships an appupdate-no-op artifact. The documented approach is product flavors, with the real library in a non-Play flavor and the no-op version in the googlePlay flavor.
What is the minimum Android version AppUpdate supports?
The README's feature list says Android 4.1 and above, and the badge at the top of the page reads miniSdk 16+. Notification handling is documented as adapted through Android 13.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/azhon-appupdate)