设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间01 现在时间01 缓存数据 当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

查看: 228|回复: 0

Android实现Android APP自动更新功能

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:234
  • 打卡月天数:0
  • 打卡总奖励:3470
  • 最近打卡:2025-11-29 17:57:30
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
431
主题
392
精华
0
金钱
4746
积分
901
注册时间
2023-1-13
最后登录
2025-11-29

发表于 2025-9-8 12:26:57 | 显示全部楼层 |阅读模式
一、项目介绍

在移动应用的全生命周期中,版本迭代和用户更新体验至关重要。传统的做法是依赖 Google Play 商店强制推送更新,但在某些场景下,我们需要:

  • 更即时地控制更新流程(如灰度、强制升级、提醒升级等);
  • 支持市场外分发,比如企业内部应用分发、第三方应用商店;
  • 自定义更新 UI,与应用风格保持一致。
本项目示例将展示两种主流方案:

  • Google Play In‑App Updates(官方方案,适用于上架 Play 商店的应用)
  • 自建服务 + APK 下载 & 安装(适用于非 Play 分发场景)
通过本教程,你将学会如何在应用内检测新版本、弹出升级对话框、后台下载 APK、以及无缝触发安装流程,极大提升用户体验。

二、相关知识


  • Google Play Core Library

      1. com.google.android.play:core:1.x.x
      复制代码
      包含了 In‑App Updates API,让应用可在运行时检查并触发“灵活更新”或“立即更新”流程,无需用户去 Play 商店界面。

  • FileProvider & 安装意图

    • 对于自建更新方案,需要在
      1. AndroidManifest.xml
      复制代码
      配置
      1. FileProvider
      复制代码
      ,并通过
      1. Intent.ACTION_VIEW
      复制代码
      携带 APK 的
      1. content://
      复制代码
      URI,调用系统安装界面。

  • WorkManager / DownloadManager

    • 长任务(如后台下载 APK)应使用
      1. WorkManager
      复制代码
      或系统
      1. DownloadManager
      复制代码
      ,保证下载可在后台稳定运行,且重启后可续传。

  • 运行时权限 & 兼容性

    • Android 8.0+(API 26+)安装需获取 “允许安装未知应用” 权限 (
      1. REQUEST_INSTALL_PACKAGES
      复制代码
      )。
    • Android 7.0+(API 24+)文件 URI 必须走
      1. FileProvider
      复制代码
      ,否则会抛
      1. FileUriExposedException
      复制代码



三、项目实现思路


  • 版本检测

    • Play 方案:调用 Play Core 的
      1. AppUpdateManager.getAppUpdateInfo()
      复制代码
      检查更新状态。
    • 自建方案:向自有服务器发起网络请求(如 GET
      1. /latest_version.json
      复制代码
      ),获取最新版本号、APK 下载地址、更新说明等。

  • 弹窗交互

    • 根据策略选择“立即更新”(强制)或“灵活更新”(允许后台运行时再重启安装),并展示更新日志。

  • 下载 APK

    • Play 方案:由 Play Core 自动下载。
    • 自建方案:用
      1. DownloadManager
      复制代码
      启动下载,并监听广播获取下载完成通知。

  • 触发安装

    • 下载完成后,构造
      1. Intent.ACTION_VIEW
      复制代码
      ,指定 MIME 类型
      1. application/vnd.android.package-archive
      复制代码
      ,使用
      1. FileProvider
      复制代码
      共享 APK URI,启动安装流程。


四、完整代码(All‑in‑One,含详细注释)
  1. // =======================================
  2. // 文件: AutoUpdateManager.java + MainActivity
  3. // (本示例将 Manager 与 Activity 合写于一处,注释区分)
  4. // =======================================

  5. package com.example.autoupdate;

  6. import android.Manifest;
  7. import android.app.Activity;
  8. import android.app.AlertDialog;
  9. import android.app.DownloadManager;
  10. import android.content.ActivityNotFoundException;
  11. import android.content.BroadcastReceiver;
  12. import android.content.Context;
  13. import android.content.DialogInterface;
  14. import android.content.Intent;
  15. import android.content.IntentFilter;
  16. import android.content.pm.PackageManager;
  17. import android.content.res.Configuration;
  18. import android.net.Uri;
  19. import android.os.Build;
  20. import android.os.Environment;
  21. import androidx.annotation.NonNull;
  22. import androidx.appcompat.app.AppCompatActivity;
  23. import androidx.core.app.ActivityCompat;
  24. import androidx.core.content.FileProvider;

  25. import android.os.Bundle;
  26. import android.util.Log;
  27. import android.view.View;
  28. import android.widget.Button;
  29. import android.widget.Toast;

  30. // —— Play Core 库依赖(立即更新/灵活更新)
  31. // implementation "com.google.android.play:core:1.10.3"
  32. import com.google.android.play.core.appupdate.AppUpdateInfo;
  33. import com.google.android.play.core.appupdate.AppUpdateManagerFactory;
  34. import com.google.android.play.core.install.model.AppUpdateType;
  35. import com.google.android.play.core.install.model.UpdateAvailability;
  36. import com.google.android.play.core.tasks.Task;

  37. import org.json.JSONObject;

  38. import java.io.File;
  39. import java.io.InputStream;
  40. import java.net.HttpURLConnection;
  41. import java.net.URL;
  42. import java.util.Scanner;

  43. public class MainActivity extends AppCompatActivity {

  44.     // ---------- 常量区 ----------
  45.     private static final int REQUEST_CODE_UPDATE = 100;               // Play 更新请求码
  46.     private static final int REQUEST_INSTALL_PERMISSION = 101;        // 动态安装权限
  47.     private static final String TAG = "AutoUpdate";
  48.     private long downloadId;                                          // DownloadManager 返回 ID
  49.     private DownloadManager downloadManager;

  50.     // ---------- onCreate ----------
  51.     @Override
  52.     protected void onCreate(Bundle savedInstanceState) {
  53.         super.onCreate(savedInstanceState);
  54.         setContentView(R.layout.activity_main);  // 布局见下文

  55.         // 按钮触发两种更新
  56.         Button btnPlayUpdate    = findViewById(R.id.btnPlayUpdate);
  57.         Button btnCustomUpdate  = findViewById(R.id.btnCustomUpdate);

  58.         btnPlayUpdate.setOnClickListener(new View.OnClickListener() {
  59.             @Override public void onClick(View v) {
  60.                 checkPlayUpdate();   // Play 商店内更新
  61.             }
  62.         });

  63.         btnCustomUpdate.setOnClickListener(new View.OnClickListener() {
  64.             @Override public void onClick(View v) {
  65.                 checkCustomUpdate(); // 自建服务器更新
  66.             }
  67.         });

  68.         // 初始化 DownloadManager
  69.         downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
  70.         // 注册下载完成广播
  71.         registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
  72.     }

  73.     // ---------- 1. Play In‑App Updates 检查 ----------
  74.     private void checkPlayUpdate() {
  75.         // 创建 AppUpdateManager
  76.         com.google.android.play.core.appupdate.AppUpdateManager appUpdateManager =
  77.                 AppUpdateManagerFactory.create(this);

  78.         // 异步获取更新信息
  79.         Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
  80.         appUpdateInfoTask.addOnSuccessListener(info -> {
  81.             // 判断是否有更新且支持立即更新
  82.             if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
  83.                     && info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
  84.                 try {
  85.                     // 发起灵活更新请求
  86.                     appUpdateManager.startUpdateFlowForResult(
  87.                             info,
  88.                             AppUpdateType.FLEXIBLE,
  89.                             this,
  90.                             REQUEST_CODE_UPDATE);
  91.                 } catch (Exception e) {
  92.                     Log.e(TAG, "Play 更新启动失败", e);
  93.                 }
  94.             } else {
  95.                 Toast.makeText(this, "无可用更新或不支持此更新类型", Toast.LENGTH_SHORT).show();
  96.             }
  97.         });
  98.     }

  99.     // ---------- 2. 自建服务器版本检测 ----------
  100.     private void checkCustomUpdate() {
  101.         new Thread(() -> {
  102.             try {
  103.                 // 1) 请求服务器 JSON
  104.                 URL url = new URL("https://your.server.com/latest_version.json");
  105.                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  106.                 conn.setConnectTimeout(5000);
  107.                 conn.setRequestMethod("GET");
  108.                 InputStream in = conn.getInputStream();
  109.                 Scanner sc = new Scanner(in).useDelimiter("\\A");
  110.                 String json = sc.hasNext() ? sc.next() : "";
  111.                 JSONObject obj = new JSONObject(json);
  112.                 final int serverVersionCode = obj.getInt("versionCode");
  113.                 final String apkUrl       = obj.getString("apkUrl");
  114.                 final String changeLog   = obj.getString("changeLog");

  115.                 // 2) 获取本地版本号
  116.                 int localVersionCode = getPackageManager()
  117.                         .getPackageInfo(getPackageName(), 0).versionCode;

  118.                 if (serverVersionCode > localVersionCode) {
  119.                     // 有新版,回到主线程弹窗提示
  120.                     runOnUiThread(() ->
  121.                         showUpdateDialog(apkUrl, changeLog)
  122.                     );
  123.                 } else {
  124.                     runOnUiThread(() ->
  125.                         Toast.makeText(this, "已是最新版本", Toast.LENGTH_SHORT).show()
  126.                     );
  127.                 }
  128.             } catch (Exception e) {
  129.                 Log.e(TAG, "检查更新失败", e);
  130.             }
  131.         }).start();
  132.     }

  133.     // ---------- 3. 弹出更新对话框 ----------
  134.     private void showUpdateDialog(String apkUrl, String changeLog) {
  135.         new AlertDialog.Builder(this)
  136.             .setTitle("发现新版本")
  137.             .setMessage(changeLog)
  138.             .setCancelable(false)
  139.             .setPositiveButton("立即更新", (dialog, which) -> {
  140.                 startDownload(apkUrl);
  141.             })
  142.             .setNegativeButton("稍后再说", null)
  143.             .show();
  144.     }

  145.     // ---------- 4. 启动系统 DownloadManager 下载 APK ----------
  146.     private void startDownload(String apkUrl) {
  147.         DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
  148.         request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI
  149.                                       | DownloadManager.Request.NETWORK_MOBILE);
  150.         request.setTitle("正在下载更新包");
  151.         request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk");
  152.         // 开始下载
  153.         downloadId = downloadManager.enqueue(request);
  154.     }

  155.     // ---------- 5. 监听下载完成,触发安装 ----------
  156.     private BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
  157.         @Override public void onReceive(Context context, Intent intent) {
  158.             long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
  159.             if (id != downloadId) return;

  160.             // 下载完成,安装 APK
  161.             File apkFile = new File(Environment.getExternalStoragePublicDirectory(
  162.                             Environment.DIRECTORY_DOWNLOADS), "update.apk");

  163.             // Android 8.0+ 需要请求安装未知应用权限
  164.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  165.                 boolean canInstall = getPackageManager().canRequestPackageInstalls();
  166.                 if (!canInstall) {
  167.                     // 请求“安装未知应用”权限
  168.                     ActivityCompat.requestPermissions(MainActivity.this,
  169.                         new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES},
  170.                         REQUEST_INSTALL_PERMISSION);
  171.                     return;
  172.                 }
  173.             }
  174.             installApk(apkFile);
  175.         }
  176.     };

  177.     // ---------- 6. 处理未知来源权限申请结果 ----------
  178.     @Override
  179.     public void onRequestPermissionsResult(int requestCode,
  180.                                            @NonNull String[] permissions,
  181.                                            @NonNull int[] grantResults) {
  182.         if (requestCode == REQUEST_INSTALL_PERMISSION) {
  183.             if (grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED) {
  184.                 // 再次触发安装(假设 APK 仍在下载目录)
  185.                 File apkFile = new File(Environment.getExternalStoragePublicDirectory(
  186.                                 Environment.DIRECTORY_DOWNLOADS), "update.apk");
  187.                 installApk(apkFile);
  188.             } else {
  189.                 Toast.makeText(this, "安装权限被拒绝,无法自动更新", Toast.LENGTH_LONG).show();
  190.             }
  191.         }
  192.         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  193.     }

  194.     // ---------- 7. 安装 APK 辅助方法 ----------
  195.     private void installApk(File apkFile) {
  196.         Uri apkUri = FileProvider.getUriForFile(this,
  197.                 getPackageName() + ".fileprovider", apkFile);

  198.         Intent intent = new Intent(Intent.ACTION_VIEW);
  199.         intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
  200.         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  201.         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  202.         try {
  203.             startActivity(intent);
  204.         } catch (ActivityNotFoundException e) {
  205.             Toast.makeText(this, "无法启动安装程序", Toast.LENGTH_LONG).show();
  206.         }
  207.     }

  208.     // ---------- 8. Activity 销毁时注销 Receiver ----------
  209.     @Override
  210.     protected void onDestroy() {
  211.         super.onDestroy();
  212.         unregisterReceiver(onDownloadComplete);
  213.     }
  214. }
复制代码
  1. <!-- ======================================
  2. 文件: AndroidManifest.xml
  3. 注意:需要配置 FileProvider 与权限
  4. ====================================== -->
  5. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  6.     package="com.example.autoupdate">

  7.     <!-- 安装未知来源权限(Android 8.0+ 需动态申请) -->
  8.     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
  9.     <uses-permission android:name="android.permission.INTERNET"/>
  10.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

  11.     <application
  12.         android:allowBackup="true"
  13.         android:label="@string/app_name"
  14.         android:theme="@style/Theme.AppCompat.Light.NoActionBar">

  15.         <!-- FileProvider 声明 -->
  16.         <provider
  17.             android:name="androidx.core.content.FileProvider"
  18.             android:authorities="${applicationId}.fileprovider"
  19.             android:exported="false"
  20.             android:grantUriPermissions="true">
  21.             <meta-data
  22.                 android:name="android.support.FILE_PROVIDER_PATHS"
  23.                 android:resource="@xml/file_paths" />
  24.         </provider>

  25.         <activity android:name=".MainActivity">
  26.             <intent-filter>
  27.                 <action android:name="android.intent.action.MAIN"/>
  28.                 <category android:name="android.intent.category.LAUNCHER"/>
  29.             </intent-filter>
  30.         </activity>
  31.     </application>
  32. </manifest>
复制代码
  1. <!-- ======================================
  2. 文件: res/xml/file_paths.xml
  3. FileProvider 路径配置
  4. ====================================== -->
  5. <paths xmlns:android="http://schemas.android.com/apk/res/android">
  6.     <external-path name="download" path="Download/"/>
  7. </paths>
复制代码
  1. <!-- ======================================
  2. 文件: res/layout/activity_main.xml
  3. 简单示例界面
  4. ====================================== -->
  5. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  6.     android:orientation="vertical" android:gravity="center"
  7.     android:layout_width="match_parent" android:layout_height="match_parent"
  8.     android:padding="24dp">

  9.     <Button
  10.         android:id="@+id/btnPlayUpdate"
  11.         android:layout_width="wrap_content"
  12.         android:layout_height="wrap_content"
  13.         android:text="Play In‑App 更新"/>

  14.     <View android:layout_height="16dp" android:layout_width="match_parent"/>

  15.     <Button
  16.         android:id="@+id/btnCustomUpdate"
  17.         android:layout_width="wrap_content"
  18.         android:layout_height="wrap_content"
  19.         android:text="自建服务更新"/>
  20. </LinearLayout>
复制代码
五、方法解读


  • checkPlayUpdate()
    检查 Google Play 上的更新可用性,并以“灵活更新”方式启动下载和安装流程。
  • checkCustomUpdate()
    通过
    1. HttpURLConnection
    复制代码
    请求服务器 JSON,解析最新
    1. versionCode
    复制代码
    1. apkUrl
    复制代码
    ,对比本地版本,决定是否弹窗。
  • showUpdateDialog(...)
    基于服务器返回的
    1. changeLog
    复制代码
    构建
    1. AlertDialog
    复制代码
    ,提供“立即更新”与“稍后再说”两种交互。
  • startDownload(String apkUrl)
    使用系统
    1. DownloadManager
    复制代码
    发起后台下载,保存至公开目录,支持断点续传和系统下载通知。
  • BroadcastReceiver onDownloadComplete
    监听
    1. DownloadManager.ACTION_DOWNLOAD_COMPLETE
    复制代码
    广播,确认是本次下载后触发安装流程。
  • onRequestPermissionsResult(...)
    处理 Android 8.0+ “安装未知来源”权限授权结果,授权后继续调用
    1. installApk()
    复制代码

  • installApk(File apkFile)
    通过
    1. FileProvider
    复制代码
    获取 APK 的 content URI,并以
    1. Intent.ACTION_VIEW
    复制代码
    调用系统安装器。

六、项目总结


优势


  • Play Core In‑App 更新:官方支持,体验与 Play 商店一致,无需手工管理下载逻辑。
  • 自建方案:灵活可控,支持任意分发渠道,自定义 UI 与灰度策略。

注意与优化


  • 权限与兼容

    • Android 7.0+ 必须使用
      1. FileProvider
      复制代码

    • Android 8.0+ 需动态申请
      1. REQUEST_INSTALL_PACKAGES
      复制代码


  • 下载失败重试

    • 可结合
      1. WorkManager
      复制代码
      增加重试与网络断线重连逻辑。

  • 安全性

    • 建议对 APK 做签名校验(计算 SHA256 与服务器比对),防止被篡改。

  • UI 体验

    • 对“立即更新”与“后台更新”作更多状态提示。
    • 可显示下载进度条、进度通知等。

  • 灰度/强制升级

    • 可在服务器 JSON 中添加策略字段,如
      1. forceUpdate
      复制代码
      ,在对话框中禁止“稍后再说”。

到此这篇关于Android实现Android APP自动更新功能的文章就介绍到这了,更多相关Android APP自动更新内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表