
离线 TA的专栏
- 打卡等级:热心大叔
- 打卡总天数:234
- 打卡月天数:0
- 打卡总奖励:3470
- 最近打卡:2025-11-29 17:57:30
|
一、项目介绍
在移动应用的全生命周期中,版本迭代和用户更新体验至关重要。传统的做法是依赖 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 & 安装意图
- 对于自建更新方案,需要在配置,并通过携带 APK 的URI,调用系统安装界面。
- WorkManager / DownloadManager
- 长任务(如后台下载 APK)应使用或系统,保证下载可在后台稳定运行,且重启后可续传。
- 运行时权限 & 兼容性
- Android 8.0+(API 26+)安装需获取 “允许安装未知应用” 权限 ()。
- Android 7.0+(API 24+)文件 URI 必须走,否则会抛。
三、项目实现思路
- 版本检测
- Play 方案:调用 Play Core 的
- AppUpdateManager.getAppUpdateInfo()
复制代码 检查更新状态。
- 自建方案:向自有服务器发起网络请求(如 GET),获取最新版本号、APK 下载地址、更新说明等。
- 弹窗交互
- 根据策略选择“立即更新”(强制)或“灵活更新”(允许后台运行时再重启安装),并展示更新日志。
- 下载 APK
- Play 方案:由 Play Core 自动下载。
- 自建方案:用启动下载,并监听广播获取下载完成通知。
- 触发安装
- 下载完成后,构造,指定 MIME 类型
- application/vnd.android.package-archive
复制代码 ,使用共享 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()
通过请求服务器 JSON,解析最新与,对比本地版本,决定是否弹窗。
- showUpdateDialog(...)
基于服务器返回的构建,提供“立即更新”与“稍后再说”两种交互。
- startDownload(String apkUrl)
使用系统发起后台下载,保存至公开目录,支持断点续传和系统下载通知。
- BroadcastReceiver onDownloadComplete
监听- DownloadManager.ACTION_DOWNLOAD_COMPLETE
复制代码 广播,确认是本次下载后触发安装流程。
- onRequestPermissionsResult(...)
处理 Android 8.0+ “安装未知来源”权限授权结果,授权后继续调用。
- installApk(File apkFile)
通过获取 APK 的 content URI,并以调用系统安装器。
六、项目总结
优势
- Play Core In‑App 更新:官方支持,体验与 Play 商店一致,无需手工管理下载逻辑。
- 自建方案:灵活可控,支持任意分发渠道,自定义 UI 与灰度策略。
注意与优化
- 权限与兼容
- Android 7.0+ 必须使用。
- Android 8.0+ 需动态申请。
- 下载失败重试
- 安全性
- 建议对 APK 做签名校验(计算 SHA256 与服务器比对),防止被篡改。
- UI 体验
- 对“立即更新”与“后台更新”作更多状态提示。
- 可显示下载进度条、进度通知等。
- 灰度/强制升级
- 可在服务器 JSON 中添加策略字段,如,在对话框中禁止“稍后再说”。
到此这篇关于Android实现Android APP自动更新功能的文章就介绍到这了,更多相关Android APP自动更新内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:  进行删除处理。
4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
|