整個(gè)流程分為三步:
1.版本檢測(cè)
2.新apk文件下載
3.覆蓋安裝
基本流程就是這樣的了, 區(qū)別在于怎么安排, 用什么策略更新. 網(wǎng)上的思路基本上是, 在主頁(yè)里檢測(cè)apk版本, 需要更新就彈一個(gè)對(duì)話框給用戶(hù), 提醒其更新. 用戶(hù)確定更新, 就在主頁(yè)中或者開(kāi)一個(gè)服務(wù), 利用downloadmanager進(jìn)行文件下載廣播注冊(cè), 下載完了在廣播中進(jìn)行自動(dòng)安裝(downloadmanager下載完了會(huì)發(fā)出一條廣播).
在實(shí)際項(xiàng)目中, 考慮到版本維護(hù)的成本, 更新策略選擇強(qiáng)制用戶(hù)更新. 具體做法有點(diǎn)類(lèi)似與應(yīng)用商店的靜默安裝, 有更新就先后臺(tái)下載, 等到下次打開(kāi)彈出一個(gè)對(duì)話框進(jìn)行安裝.
一. 版本檢測(cè)
思路
一般最新的版本信息會(huì)以json文件的形式放在服務(wù)器上, 我們要做的就是利用OKhttp3下載這個(gè)json文件, 解析取出對(duì)應(yīng)字段, 和本地versionName比較, 不同就進(jìn)入下一步.
OKhttp3
Android OkHttp完全解析 是時(shí)候來(lái)了解OkHttp了
Android 一個(gè)改善的okHttp封裝庫(kù)
Android Https相關(guān)完全解析 當(dāng)OkHttp遇到Https
本地版本信息獲取
PackageManager packageManager = getPackageManager();
#0 means all the flags are turned off
PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);
String version = packageInfo.versionName;
二. 新apk文件下載
思路
這里使用Google推薦的DownloadManager來(lái)進(jìn)行異步下載
DownloadManager
如果你不想糾結(jié)什么斷點(diǎn)續(xù)傳, 網(wǎng)絡(luò)環(huán)境啥的, 那么你就使用DownloadManager吧
//首先, 構(gòu)建一個(gè)下載請(qǐng)求, uri 是你的下載地址,可以使用Uri.parse("http://")包裝成Uri對(duì)象
DownloadManager.Request req = new DownloadManager.Request(uri);
//通過(guò)setAllowedNetworkTypes方法可以設(shè)置允許在何種網(wǎng)絡(luò)下下載
req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 此方法表示在下載過(guò)程中通知欄會(huì)一直顯示該下載,在下載完成后仍然會(huì)顯示,
// 直到用戶(hù)點(diǎn)擊該通知或者消除該通知愿题。還有其他參數(shù)可供選擇
//req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//我希望靜悄悄地下載
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
// 設(shè)置下載文件存放的路徑,同樣你可以選擇以下方法存放在你想要的位置途茫。
// setDestinationUri
// setDestinationInExternalPublicDir
req.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, title);
// 設(shè)置一些基本顯示信息
//req.setTitle("Android.apk");
//req.setDescription("下載完后請(qǐng)點(diǎn)擊打開(kāi)");
req.setMimeType("application/vnd.android.package-archive");
// 構(gòu)建完下載任務(wù), 傳入downloadmanager進(jìn)行下載, 是不是很像我們使用迅雷下載電影
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId = dm.enqueue(req);
下載的基本配置就是這樣, 還是挺簡(jiǎn)單的, 并且性能較高.
在實(shí)際使用中我們更應(yīng)該關(guān)心的是下載前的準(zhǔn)備工作和下載后的善后工作.
下載前的準(zhǔn)備工作
首先, 我的需求是發(fā)現(xiàn)有新版本, 先下載, 下載完后等到用戶(hù)再次打開(kāi)軟件進(jìn)行更新操作. 所以這里就有一個(gè)判斷最新軟件包是否下載完的邏輯判斷, 沒(méi)有就下載, 如果已經(jīng)就直接進(jìn)入到安裝環(huán)節(jié).
/**
* 下載APK文件
*
* @param context
* @param url
* @param info
* @param appName
*/
public void downloadApk(MainActivity mainActivity, final Context context, String url, String info, final String appName) {
//獲取存儲(chǔ)的下載ID
long downloadId = (long) SPUtil.get(context, DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
if (downloadId != -1) {
//獲取當(dāng)前狀態(tài)
int status = getDownloadStatus(downloadId);
//狀態(tài)為下載成功
if (DownloadManager.STATUS_SUCCESSFUL == status) {
//獲取下載路徑URI
final Uri downloadUri = getDownloadUri(downloadId);
if (downloadUri != null) {
//存在下載的APK桌吃,如果兩個(gè)APK相同娜睛,啟動(dòng)更新界面哆键。否之則刪除掘托,重新下載。
//安裝邏輯......
return;
} else {
//刪除下載任務(wù)以及文件
mDownloadManager.remove(downloadId);
}
}
start(context, url, info, appName);
} else if (DownloadManager.STATUS_FAILED == status) {
//下載失敗,重新下載
start(context, url, info, appName);
} else {
Log.d(context.getPackageName(), "apk is already downloading");
}
} else {
//不存在downloadId籍嘹,沒(méi)有下載過(guò)APK
start(context, url, info, appName);
}
}
上面代碼中涉及獲取下載文件以及判斷下載狀態(tài)的操作, 均與downloadmanager有關(guān):
//獲取下載文件
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null) {
if (c.moveToFirst()) {
String fileUri = c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
// TODO 你可以在這里處理你的文件
}
c.close();
}
//獲取下載狀態(tài)
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null && c.moveToFirst()) {
int status = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
switch (status) {
case DownloadManager.STATUS_PENDING:
break;
case DownloadManager.STATUS_PAUSED:
break;
case DownloadManager.STATUS_RUNNING:
break;
case DownloadManager.STATUS_SUCCESSFUL:
break;
case DownloadManager.STATUS_FAILED:
break;
}
if (c != null) {
c.close();
}
當(dāng)然, 下載前需要先判斷一下能不能下載, WiFi有沒(méi)有打開(kāi), 沒(méi)有打開(kāi)跳轉(zhuǎn)到手機(jī)設(shè)置頁(yè)
public boolean canDownload() {
try {
int state = mContext.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
public void skipToDownloadManager() {
String packageName = "com.android.providers.downloads";
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
mContext.startActivity(intent);
}
下載后的善后工作
下載完后, downloadmanager會(huì)發(fā)送一條DownloadManager.ACTION_DOWNLOAD_COMPLETE廣播闪盔,并傳遞downloadId作為參數(shù), 我們可以自定義一個(gè)廣播接收器接收這條廣播完成自動(dòng)安裝. 但這里我們時(shí)重啟APP后安裝, 所以用不到這個(gè)廣播功能.
但是這里我們要做的工作是安裝完后刪除apk文件, 雖然很多手機(jī)能夠自動(dòng)安裝后刪除, 但為了以防萬(wàn)一嘛.
這個(gè)刪除判斷放在下載文件前, 邏輯是: 如果存在apk文件并且已經(jīng)安裝過(guò)了就刪除.
public void removeFile(Context context) {
//獲取存儲(chǔ)的下載ID
long downloadId = (long) SPUtil.get(context, DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
if (downloadId != -1) {
//或者使用mDownloadManager.remove(downloadId)直接刪除文件;
Uri filePath = getDownloadUri(downloadId);
if (filePath != null) {
//刪除之前先判斷用戶(hù)是否已經(jīng)安裝了,安裝了才刪除, 判斷邏輯還是檢測(cè)versionCode
if (!compare(getApkInfo(context, filePath.getPath()), context)) {
File downloadFile = new File(filePath.getPath());
if (null != downloadFile && downloadFile.exists()) {
downloadFile.delete();
}
}
}
}
}
三. 覆蓋安裝
思路
最后就是覆蓋安裝了, 看你選擇什么策略了, 我這里是直接彈出一個(gè)用戶(hù)不能拒絕的對(duì)話框, 讓他更新, 以節(jié)省后期維護(hù)成本
if (compare(getApkInfo(context, downloadUri.getPath()), context)) {
AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
builder.setTitle("檢測(cè)到有新版本!");
builder.setMessage(info);
builder.setCancelable(false);
builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startInstall(context, downloadUri);
}
});
mAlertDialog = builder.create();
handler.sendEmptyMessageDelayed(0, 1000);