對(duì)于Android app來說,應(yīng)用內(nèi)更新幾乎成了一個(gè)標(biāo)配的功能了。原理其實(shí)不難,今天我們就從零開始擼一個(gè)自己的應(yīng)用內(nèi)更新的demo出來。
先看看最終實(shí)現(xiàn)的效果:

上圖的效果,稍微將功能拆分一下,可以總結(jié)為以下幾點(diǎn)。
1\. 檢查更新;
2\. 最新apk下載;
3\. apk下載成功后應(yīng)用內(nèi)跳轉(zhuǎn)安裝;
1.檢查更新
為了檢驗(yàn)檢查更新的效果,我們需要一個(gè)tomcat服務(wù)器。至于tomcat怎么搭建,這里就不花篇幅去講了,網(wǎng)上資料還是很多的。
Tomcat部署完成后,在Tomcat ROOT目錄上新建一個(gè)本次demo的目錄,并且將新版的apk文件和一個(gè)保存了新版apk相關(guān)信息的json文件放在demo目錄下。如下圖所示:

各位應(yīng)該已經(jīng)想到檢查更新的原理了,其實(shí)就是解析保存了新版apk信息的json文件,然后根據(jù)json中新版apk的版本信息來判斷當(dāng)前apk是否有可以更新。
我們這里模擬一個(gè)新版apk相關(guān)信息的json文件內(nèi)容。
{"data":{"content":"更新內(nèi)容如下:\n 1.xxxxxx;\n 2.xxxxxx;\n 3.xxxxxx;\n","id":"1","api_key":"android","version_code":"2","version_name":"1.0.2"},"msg":"獲取成功","status":1}
可以看到,對(duì)于檢查更新來說,最重要的幾個(gè)信息都包含在data字段中,包括了更新內(nèi)容,新版apk版本號(hào),新版apk版本名稱等。當(dāng)然,根據(jù)實(shí)際需求,這個(gè)json可能會(huì)有所不同,具體項(xiàng)目中可以做一些修改,屆時(shí)解析的時(shí)候稍作改動(dòng)就好了。
接下來完成檢查更新相關(guān)代碼:
/**
* 檢查更新
*/
public void checkUpdate(OnCheckUpdateListener onCheckUpdateListener) {
mOnCheckUpdateListener = onCheckUpdateListener;
HttpUtils.sendOkHttpRequest(mUpdateHelper.getNewestApkVersionInfoUrl(), new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtils.showToast("check update failed.");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String strJson = response.body().string();
Log.e("onResponse", "response.body().string() = " + strJson);
if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
} else {
sendMessage(MSG_ON_NEWEST, null);
}
}
});
}
/**
* 解析json數(shù)據(jù)
*
* @param jsonData json數(shù)據(jù)
* {
* "data": {
* "content": "更新內(nèi)容如下:1.xxxxxx;/n 2.xxxxxx;/n 3.xxxxxx;/n",
* "id": "1",
* "api_key": "update test",
* "version_code": "2"
* },
* "msg": "獲取成功",
* "status": 1
* }
* @return dataBean
*/
private DataBean parseJson(String jsonData) {
DataBean dataBean = new DataBean();
try {
JSONObject jsonObject = new JSONObject(jsonData);
JSONObject dataObject = jsonObject.getJSONObject("data");
dataBean.setContent(dataObject.getString("content"));
dataBean.setId(dataObject.getInt("id"));
dataBean.setApiKey(dataObject.getString("api_key"));
dataBean.setVersionCode(dataObject.getInt("version_code"));
dataBean.setVersionName(dataObject.getString("version_name"));
// Log.e("parseJson", "content " + dataObject.getString("content"));
// Log.e("parseJson", "id " + dataObject.getInt("id"));
// Log.e("parseJson", "api_key " + dataObject.getString("api_key"));
// Log.e("parseJson", "version_code " + dataObject.getInt("version_code"));
// Log.e("parseJson", "version_name " + dataObject.getString
// ("version_name"));
} catch (Exception e) {
e.printStackTrace();
}
return dataBean;
}
2.最新apk文件下載
如何檢查更新的代碼搞定了,接下來是稍微麻煩一點(diǎn)的,就是下載apk文件。
由于下載文件可以作為一個(gè)單獨(dú)的功能,所以將下載文件這一塊單獨(dú)獨(dú)立出來作為一個(gè)子模塊來編寫。其實(shí)谷歌官方有提供專門的DownloadManager來下載文件,但是很多國(guó)內(nèi)的rom把DownloadManager閹割了。所以這里我們自己來擼一個(gè)DownloadManager,具體代碼如下,注釋還是比較清晰的,就不多做解釋了。
DownloadManager.java:
public class DownloadManager {
private static volatile DownloadManager manager = null;
private DownloadTask downloadTask;
private String mFileName;
private String mFileParentPath;
private DownloadManager() {
mFileParentPath = DownloadHelper.getDownloadParentFilePath();
}
public static DownloadManager getInstance() {
if (manager == null) {
synchronized (DownloadManager.class) {
if (manager == null) {
manager = new DownloadManager();
}
}
}
return manager;
}
/**
* 開啟下載任務(wù)
*
* @param url 下載鏈接
* @param onDownloadListener onDownloadListener
*/
public void startDownload(String url, OnDownloadListener onDownloadListener) {
startDownload(url, DownloadHelper.getDownloadParentFilePath(), DownloadHelper
.getUrlFileName(url)
, onDownloadListener);
}
/**
* 開啟下載任務(wù)
*
* @param url 下載鏈接
* @param fileName 指定下載文件名
* @param onDownloadListener onDownloadListener
*/
public void startDownload(String url, @NonNull String fileName, OnDownloadListener
onDownloadListener) {
startDownload(url, DownloadHelper.getDownloadParentFilePath(), fileName,
onDownloadListener);
}
/**
* 開啟下載任務(wù)
*
* @param url 下載鏈接
* @param fileParentPath 指定下載文件目錄
* @param fileName 指定下載文件名
* @param onDownloadListener onDownloadListener
*/
public void startDownload(String url, @NonNull String fileParentPath, @NonNull String fileName,
OnDownloadListener onDownloadListener) {
if (StringUtils.isEmpty(fileParentPath))
fileParentPath = DownloadHelper.getDownloadParentFilePath();
if (StringUtils.isEmpty(fileName))
fileName = DownloadHelper.getUrlFileName(url);
mFileParentPath = fileParentPath;
mFileName = fileName;
if (downloadTask == null) {
downloadTask = new DownloadTask(onDownloadListener);
downloadTask.execute(url, fileParentPath, fileName);
downloadTask.setOnDownloadTaskFinshedListener(new DownloadTask
.OnDownloadTaskFinshedListener() {
@Override
public void onFinished() {
downloadTask = null;
}
@Override
public void onCanceled() {
//下載任務(wù)取消,刪除已下載的文件
clearCacheFile(getDownloadFilePath());
}
@Override
public void onException() {
//下載任務(wù)異常,刪除已下載的文件
clearCacheFile(getDownloadFilePath());
}
});
}
}
/**
* 暫停下載任務(wù)
*/
public void pauseDownload() {
if (downloadTask != null) {
downloadTask.pauseDownload();
}
}
/**
* 取消下載任務(wù)
*/
public void cancelDownload() {
if (downloadTask != null) {
downloadTask.cancelDownload();
}
}
/**
* 清除下載的全部cache文件
*/
public void clearAllCacheFile() {
if (StringUtils.isEmpty(mFileParentPath))
return;
FileUtils.delAllFile(mFileParentPath);
}
/**
* 清除下載的cache文件
*
* @param filePath 要?jiǎng)h除文件的絕對(duì)路徑
*/
public void clearCacheFile(String filePath) {
if (StringUtils.isEmpty(filePath))
return;
FileUtils.delFile(filePath);
}
/**
* 獲取下載文件的全路徑
*
* @return 下載文件的全路徑
*/
public String getDownloadFilePath() {
if (StringUtils.isEmpty(mFileParentPath) || StringUtils.isEmpty(mFileName))
return null;
return mFileParentPath + "/" + mFileName;
}
}
DownloadTask.java
public class DownloadTask extends AsyncTask<String, Integer, Integer> {
private static final int TYPE_SUCCESS = 0;
private static final int TYPE_FAILED = 1;
private static final int TYPE_PAUSED = 2;
private static final int TYPE_CANCELED = 3;
private OnDownloadListener mOnDownloadListener;
private OnDownloadTaskFinshedListener mOnDownloadTaskFinshedListener;
private boolean isCanceled = false;
private boolean isPaused = false;
private int lastProgress;
private File mDownloadFile = null;
private long mContentLength; // 記錄url下載文件的長(zhǎng)度
public DownloadTask(OnDownloadListener onDownloadListener) {
mOnDownloadListener = onDownloadListener;
}
public void setOnDownloadTaskFinshedListener(OnDownloadTaskFinshedListener
onDownloadTaskFinshedListener) {
mOnDownloadTaskFinshedListener = onDownloadTaskFinshedListener;
}
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;
RandomAccessFile savedFile = null;
try {
long downloadLength = 0; // 記錄已下載的文件長(zhǎng)度
String downloadUrl = params[0];
String fileParentPath = params[1];
String fileName = params[2];
mDownloadFile = new File(fileParentPath, fileName);
if (mDownloadFile.exists()) {
downloadLength = mDownloadFile.length();
}
mContentLength = getContentLength(downloadUrl);
if (mContentLength == 0) {
return TYPE_FAILED;
} else if (mContentLength == downloadLength) {
// 已下載字節(jié)和文件總字節(jié)相等,說明已經(jīng)下載完成了
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
// 斷點(diǎn)下載,指定從哪個(gè)字節(jié)開始下載
.addHeader("RANGE", "bytes=" + downloadLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();
savedFile = new RandomAccessFile(mDownloadFile, "rw");
savedFile.seek(downloadLength); // 跳過已下載的字節(jié)
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1) {
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPaused) {
return TYPE_PAUSED;
} else {
total += len;
savedFile.write(b, 0, len);
// 計(jì)算已下載的百分比
int progress = (int) ((total + downloadLength) * 100 / mContentLength);
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (savedFile != null) {
savedFile.close();
}
if (isCanceled && mDownloadFile != null) {
mDownloadFile.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return TYPE_FAILED;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
mOnDownloadListener.onProgress(progress);
lastProgress = progress;
}
}
@Override
protected void onPostExecute(Integer status) {
switch (status) {
case TYPE_SUCCESS:
if (mContentLength != mDownloadFile.length()) {
if (mOnDownloadListener != null)
mOnDownloadListener.onException();
//下載數(shù)據(jù)異常,告知downManager下載任務(wù)已失敗
if (mOnDownloadTaskFinshedListener != null)
mOnDownloadTaskFinshedListener.onException();
} else {
if (mOnDownloadListener != null)
mOnDownloadListener.onSuccess();
}
break;
case TYPE_FAILED:
if (mOnDownloadListener != null)
mOnDownloadListener.onFailed();
break;
case TYPE_PAUSED:
if (mOnDownloadListener != null)
mOnDownloadListener.onPaused();
break;
case TYPE_CANCELED:
if (mOnDownloadListener != null)
mOnDownloadListener.onCanceled();
if (mOnDownloadTaskFinshedListener != null)
mOnDownloadTaskFinshedListener.onCanceled();
default:
break;
}
if (mOnDownloadTaskFinshedListener != null)
mOnDownloadTaskFinshedListener.onFinished();
}
/**
* 暫停下載任務(wù)
*/
public void pauseDownload() {
isPaused = true;
}
/**
* 取消下載任務(wù)
*/
public void cancelDownload() {
isCanceled = true;
}
/**
* 獲取下載文件長(zhǎng)度
* @param downloadUrl 下載文件url
* @return 下載文件長(zhǎng)度
* @throws IOException IOException
*/
private long getContentLength(String downloadUrl) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
public interface OnDownloadTaskFinshedListener {
/**
* 下載任務(wù)已結(jié)束
*/
void onFinished();
/**
* 下載任務(wù)已取消
*/
void onCanceled();
/**
* 下載文件異常,不是完整的文件或者文件包異常
*/
void onException();
}
}
3.apk下載成功后應(yīng)用內(nèi)跳轉(zhuǎn)安裝
在7.0以前,通過apk的uri跳轉(zhuǎn)安裝就可以了。7.0以后由于StrictMode API 政策,需要通過FileProvider來安裝apk文件,使用步驟也比較簡(jiǎn)單。
第一步:在AndroidManifest.xml清單文件中注冊(cè)provider
exported:必須為false,true會(huì)報(bào)安全異常。
grantUriPermissions:true,授予 URI 臨時(shí)訪問權(quán)限。
authorities 組件標(biāo)識(shí)
<provider
android:name="android.support.v4.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>
第二步:指定共享的目錄
在res資源目錄下新建一個(gè)xml目錄,并新建一個(gè)名字和上一步指定的resource文件相同名字的xml文件,這里是:file_paths。 file_path.xml的內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="" name="updateDemo" />
</paths>
< files-path />代表根目錄: Context.getFilesDir()
< external-path />代表根目錄: Environment.getExternalStorageDirectory()
< cache-path />代表根目錄: getCacheDir()
external-path path=""代表根目錄,也就是說你可以向其它的應(yīng)用共享根目錄及其子目錄下任何一個(gè)文件了。
第三步:使用FileProvider
/**
* 安裝 apk
*
* @param apkPath apk全路徑
*/
public void installApk(String apkPath) {
if (StringUtils.isEmpty(apkPath)) {
Log.e("tag", "apkPath is null.");
return;
}
File file = new File(apkPath);
Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于沒有在Activity環(huán)境下啟動(dòng)Activity,設(shè)置下面的標(biāo)簽
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) { //判斷版本是否在7.0以上
//參數(shù)1 上下文, 參數(shù)2 Provider主機(jī)地址 和配置文件中保持一致 參數(shù)3 共享的文件
Uri apkUri = FileProvider.getUriForFile(AppUtils.getContext(), BuildConfig
.APPLICATION_ID + ".fileProvider", file);
//添加這一句表示對(duì)目標(biāo)應(yīng)用臨時(shí)授權(quán)該Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
AppUtils.getContext().startActivity(intent);
}
接下來完成UpdateManager的全部代碼:
package com.zyw.horrarndoo.updatedemo.update;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.FileProvider;
import android.util.Log;
import com.zyw.horrarndoo.updatedemo.BuildConfig;
import com.zyw.horrarndoo.updatedemo.bean.DataBean;
import com.zyw.horrarndoo.updatedemo.download.DownloadHelper;
import com.zyw.horrarndoo.updatedemo.download.DownloadManager;
import com.zyw.horrarndoo.updatedemo.download.OnDownloadListener;
import com.zyw.horrarndoo.updatedemo.net.HttpUtils;
import com.zyw.horrarndoo.updatedemo.utils.AppUtils;
import com.zyw.horrarndoo.updatedemo.utils.SpUtils;
import com.zyw.horrarndoo.updatedemo.utils.StringUtils;
import com.zyw.horrarndoo.updatedemo.utils.ToastUtils;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import static com.zyw.horrarndoo.updatedemo.constant.Constant.SP_KEY_CACHE_APK_VERSION_CODE;
import static com.zyw.horrarndoo.updatedemo.constant.Constant.SP_KEY_CACHE_VALID_TIME;
/**
* Created by Horrarndoo on 2018/2/1.
* <p>
*/
public class UpdateManager {
private static volatile UpdateManager manager = null;
private static final int MSG_ON_START = 1;
private static final int MSG_ON_PROGRESS = 2;
private static final int MSG_ON_DOWNLOAD_FINISH = 3;
private static final int MSG_ON_FAILED = 4;
private static final int MSG_ON_CANCLE = 5;
private static final int MSG_ON_FIND_NEW_VERSION = 6;
private static final int MSG_ON_NEWEST = 7;
private static final int MSG_ON_UPDATE_EXCEPTION = 8;
private DownloadManager mDownloadManager;
private OnUpdateListener mOnUpdateListener;
private OnCheckUpdateListener mOnCheckUpdateListener;
private int mNewestVersionCode;
private String mNewestVersionName;
private String mNewVersionContent;
/**
* 最后一次保存cache的時(shí)間
*/
private long mLastCacheSaveTime = 0;
private UpdateManager() {
mDownloadManager = DownloadManager.getInstance();
}
/**
* 獲取updateManager實(shí)例
*
* @return updateManager實(shí)例
*/
public static UpdateManager getInstance() {
if (manager == null) {
synchronized (UpdateManager.class) {
if (manager == null) {
manager = new UpdateManager();
}
}
}
return manager;
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_ON_START:
if (mOnUpdateListener != null)
mOnUpdateListener.onStartUpdate();
break;
case MSG_ON_PROGRESS:
if (mOnUpdateListener != null)
mOnUpdateListener.onProgress((Integer) msg.obj);
break;
case MSG_ON_DOWNLOAD_FINISH:
if (mOnUpdateListener != null)
mOnUpdateListener.onApkDownloadFinish((String) msg.obj);
installApk((String) msg.obj);
break;
case MSG_ON_FAILED:
if (mOnUpdateListener != null)
mOnUpdateListener.onUpdateFailed();
break;
case MSG_ON_CANCLE:
if (mOnUpdateListener != null)
mOnUpdateListener.onUpdateCanceled();
break;
case MSG_ON_UPDATE_EXCEPTION:
if (mOnUpdateListener != null)
mOnUpdateListener.onUpdateException();
break;
case MSG_ON_FIND_NEW_VERSION:
DataBean dataBean = (DataBean) msg.obj;
mNewestVersionCode = dataBean.getVersionCode();
mNewestVersionName = dataBean.getVersionName();
mNewVersionContent = dataBean.getContent();
if (mOnCheckUpdateListener != null)
mOnCheckUpdateListener.onFindNewVersion(mNewestVersionName,
mNewVersionContent);
break;
case MSG_ON_NEWEST:
if (mOnCheckUpdateListener != null)
mOnCheckUpdateListener.onNewest();
break;
}
}
};
/**
* 檢查更新
*
* @param apkInfoUrl 服務(wù)器端保存新版apk相關(guān)信息json的url
* @param onCheckUpdateListener onCheckUpdateListener
*/
public void checkUpdate(String apkInfoUrl, OnCheckUpdateListener onCheckUpdateListener) {
mOnCheckUpdateListener = onCheckUpdateListener;
HttpUtils.sendOkHttpRequest(apkInfoUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtils.showToast("check update failed.");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String strJson = response.body().string();
Log.e("onResponse", "response.body().string() = " + strJson);
if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
//最后一次緩存的時(shí)間超過緩存文件有效期,或者最后一次緩存的apk不是最新版本的apk,刪除緩存apk
if ((System.currentTimeMillis() - mLastCacheSaveTime > getCacheSaveValidTime())
|| (getCacheApkVersionCode() != parseJson(strJson).getVersionCode())) {
clearCacheApkFile();
setCacheApkVersionCode(parseJson(strJson).getVersionCode());
}
sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
} else {
sendMessage(MSG_ON_NEWEST, null);
//當(dāng)前已經(jīng)是最新版本APK,清除本地已經(jīng)緩存的apk安裝包
clearCacheApkFile();
}
}
});
}
/**
* 開始更新App
* <p>
* 此時(shí)開始正式下載更新Apk
*
* @param apkUrl 服務(wù)端最新apk文件url
* @param onUpdateListener onUpdateListener
*/
public void startToUpdate(String apkUrl, OnUpdateListener onUpdateListener) {
mOnUpdateListener = onUpdateListener;
if (StringUtils.isEmpty(mNewestVersionName) || mNewestVersionCode == 0)
return;
downloadNewestApkFile(apkUrl, mNewestVersionCode, mNewestVersionName);
}
/**
* 設(shè)置緩存文件有效時(shí)間,單位:秒
* <p>
* 默認(rèn)緩存有效期為7天
*
* @param cacheValidTime 緩存文件有效時(shí)間
*/
public void setCacheSaveValidTime(long cacheValidTime) {
SpUtils.putLong(SP_KEY_CACHE_VALID_TIME, cacheValidTime);
}
/**
* 獲取緩存文件有效時(shí)間,單位:秒
* <p>
* 默認(rèn)緩存有效期為7天
*
* @return 緩存文件有效時(shí)間
*/
public long getCacheSaveValidTime() {
return SpUtils.getLong(SP_KEY_CACHE_VALID_TIME, 60 * 60 * 24 * 7);
}
/**
* 設(shè)置緩存文件版本號(hào)
*
* @param versionCode cacheApk版本號(hào)
*/
public void setCacheApkVersionCode(int versionCode) {
SpUtils.putInt(SP_KEY_CACHE_APK_VERSION_CODE, versionCode);
}
/**
* 獲取緩存文件版本號(hào)
*
* @return 緩存文件版本號(hào)
*/
public int getCacheApkVersionCode() {
return SpUtils.getInt(SP_KEY_CACHE_APK_VERSION_CODE, 0);
}
/**
* 取消更新
*/
public void cancleUpdate() {
//保留下載已完成的部分apk cache文件,cache文件最多保留7天
mDownloadManager.pauseDownload();
}
/**
* 清除已下載的APK緩存
*/
public void clearCacheApkFile() {
Log.e("tag", "清除所有的apk文件");
mDownloadManager.clearAllCacheFile();
}
/**
* 下載最新版本的APK文件
*
* @param url 服務(wù)端最新apk文件url
* @param newestVersionCode 最新版本APK版本號(hào)
* @param newestVersionName 最新版本APK版本名稱
*/
private void downloadNewestApkFile(String url, int newestVersionCode, String
newestVersionName) {
String apkFileName = getApkNameWithVersionName(DownloadHelper.getUrlFileName(url),
newestVersionName);
sendMessage(MSG_ON_START, null);
mDownloadManager.startDownload(url, apkFileName, new
OnDownloadListener() {
@Override
public void onException() {
sendMessage(MSG_ON_UPDATE_EXCEPTION, null);
}
@Override
public void onProgress(int progress) {
sendMessage(MSG_ON_PROGRESS, progress);
}
@Override
public void onSuccess() {
mLastCacheSaveTime = System.currentTimeMillis();
sendMessage(MSG_ON_DOWNLOAD_FINISH, mDownloadManager.getDownloadFilePath());
}
@Override
public void onFailed() {
mLastCacheSaveTime = System.currentTimeMillis();
sendMessage(MSG_ON_FAILED, null);
}
@Override
public void onPaused() {
mLastCacheSaveTime = System.currentTimeMillis();
//取消升級(jí)時(shí),調(diào)用download pause,保留已下載的部分apk文件
sendMessage(MSG_ON_CANCLE, null);
}
@Override
public void onCanceled() {
//為了保證斷點(diǎn)續(xù)傳,升級(jí)時(shí),調(diào)用download pause,不使用cancle,onCancle不會(huì)被調(diào)用
mLastCacheSaveTime = System.currentTimeMillis();
sendMessage(MSG_ON_CANCLE, null);
}
});
}
private void sendMessage(int msgWhat, Object o) {
Message msg = Message.obtain();
msg.what = msgWhat;
msg.obj = o;
mHandler.sendMessage(msg);
}
/**
* 解析json數(shù)據(jù)
*
* @param jsonData json數(shù)據(jù)
* @return dataBean
*/
private DataBean parseJson(String jsonData) {
...
return dataBean;
}
/**
* 獲取帶版本名稱的apk文件名
*
* @param apkName apk原名
* @return 帶版本名稱的apk文件名
*/
private String getApkNameWithVersionName(String apkName, String versionName) {
if (StringUtils.isEmpty(apkName))
return apkName;
apkName = apkName.substring(apkName.lastIndexOf("/") + 1, apkName.indexOf("" +
".apk"));
Log.e("tag", "newApkName = " + apkName + "_v" + versionName + ".apk");
return apkName + "_v" + versionName + ".apk";
}
/**
* 安裝 apk
*
* @param apkPath apk全路徑
*/
public void installApk(String apkPath) {
...
}
}
最后在Activity中調(diào)用UpdateManager就可以簡(jiǎn)單的檢查更新我們的apk啦,不過有一點(diǎn)稍微要注意下,就是運(yùn)行時(shí)權(quán)限的獲取,如果6.0以上的手機(jī)沒有獲取sd卡權(quán)限的話,我們的程序是無法正常運(yùn)行的,所以如果用戶沒有給予權(quán)限的話,就退出程序并給出提示。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private UpdateManager mUpdateManager;
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
...
initPermission();
mUpdateManager = UpdateManager.getInstance();
}
private void initProgressDialog() {
...
}
private void initPermission() {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission
.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission
.WRITE_EXTERNAL_STORAGE}, 1);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_check_update:
mUpdateManager.checkUpdate(Constant.VERSION_INFO_URL, new OnCheckUpdateListener() {
@Override
public void onFindNewVersion(String versionName, String newVersionContent) {
String content = "最新版: V" + versionName + "\n" + newVersionContent;
buildNewVersionDialog(content);
}
@Override
public void onNewest() {
showToast("app is newest version.");
dismissProgressDialog();
}
});
break;
case R.id.btn_clear_apk:
mUpdateManager.clearCacheApkFile();
break;
default:
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] != PackageManager
.PERMISSION_GRANTED) {
Toast.makeText(this, "拒絕權(quán)限將無法使用程序", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
private void dismissProgressDialog() {
mProgressDialog.setProgress(0);
if (mProgressDialog.isShowing())
mProgressDialog.dismiss();
}
/**
* 創(chuàng)建發(fā)現(xiàn)新版本apk alert dialog
*
* @param message dialog顯示消息
*/
private void buildNewVersionDialog(String message) {
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("發(fā)現(xiàn)新版本")
.setIcon(R.mipmap.ic_launcher)
.setMessage(message)
.setPositiveButton("更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mUpdateManager.startToUpdate(Constant.APK_URL, mOnUpdateListener);
}
})
.setNegativeButton("取消", null)
.create();
dialog.show();
}
private OnUpdateListener mOnUpdateListener = new OnUpdateListener() {
@Override
public void onStartUpdate() {
mProgressDialog.show();
}
@Override
public void onProgress(int progress) {
mProgressDialog.setProgress(progress);
}
@Override
public void onApkDownloadFinish(String apkPath) {
showToast("newest apk download finish. apkPath: " + apkPath);
Log.e("tag", "newest apk download finish. apkPath: " + apkPath);
dismissProgressDialog();
//所有的更新全部在updateManager中完成,Activity在這里只是做一些界面上的處理
}
@Override
public void onUpdateFailed() {
showToast("update failed.");
dismissProgressDialog();
}
@Override
public void onUpdateCanceled() {
showToast("update cancled.");
dismissProgressDialog();
}
@Override
public void onUpdateException() {
showToast("update exception.");
dismissProgressDialog();
}
};
}
至此已經(jīng)基本完成此次的應(yīng)用內(nèi)更新模塊了,用起來還是很簡(jiǎn)單的,UpdateManager只做更新相關(guān)判斷處理,DownloadManager則處理下載文件、緩存文件續(xù)傳及相關(guān)狀態(tài)處理,并且UpdateManager和DownloadManager都是單獨(dú)可以作為一個(gè)獨(dú)立模塊在實(shí)際項(xiàng)目中使用的。