標籤:android 更新
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>
<version>標籤指定服務端的版本號碼和版本名稱,該版本號碼和版本名稱對應Android項目配置裡的versionCode和versionName(Eclipse ADT項目可在 AndroidManifest.xml中的標籤中找到,Android Studio項目在module的build.gradle中的defaultConfig中找到)。
<url>標籤指定APK的,
<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(200000); httpConn.setReadTimeout(200000); 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(), 0); 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 指定的路徑中的目錄不存在就建立指定目錄,確保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(1, array.length - 1); 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, -1); 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功能介紹及使用樣本
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
【Android】Android程式自動更新