App自動更新的步驟可分為三步:
- 檢查更新(如果有更新進行第2步,否則傳回)
- 下載下傳新版的APK安裝包
- 安裝APK
下面對這三步進行解釋,其中會穿插相應代碼,App自動更新的這三步全部被封裝到了一個單獨的Updater類中,可以直接拿來使用,我會在文章最後貼出源碼github位址。
Updater 使用示例
通過單一的類
Updater
可以友善的實作自動檢查更新、下載下傳安裝包和自動安裝,可以監聽下載下傳進度,可以自定義更新提示等。儲存路徑可以自由書寫,如果路徑中某個目錄不存在會自動建立,流式API接口易于使用。下面是使用示例,一行代碼搞定自動更新:
String savePath = Environment.getExternalStorageDirectory()
+ "/whinc/download/whinc.apk";
String updateUrl = "http://192.168.1.168:8000/update.xml";
Updater.with(mContext)
.downloadListener(mListener)
.update(updateUrl)
.save(savePath)
.create()
.checkUpdate();
第一步:檢查更新
這一步需要服務端的配合,服務端存放一個XML格式的配置檔案(也可以用JSON或其他格式)提供給用戶端檢查更新,update.xml 格式如下:
<?xml version="1.0" encoding="utf-8"?>
<info>
<version>
<code>4</code>
<name>1.0.4</name>
</version>
<url>http://192.168.1.168:8000/test.apk</url>
<description>更新 - 吧啦吧啦;修複 - 吧啦吧啦;增加 - 巴拉巴拉巴</description>
</info>
-
标簽指定服務端的版本号和版本名稱,該版本号和版本名稱對應Android項目配置裡的<version>
和versionCode
(Eclipse ADT項目可在 AndroidManifest.xml中的标簽中找到,Android Studio項目在module的build.gradle中的defaultConfig中找到)。versionName
-
标簽指定APK的下載下傳位址,<url>
-
标簽指定更新内容。<description>
用戶端通過 HTTP 請求服務端的 update.xml檔案,然後解析 update.xml,比較服務端的版本号與本地版本号,如果服務端版本号大于本地版本号說明有更新,則根據 update.xml中指定的APK下載下傳位址下載下傳最新的APK,下面将會詳細說明。
下面是檢查更新的代碼:
/**
* 檢查 App 版本号
*
* @return 如果有新版本傳回true,否則傳回false
*/
private boolean checkVersion() {
URL url;
HttpURLConnection httpConn = null;
try {
url = new URL(mCheckUpdateUrl);
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setConnectTimeout();
httpConn.setReadTimeout();
httpConn.setUseCaches(false); // disable cache for current http connection
httpConn.connect();
if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpConn.getInputStream();
// 解析 XML 資料
if (!parseXml(inputStream)) {
return false;
}
// 比較本地版本号與伺服器版本号
PackageInfo packageInfo = mContext.getPackageManager()
.getPackageInfo(mContext.getPackageName(), );
if (packageInfo.versionCode < mRemoteVersionCode) {
return true;
}
} else {
return false;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} finally {
httpConn.disconnect();
}
return false;
}
首先建立HTTPURLConnection通路服務端update.xml檔案,然後解析服務端傳回的update.xml檔案,并儲存版本資訊、APK下載下傳位址和更新日志,解析完後通過擷取目前用戶端的版本号與服務端版本号比較,如果服務端版本号更大,說明服務端有更新的版本,checkVersion() 方法傳回true,否則傳回false。
下面時檢查更新的代碼,需要注意的是,Android中不允許在主線程(UI線程)中發起網絡請求,是以
checkVersion()
的調用需要放在非主線程中。實作異步請求的方式有多種,這裡我使用 AsyncTask。
public void checkUpdate() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
boolean hasNewVersion = checkVersion();
return hasNewVersion;
}
@Override
protected void onPostExecute(Boolean hasNewVersion) {
super.onPostExecute(hasNewVersion);
if (mCheckUpdateListener == null
|| !mCheckUpdateListener.onCompleted(hasNewVersion, mRemoteVersionCode,
mRemoteVersionName, mUpdateLog, mApkDownloadUrl)) {
if (hasNewVersion) {
showUpdateDialog();
}
}
}
}.execute();
}
下載下傳新版的APK安裝包
showUpdateDialog()
調用後顯示更新提示對話框,在對話框确認按鈕點選事件中,首先建立DownloadManager.Request對象,然後設定該對象的各種屬性如下載下傳儲存路徑、通知欄标題等,最後将該下載下傳請求放到系統服務DownloadManager的下載下傳隊列中,交給系統去處理下載下傳邏輯。 為了監聽下載下傳完成事件,代碼裡注冊了廣播
DownloadManager.ACTION_DOWNLOAD_COMPLETE
。下載下傳進度通過注冊ContentObserver來監聽。
/**
* 顯示更新對話框
*/
private void showUpdateDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(mTitle);
builder.setMessage(mUpdateLog);
builder.setPositiveButton(mDialogOkBtnTxt, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
// 背景下載下傳
mDownloadMgr = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mApkDownloadUrl));
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
// 如果儲存路徑包含子目錄,需要先遞歸建立目錄
if (!createDirIfAbsent(mSavePath)) {
Log.e("TAG", "apk save path can not be created:" + mSavePath);
return;
}
request.setDestinationUri(Uri.fromFile(new File(mSavePath)));
request.setTitle(mNotificationTitle);
request.setTitle(mNotificationMessage);
// 注冊廣播,監聽下載下傳完成事件
mContext.registerReceiver(mCompleteReceiver,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
// 注冊監聽下載下傳進度
mContext.getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"),
true, mContentObserver);
mDownloadId = mDownloadMgr.enqueue(request);
} else {
Log.e("TAG", "can not access external storage!");
return;
}
Toast.makeText(mContext, "正在背景下載下傳...", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton(mDialogCancelBtnTxt, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.create().show();
}
/**
* 如果參數 path 指定的路徑中的目錄不存在就建立指定目錄
*
* @param path 絕對路徑(包含檔案名,例如 '/sdcard/storage/download/test.apk')
* @return 如果成功建立目錄傳回true,否則傳回false
*/
private boolean createDirIfAbsent(String path) {
String[] array = path.trim().split(File.separator);
List<String> dirNames = Arrays.asList(array).subList(, array.length - );
StringBuilder pathBuilder = new StringBuilder(File.separator);
for (String d : dirNames) {
pathBuilder.append(d);
File f = new File(pathBuilder.toString());
if (!f.exists() && !f.mkdir()) {
return false;
}
pathBuilder.append(File.separator);
}
return true;
}
安裝APK
一旦Apk下載下傳完成就會收到廣播消息,此時可以執行安裝APK的動作,不過要先通過下載下傳Id判斷該廣播事件是否是由于我們的APK下載下傳完成發出的,因為系統可能同時有多個下載下傳任務,通過下載下傳id區分。
mCompleteReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -);
if (downloadId == mDownloadId) {
installApk();
release();
}
}
};
下面是 installApk() 方法,首先通過下載下傳Id從DownloadManager中檢索到下載下傳的APK存儲路徑,然後通過Intent安裝下載下傳的APK,代碼非常簡單。注意,Intent設定辨別為
Intent.FLAG_ACTIVITY_NEW_TASK
,否則不能正常啟動安裝程式。
/**
* 替換安裝目前App,注意:簽名一緻
*/
private void installApk() {
// 擷取下載下傳的 APK 位址
Uri apkUri = mDownloadMgr.getUriForDownloadedFile(mDownloadId);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
mContext.startActivity(intent);
}
github 源碼
whinc/Android-UpdateManager
比較好的參考資料:
DownloadManager | Android Developers
Android系統下載下傳管理DownloadManager功能介紹及使用示例