如何为非应用商店上架、存于Google Drive的App实现版本更新提示?
实现私用App的版本检测与手动下载方案
嘿,这个需求太贴合私用小圈子App的场景了!我给你整理一套简单可靠的实现方案,全程不需要依赖Google Play,只用Google Drive和Android原生API就能搞定:
核心思路
我们需要三个关键环节:
- 在Google Drive上托管版本号配置文件和最新APK安装包
- App端请求配置文件,对比本地版本号判断是否需要更新
- 弹出提示对话框,用户点击按钮后触发APK下载并引导安装
第一步:Google Drive文件配置
首先你得把两个文件传到Google Drive并配置可访问链接:
version.txt:纯文本文件,内容只有最新版本号(比如1.0.3,格式要统一,建议用三段式x.y.z)- 最新的APK安装包(比如
NotesApp_v1.0.3.apk)
转换为可直接下载的链接
Google Drive默认的分享链接是预览页,不能直接下载,需要把链接转换成下载格式:
- 分享文件时设置为「知道链接的任何人都能查看」
- 复制分享链接的
FILE_ID(比如链接https://drive.google.com/file/d/abc123xyz/view里的abc123xyz) - 拼接成下载链接:
- 版本文件:
https://drive.google.com/uc?export=download&id=你的version.txt的FILE_ID - APK文件:
https://drive.google.com/uc?export=download&id=你的APK的FILE_ID
- 版本文件:
第二步:App端代码实现
以下是基于Kotlin的示例代码,如果你用Java可以直接转译,逻辑是一样的:
1. 获取本地App版本号
先写个工具函数获取当前App的版本名称:
fun getCurrentAppVersion(context: Context): String { return try { val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) packageInfo.versionName } catch (e: PackageManager.NameNotFoundException) { "1.0.0" // 兜底默认版本号 } }
2. 版本号对比工具函数
注意:直接字符串对比"1.0.9"和"1.0.10"会出错,因为字符串排序9比10大,所以要写个分段比较的函数:
fun isVersionNewer(latestVersion: String, currentVersion: String): Boolean { val latestParts = latestVersion.split(".").mapNotNull { it.toIntOrNull() } val currentParts = currentVersion.split(".").mapNotNull { it.toIntOrNull() } val maxLength = maxOf(latestParts.size, currentParts.size) for (i in 0 until maxLength) { val latestNum = if (i < latestParts.size) latestParts[i] else 0 val currentNum = if (i < currentParts.size) currentParts[i] else 0 if (latestNum > currentNum) return true if (latestNum < currentNum) return false } return false // 版本号相同 }
3. 检查更新逻辑
用OkHttp请求远程版本文件(记得在build.gradle里添加OkHttp依赖:implementation("com.squareup.okhttp3:okhttp:4.12.0")):
private fun checkForUpdate() { val versionUrl = "你的version.txt下载链接" val client = OkHttpClient() val request = Request.Builder().url(versionUrl).build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { runOnUiThread { Toast.makeText(this@MainActivity, "检查更新失败,请检查网络", Toast.LENGTH_SHORT).show() } } override fun onResponse(call: Call, response: Response) { response.body?.string()?.trim()?.let { latestVersion -> val currentVersion = getCurrentAppVersion(this@MainActivity) if (isVersionNewer(latestVersion, currentVersion)) { runOnUiThread { showUpdateDialog(latestVersion) } } else { runOnUiThread { Toast.makeText(this@MainActivity, "当前已是最新版本", Toast.LENGTH_SHORT).show() } } } } }) }
4. 显示更新对话框
弹出对话框让用户选择是否下载:
private fun showUpdateDialog(latestVersion: String) { AlertDialog.Builder(this) .setTitle("发现新版本 v$latestVersion") .setMessage("点击按钮即可下载最新版本,下载完成后请手动安装") .setPositiveButton("立即下载") { _, _ -> startApkDownload() } .setNegativeButton("稍后再说", null) .setCancelable(false) .show() }
5. 触发APK下载
用Android原生DownloadManager来处理下载,不需要自己处理存储权限:
private var downloadId: Long = -1 // 保存下载ID,用于监听下载完成 private lateinit var downloadReceiver: BroadcastReceiver private fun startApkDownload() { val apkUrl = "你的APK下载链接" val request = DownloadManager.Request(Uri.parse(apkUrl)) .setTitle("课堂笔记App 更新") .setDescription("正在下载最新版本...") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "NotesApp_vLatest.apk") .setAllowedOverMetered(true) // 允许移动数据下载 .setAllowedOverRoaming(true) val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager downloadId = downloadManager.enqueue(request) Toast.makeText(this, "开始下载,可在通知栏查看进度", Toast.LENGTH_SHORT).show() }
6. 监听下载完成并引导安装
注册广播接收器,下载完成后提示用户安装:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 注册下载完成广播 downloadReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val receivedId = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) if (receivedId == downloadId && receivedId != -1L) { val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager val query = DownloadManager.Query().setFilterById(receivedId) val cursor = downloadManager.query(query) if (cursor.moveToFirst()) { val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) if (status == DownloadManager.STATUS_SUCCESSFUL) { val apkUri = Uri.parse(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))) installApk(apkUri) } } cursor.close() } } } registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) } private fun installApk(uri: Uri) { val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) installIntent.setDataAndType(uri, "application/vnd.android.package-archive") installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(installIntent) } override fun onDestroy() { super.onDestroy() unregisterReceiver(downloadReceiver) // 记得注销广播 }
第三步:权限与配置
在AndroidManifest.xml里添加必要权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
如果你的App目标SDK是Android 10(API 29)及以上,不需要额外申请存储权限,DownloadManager会自动把文件存到公共下载目录。
额外优化建议
- 给App加个「检查更新」按钮(比如在设置页面),让用户可以手动触发检查
- 可以在
version.txt里加个简单校验(比如加上你的App包名),防止别人恶意修改版本文件 - 如果担心Google Drive链接被爬虫抓取,可以把版本文件的链接加密,App端请求前再解密
内容的提问来源于stack exchange,提问作者Neon
相关产品推荐
相关产品推荐

