Android實現Android?APP自動更新功能
一、項目介紹
在移動應用的全生命周期中,版本迭代和用戶更新體驗至關重要。傳統(tǒng)的做法是依賴 Google Play 商店強制推送更新,但在某些場景下,我們需要:
更即時地控制更新流程(如灰度、強制升級、提醒升級等);
支持市場外分發(fā),比如企業(yè)內部應用分發(fā)、第三方應用商店;
自定義更新 UI,與應用風格保持一致。
本項目示例將展示兩種主流方案:
Google Play In?App Updates(官方方案,適用于上架 Play 商店的應用)
自建服務 + APK 下載 & 安裝(適用于非 Play 分發(fā)場景)
通過本教程,你將學會如何在應用內檢測新版本、彈出升級對話框、后臺下載 APK、以及無縫觸發(fā)安裝流程,極大提升用戶體驗。
二、相關知識
Google Play Core Library
com.google.android.play:core:1.x.x包含了 In?App Updates API,讓應用可在運行時檢查并觸發(fā)“靈活更新”或“立即更新”流程,無需用戶去 Play 商店界面。
FileProvider & 安裝意圖
對于自建更新方案,需要在
AndroidManifest.xml配置FileProvider,并通過Intent.ACTION_VIEW攜帶 APK 的content://URI,調用系統(tǒng)安裝界面。
WorkManager / DownloadManager
長任務(如后臺下載 APK)應使用
WorkManager或系統(tǒng)DownloadManager,保證下載可在后臺穩(wěn)定運行,且重啟后可續(xù)傳。
運行時權限 & 兼容性
Android 8.0+(API 26+)安裝需獲取 “允許安裝未知應用” 權限 (
REQUEST_INSTALL_PACKAGES)。Android 7.0+(API 24+)文件 URI 必須走
FileProvider,否則會拋FileUriExposedException。
三、項目實現思路
版本檢測
Play 方案:調用 Play Core 的
AppUpdateManager.getAppUpdateInfo()檢查更新狀態(tài)。自建方案:向自有服務器發(fā)起網絡請求(如 GET
/latest_version.json),獲取最新版本號、APK 下載地址、更新說明等。
彈窗交互
根據策略選擇“立即更新”(強制)或“靈活更新”(允許后臺運行時再重啟安裝),并展示更新日志。
下載 APK
Play 方案:由 Play Core 自動下載。
自建方案:用
DownloadManager啟動下載,并監(jiān)聽廣播獲取下載完成通知。
觸發(fā)安裝
下載完成后,構造
Intent.ACTION_VIEW,指定 MIME 類型application/vnd.android.package-archive,使用FileProvider共享 APK URI,啟動安裝流程。
四、完整代碼(All?in?One,含詳細注釋)
// =======================================
// 文件: AutoUpdateManager.java + MainActivity
// (本示例將 Manager 與 Activity 合寫于一處,注釋區(qū)分)
// =======================================
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 {
// ---------- 常量區(qū) ----------
private static final int REQUEST_CODE_UPDATE = 100; // Play 更新請求碼
private static final int REQUEST_INSTALL_PERMISSION = 101; // 動態(tài)安裝權限
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); // 布局見下文
// 按鈕觸發(fā)兩種更新
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() {
// 創(chuàng)建 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 {
// 發(fā)起靈活更新請求
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("發(fā)現新版本")
.setMessage(changeLog)
.setCancelable(false)
.setPositiveButton("立即更新", (dialog, which) -> {
startDownload(apkUrl);
})
.setNegativeButton("稍后再說", null)
.show();
}
// ---------- 4. 啟動系統(tǒng) 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. 監(jiān)聽下載完成,觸發(fā)安裝 ----------
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) {
// 再次觸發(fā)安裝(假設 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+ 需動態(tài)申請) -->
<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)
使用系統(tǒng)DownloadManager發(fā)起后臺下載,保存至公開目錄,支持斷點續(xù)傳和系統(tǒng)下載通知。BroadcastReceiver onDownloadComplete
監(jiān)聽DownloadManager.ACTION_DOWNLOAD_COMPLETE廣播,確認是本次下載后觸發(fā)安裝流程。onRequestPermissionsResult(...)
處理 Android 8.0+ “安裝未知來源”權限授權結果,授權后繼續(xù)調用installApk()。installApk(File apkFile)
通過FileProvider獲取 APK 的 content URI,并以Intent.ACTION_VIEW調用系統(tǒng)安裝器。
六、項目總結
優(yōu)勢
Play Core In?App 更新:官方支持,體驗與 Play 商店一致,無需手工管理下載邏輯。
自建方案:靈活可控,支持任意分發(fā)渠道,自定義 UI 與灰度策略。
注意與優(yōu)化
權限與兼容
Android 7.0+ 必須使用
FileProvider。Android 8.0+ 需動態(tài)申請
REQUEST_INSTALL_PACKAGES。
下載失敗重試
可結合
WorkManager增加重試與網絡斷線重連邏輯。
安全性
建議對 APK 做簽名校驗(計算 SHA256 與服務器比對),防止被篡改。
UI 體驗
對“立即更新”與“后臺更新”作更多狀態(tài)提示。
可顯示下載進度條、進度通知等。
灰度/強制升級
可在服務器 JSON 中添加策略字段,如
forceUpdate,在對話框中禁止“稍后再說”。
到此這篇關于Android實現Android APP自動更新功能的文章就介紹到這了,更多相關Android APP自動更新內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android SharedPreferences數據存儲詳解
SharedPreferences是安卓平臺上一個輕量級的存儲類,用來保存應用的一些常用配置,比如Activity狀態(tài),Activity暫停時,將此activity的狀態(tài)保存到SharedPereferences中;當Activity重載,系統(tǒng)回調方法onSaveInstanceState時,再從SharedPreferences中將值取出2022-11-11
Android中控件GridView實現設置行列分割線的方法示例
這篇文章主要介紹了利用Android中控件GridView實現設置行列分割線的方法,文中給出了詳細的介紹與示例代碼,相信對大家具有一定的參考價值,有需要的朋友們下面來一起看看吧。2017-01-01
Android?BottomSheetBehavior使用方法及常見問題詳解
這篇文章主要介紹了Android?BottomSheetBehavior使用方法及常見問題的相關資料,BottomSheetBehavior是AndroidX中用于實現底部彈出式面板(底部抽屜)的行為類,支持拖拽、展開/收起、狀態(tài)監(jiān)聽等核心能力,需要的朋友可以參考下2025-12-12
Android WebView自定義長按選擇實現收藏/分享選中文本功能
這篇文章主要介紹了Android WebView自定義長按選擇實現收藏/分享選中文本功能,需要的朋友可以參考下2017-06-06
Flutter實現頁面切換后保持原頁面狀態(tài)的3種方法
這篇文章主要給大家介紹了關于Flutter實現頁面切換后保持原頁面狀態(tài)的3種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用Flutter具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-03-03

