Android線上應用內更新(站內更新) 適配6.0、7.0、8.0
概要
線上應用內更新 在APP開發中是最基礎的一項功能。主要可以分為以下幾步:
- 獲取當前版本資訊;
- 獲取後臺線上版本資訊;
- 版本對比,提示更新
- 點選取消,跳過更新,進入app
- 點選確定,開始下載
- 下載結束,提示安裝
正文
這裡略過1—4步驟,主要講下載及安裝部分。
檔案的下載有很多中方式與第三方框架,這裡由於僅僅是下載應用,所有綜合考慮,強烈建議使用安卓 SDK 自帶下載框架 DownloadManager 。
1.最基礎也是重要的 申請檔案讀寫許可權、網路許可權,8.0系統需要獲取“未知應用安裝許可權”
<!-- 網路許可權 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 檔案讀寫許可權 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 8.0系統 獲取 未知應用安裝許可權 -->
<uses-permission android:name ="android.permission.REQUEST_INSTALL_PACKAGES" />
2.檢測版本與安裝許可權
由於 8.0 “未知應用安裝許可權”被除掉,替換為“未知來源應用安裝許可權”,預設是關閉狀態,需要使用者手動開啟才能安裝最新版本安裝包,所以這裡需要判斷是否為 8.0 系統,然後獲取是否有該許可權。
為了使用者體驗比較好,這裡我在 onResume 中進行了版本判斷。其中 haveInstallPermission 為 boolean 型別,預設為true。
//版本判斷
if (Build.VERSION.SDK_INT >= Build.VERSION _CODES.O) {
//獲取是否有安裝未知來源應用的許可權
haveInstallPermission = MainActivity.this.getPackageManager().canRequestPackageInstalls();
}
3.封裝下載請求
這裡由於註釋很詳細,就不多做解釋,直接上程式碼。關於 DownloadManager.Request 的屬性詳解可以檢視這裡
public void downloadAPK() {
//已存在 -- 刪除
File apkFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxxx.apk");
if (apkFile != null && apkFile.exists()) {
apkFile.delete();
}
//下載 (AppCont.Update_URL 是你的app下載地址)
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(AppCont.Update_URL));
//設定title
request.setTitle(GetResourcesUtil.getString(R.string.app_name));
// 完成後顯示通知欄
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//設定儲存路徑 --這個是應用專用的,軟體解除安裝後,下載的檔案將隨著解除安裝全部被刪除
request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, "xxx.apk");
//設定 檔案型別
request.setMimeType("application/vnd.android.package-archive");
downloadManager.enqueue(request);
}
4.開始下載
在第二步中拿到了 是否有安裝未知來源應用的許可權 ,可以在使用者點選確定下載最新版本時去判斷,如果沒有許可權,跳轉到許可權授權介面;如果有許可權,則直接下載更新。
if (!haveInstallPermission) {
ToastUtils.showShort("請開啟安裝未知來源應用的許可權");
//沒有許可權 在 yourAppPackageName 設定你的app包名
Uri packageURI = Uri.parse("package:" + yourAppPackageName);
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
MainActivity.this.startActivityForResult(intent, 010);
}else {
diaglog.dismiss();
ToastUtils.showShort("正在下載....");
//下載更新
updateUtil = new UpdateUtil(MainActivity.this);
updateUtil.downloadAPK();
}
5.設定廣播接收器,監聽下載完成
downloadmanager 在下載完成時,會發送一條廣播,我們可以設定接收器,接受這條廣播,並做下載完成後的操作。
這裡先做廣播接收器
public class DownloadManagerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//下載完成
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
//獲取 downloadmanager 下載任務id
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
//安裝應用
UpdateUtil updateUtil = new UpdateUtil(context);
updateUtil.installApk(id);
}
}
}
// AndroidManifest.xml
<receiver android:name="com.xxxx.xxxx.util.DownloadManagerReceiver">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
</intent-filter>
</receiver>
在更新提示的介面註冊廣播接收器。建議在 onResume() 中註冊,在onPause() 中解除註冊。
@Override
protected void onResume() {
super.onResume();
//註冊廣播
downloadManagerReceiver = new DownloadManagerReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
registerReceiver(downloadManagerReceiver, intentFilter);
}
}
@Override
protected void onPause() {
super.onPause();
//解除註冊
unregisterReceiver(downloadManagerReceiver);
}
6.安裝app
在安裝 app 的時候需要獲取 apk 儲存地址,這裡需要注意一下,需要做6.0以下(不包含6.0)/6.0/7.0以上(包含7.0)三種版本的適配。
6.0以下與6.0系統的區別 與 適配:
6.0以下(不包含6.0)系統的可以直接獲取儲存路徑;getUriForDownloadedFile得到 值為: content://downloads/my_downloads/10
Android6.0以下,getUriForDownloadedFile得到的值為:file:///storage/emulated/0/Android/data/packgeName/files/Download/xxx.apk7.0適配 :
由於7.0對檔案訪問許可權做出了限制,必須通過 FileProvider 封裝後訪問。具體詳解可以檢視這裡
public Uri getDownloadPath(long downloadId) {
Uri path = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
//6.0 以下
path = downloadManager.getUriForDownloadedFile(downloadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
//6.0 — 7.0
path = Uri.fromFile(queryDownloadedApk(downloadId));
} else {
//7.0 以上
path = FileProvider.getUriForFile(context, "com.xxx.xxxx", new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxxx.apk"));
}
return path;
}
private File queryDownloadedApk(long downloadId) {
File downloadFilePath = null;
DownloadManager.Query query = new DownloadManager.Query();
Cursor cursor = null;
try {
cursor = downloadManager.query(query.setFilterById(downloadId));
if (cursor != null || cursor.moveToFirst()) {
String uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (!TextUtils.isEmpty(uriString)) {
downloadFilePath = new File(Uri.parse(uriString).getPath());
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return downloadFilePath;
}
獲取到apk儲存路徑後,可以進行最後的步驟安裝了,這裡很簡單,直接上程式碼。
public void installApk(long id) {
//在上面已經獲取到apk儲存路徑
Uri uri = getDownloadPath(id);
//跳轉安裝
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}