Android实现Android APP自动更新功能
作者:Katie。
一、项目介绍
在移动应用的全生命周期中,版本迭代和用户更新体验至关重要。传统的做法是依赖 Google Play 商店强制推送更新,但在某些场景下,我们需要:
更即时地控制更新流程(如灰度、强制升级、提醒升级等);
支持市场外分发,比如企业内部应用分发、第三方应用商店;
自定义更新 UI,与应用风格保持一致。
本项目示例将展示两种主流方案:
Google Play In‑App Updates(官方方案,适用于上架 Play 商店的应用)
自建服务 + APK 下载 & 安装(适用于非 Play 分发场景)
通过本教程,你将学会如何在应用内检测新版本、弹出升级对话框、后台下载 APK、以及无缝触发安装流程,极大提升用户体验。
二、相关知识
Google Play Core Library
com.google.android.play:core:1.x.x
包含了 In‑App Updates API,让应用可在运行时检查并触发“灵活更新”或“立即更新”流程,无需用户去 Play 商店界面。
FileProvider & 安装意图
对于自建更新方案,需要在
AndroidManifest.xml
配置FileProvider
,并通过Intent.ACTION_VIEW
携带 APK 的content://
URI,调用系统安装界面。
WorkManager / DownloadManager
长任务(如后台下载 APK)应使用
WorkManager
或系统DownloadManager
,保证下载可在后台稳定运行,且重启后可续传。
运行时权限 & 兼容性
Android 8.0+(API 26+)安装需获取 “允许安装未知应用” 权限 (
REQUEST_INSTALL_PACKAGES
)。Android 7.0+(API 24+)文件 URI 必须走
FileProvider
,否则会抛FileUriExposedException
。
三、项目实现思路
版本检测
Play 方案:调用 Play Core 的
AppUpdateManager.getAppUpdateInfo()
检查更新状态。自建方案:向自有服务器发起网络请求(如 GET
/latest_version.json
),获取最新版本号、APK 下载地址、更新说明等。
弹窗交互
根据策略选择“立即更新”(强制)或“灵活更新”(允许后台运行时再重启安装),并展示更新日志。
下载 APK
Play 方案:由 Play Core 自动下载。
自建方案:用
DownloadManager
启动下载,并监听广播获取下载完成通知。
触发安装
下载完成后,构造
Intent.ACTION_VIEW
,指定 MIME 类型application/vnd.android.package-archive
,使用FileProvider
共享 APK URI,启动安装流程。
四、完整代码(All‑in‑One,含详细注释)
// ======================================= // 文件: AutoUpdateManager.java + MainActivity // (本示例将 Manager 与 Activity 合写于一处,注释区分) // ======================================= package com.example.autoupdate; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.DownloadManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.net.Uri; import android.os.Build; import android.os.Environment; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; // —— Play Core 库依赖(立即更新/灵活更新) // implementation "com.google.android.play:core:1.10.3" import com.google.android.play.core.appupdate.AppUpdateInfo; import com.google.android.play.core.appupdate.AppUpdateManagerFactory; import com.google.android.play.core.install.model.AppUpdateType; import com.google.android.play.core.install.model.UpdateAvailability; import com.google.android.play.core.tasks.Task; import org.json.JSONObject; import java.io.File; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Scanner; public class MainActivity extends AppCompatActivity { // ---------- 常量区 ---------- private static final int REQUEST_CODE_UPDATE = 100; // Play 更新请求码 private static final int REQUEST_INSTALL_PERMISSION = 101; // 动态安装权限 private static final String TAG = "AutoUpdate"; private long downloadId; // DownloadManager 返回 ID private DownloadManager downloadManager; // ---------- onCreate ---------- @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 布局见下文 // 按钮触发两种更新 Button btnPlayUpdate = findViewById(R.id.btnPlayUpdate); Button btnCustomUpdate = findViewById(R.id.btnCustomUpdate); btnPlayUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { checkPlayUpdate(); // Play 商店内更新 } }); btnCustomUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { checkCustomUpdate(); // 自建服务器更新 } }); // 初始化 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); // 注册下载完成广播 registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } // ---------- 1. Play In‑App Updates 检查 ---------- private void checkPlayUpdate() { // 创建 AppUpdateManager com.google.android.play.core.appupdate.AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this); // 异步获取更新信息 Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo(); appUpdateInfoTask.addOnSuccessListener(info -> { // 判断是否有更新且支持立即更新 if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { try { // 发起灵活更新请求 appUpdateManager.startUpdateFlowForResult( info, AppUpdateType.FLEXIBLE, this, REQUEST_CODE_UPDATE); } catch (Exception e) { Log.e(TAG, "Play 更新启动失败", e); } } else { Toast.makeText(this, "无可用更新或不支持此更新类型", Toast.LENGTH_SHORT).show(); } }); } // ---------- 2. 自建服务器版本检测 ---------- private void checkCustomUpdate() { new Thread(() -> { try { // 1) 请求服务器 JSON URL url = new URL("https://your.server.com/latest_version.json"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); InputStream in = conn.getInputStream(); Scanner sc = new Scanner(in).useDelimiter("\\A"); String json = sc.hasNext() ? sc.next() : ""; JSONObject obj = new JSONObject(json); final int serverVersionCode = obj.getInt("versionCode"); final String apkUrl = obj.getString("apkUrl"); final String changeLog = obj.getString("changeLog"); // 2) 获取本地版本号 int localVersionCode = getPackageManager() .getPackageInfo(getPackageName(), 0).versionCode; if (serverVersionCode > localVersionCode) { // 有新版,回到主线程弹窗提示 runOnUiThread(() -> showUpdateDialog(apkUrl, changeLog) ); } else { runOnUiThread(() -> Toast.makeText(this, "已是最新版本", Toast.LENGTH_SHORT).show() ); } } catch (Exception e) { Log.e(TAG, "检查更新失败", e); } }).start(); } // ---------- 3. 弹出更新对话框 ---------- private void showUpdateDialog(String apkUrl, String changeLog) { new AlertDialog.Builder(this) .setTitle("发现新版本") .setMessage(changeLog) .setCancelable(false) .setPositiveButton("立即更新", (dialog, which) -> { startDownload(apkUrl); }) .setNegativeButton("稍后再说", null) .show(); } // ---------- 4. 启动系统 DownloadManager 下载 APK ---------- private void startDownload(String apkUrl) { DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl)); request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE); request.setTitle("正在下载更新包"); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk"); // 开始下载 downloadId = downloadManager.enqueue(request); } // ---------- 5. 监听下载完成,触发安装 ---------- private BroadcastReceiver onDownloadComplete = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if (id != downloadId) return; // 下载完成,安装 APK File apkFile = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS), "update.apk"); // Android 8.0+ 需要请求安装未知应用权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean canInstall = getPackageManager().canRequestPackageInstalls(); if (!canInstall) { // 请求“安装未知应用”权限 ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, REQUEST_INSTALL_PERMISSION); return; } } installApk(apkFile); } }; // ---------- 6. 处理未知来源权限申请结果 ---------- @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_INSTALL_PERMISSION) { if (grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED) { // 再次触发安装(假设 APK 仍在下载目录) File apkFile = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS), "update.apk"); installApk(apkFile); } else { Toast.makeText(this, "安装权限被拒绝,无法自动更新", Toast.LENGTH_LONG).show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } // ---------- 7. 安装 APK 辅助方法 ---------- private void installApk(File apkFile) { Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(this, "无法启动安装程序", Toast.LENGTH_LONG).show(); } } // ---------- 8. Activity 销毁时注销 Receiver ---------- @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(onDownloadComplete); } }
<!-- ====================================== 文件: AndroidManifest.xml 注意:需要配置 FileProvider 与权限 ====================================== --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.autoupdate"> <!-- 安装未知来源权限(Android 8.0+ 需动态申请) --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:label="@string/app_name" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> <!-- FileProvider 声明 --> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
<!-- ====================================== 文件: res/xml/file_paths.xml FileProvider 路径配置 ====================================== --> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="download" path="Download/"/> </paths>
<!-- ====================================== 文件: res/layout/activity_main.xml 简单示例界面 ====================================== --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="24dp"> <Button android:id="@+id/btnPlayUpdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Play In‑App 更新"/> <View android:layout_height="16dp" android:layout_width="match_parent"/> <Button android:id="@+id/btnCustomUpdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="自建服务更新"/> </LinearLayout>
五、方法解读
checkPlayUpdate()
检查 Google Play 上的更新可用性,并以“灵活更新”方式启动下载和安装流程。checkCustomUpdate()
通过HttpURLConnection
请求服务器 JSON,解析最新versionCode
与apkUrl
,对比本地版本,决定是否弹窗。showUpdateDialog(...)
基于服务器返回的changeLog
构建AlertDialog
,提供“立即更新”与“稍后再说”两种交互。startDownload(String apkUrl)
使用系统DownloadManager
发起后台下载,保存至公开目录,支持断点续传和系统下载通知。BroadcastReceiver onDownloadComplete
监听DownloadManager.ACTION_DOWNLOAD_COMPLETE
广播,确认是本次下载后触发安装流程。onRequestPermissionsResult(...)
处理 Android 8.0+ “安装未知来源”权限授权结果,授权后继续调用installApk()
。installApk(File apkFile)
通过FileProvider
获取 APK 的 content URI,并以Intent.ACTION_VIEW
调用系统安装器。
六、项目总结
优势
Play Core In‑App 更新:官方支持,体验与 Play 商店一致,无需手工管理下载逻辑。
自建方案:灵活可控,支持任意分发渠道,自定义 UI 与灰度策略。
注意与优化
权限与兼容
Android 7.0+ 必须使用
FileProvider
。Android 8.0+ 需动态申请
REQUEST_INSTALL_PACKAGES
。
下载失败重试
可结合
WorkManager
增加重试与网络断线重连逻辑。
安全性
建议对 APK 做签名校验(计算 SHA256 与服务器比对),防止被篡改。
UI 体验
对“立即更新”与“后台更新”作更多状态提示。
可显示下载进度条、进度通知等。
灰度/强制升级
可在服务器 JSON 中添加策略字段,如
forceUpdate
,在对话框中禁止“稍后再说”。
到此这篇关于Android实现Android APP自动更新功能的文章就介绍到这了,更多相关Android APP自动更新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!