需求:app本地检查更新,后台有新版apk则下载新版apk并安装。
业务逻辑流程图基本上是这样的 :
查阅的一些资料后基本上得到三种解决方案:
1,AsyncTask+Http下载;
2,DownloadManager;
3,Service+Http下载;
而DownloadManager(以下表示为DM)相对来说比较简单这里就选择第二种方案:
DM是一种处理长时间运行的HTTP下载的系统服务。客户端可以通过URI请求下载特定的目标文件。下载管理器将在后台进行下载,负责HTTP交互并在发生故障或连接更改和系统重新启动后重试下载;这是官方对DownloadManager的说明。
DM中主要包含两个静态内部类DownloadManager.Request(以下表示为DM.Request)和DownloadManager.Query(以下表示为DM.Query);DM.Request主要是用来配置请求信息,比如是否在系统通知栏显示下载,设置下的网络类型(wifi,移动网络)下载后文件的存储路径等;DM.Query主要是用来查询跟下载相关的信息;DM类中COLUMN_开头的静态常量都是可以通过DM.Query查询的如COLUMN_TOTAL_SIZE_BYTES查询下载文件的总字节数,COLUMN_BYTES_DOWNLOADED_SO_FAR查询当前已下载的字节数,COLUMN_STATUS查询下载状态;
下面贴出核心代码:
一,封装常用下载方法FileDownloadManager 工具
public class FileDownloadManager {
private DownloadManager dManager;
private Context context;
private static FileDownloadManager instance;
private FileDownloadManager(Context context) {
dManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
this.context = context.getApplicationContext();
}
public static FileDownloadManager getInstance(Context context) {
if (instance == null) {
instance = new FileDownloadManager(context);
}
return instance;
}
/**
* @param apkUri 文件下载路径
* @param title 通知标题
* @param description 通知message
* @return download id 每一个下载任务的唯一标识
*/
public long startDownload(String apkUri, String title, String description, String appName) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUri));
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//设置文件的保存的位置[三种方式]
//第一种
//file:///storage/emulated/0/Android/data/your-package/files/Download/test/test.apk
//request.setDestinationInExternalFilesDir(UpdateActivity.this,Environment.DIRECTORY_DOWNLOADS, "test"+ File.separator+"test.apk");
//第二种
//file:///storage/emulated/0/Download/test/test.apk
//request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "test"+ File.separator+"test.apk");
//第三种 自定义文件路径
//file:///storage/emulated/0/Android/data/your-package/files/Download/test/test.apk
Uri filePath = Uri.parse("file://"+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator+"test"+ File.separator+"test.apk");
request.setDestinationUri(filePath);
request.setTitle(title);//通知栏标题
request.setDescription(description);//通知栏内容
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setMimeType("application/vnd.android.package-archive");
// 设置为可被媒体扫描器找到
request.allowScanningByMediaScanner();
// 设置为可见和可管理
request.setVisibleInDownloadsUi(true);
long downloadId = dManager.enqueue(request);
// 把当前下载任务的downloadId保存起来
SpUtil.putLong("downloadId",downloadId);//保存此次下载ID
return downloadId;
}
/**
* 获取文件保存的路径
*
* @param downloadId 下载任务id
* @return file path
* @see FileDownloadManager#getDownloadUri(long)
*/
public String getDownloadPath(long downloadId) {
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dManager.query(query);
if (c != null) {
try {
if (c.moveToFirst()) {
return c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
}
} finally {
c.close();
}
}
return null;
}
/**
* 获取保存文件的地址
*
* @param downloadId 下载任务id
* @see FileDownloadManager#getDownloadPath(long)
*/
public Uri getDownloadUri(long downloadId) {
return dManager.getUriForDownloadedFile(downloadId);
}
public DownloadManager getDownloadManager() {
return dManager;
}
/**
* 获取下载状态
*
* @param downloadId 下载任务id
* @return int
* 以下是五种下载状态对应的状态码
* @see DownloadManager#STATUS_PENDING
* @see DownloadManager#STATUS_PAUSED
* @see DownloadManager#STATUS_RUNNING
* @see DownloadManager#STATUS_SUCCESSFUL
* @see DownloadManager#STATUS_FAILED
*/
public int getDownloadStatus(long downloadId) {
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dManager.query(query);
if (c != null) {
try {
if (c.moveToFirst()) {
return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
}
} finally {
c.close();
}
}
return -1;
}
/**
* @param downloadId 下载Id
* @return 当前下载进度
*/
public int queryProgress(long downloadId){
int[] bytes = new int[] { -1, -1 };
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
int ret = 0;
Cursor c = null;
try {
c = dManager.query(query);
if (c != null && c.moveToFirst()) {
bytes[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//当前下载字节
bytes[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));//文件总字节
ret = bytes[0] * 100 / bytesAndStatus[1];
}
} finally {
if (c != null) { c.close(); }
}
return ret;
}
/**
* @param downloadId 下载任务Id
* @return 返回取消的下载任务个数
*/
public int remove(long downloadId){
int removeCount = 0;
if (downloadId != -1L) {
int downloadStatus = FileDownloadManager.getInstance(App.getContext()).getDownloadStatus(downloadId);
if (downloadStatus == DownloadManager.STATUS_RUNNING){
removeCount = dManager.remove(downloadId);
SpUtil.remove("downloadId");
}
}
return removeCount;
}
}
二,启动下载
与后台比较版本号比较简单就不多说了,在UpdateActivity中点击确认更新时的核心代码
注意这里需要检查以下SD卡权限,权限通过后调用downloadApk()方法启动下载
Timer主要用于查询当前下载进度,更新进度条对话框
private void downloadApk() {
//查看是否已经下载过apk,如果已经下载过则校验版本,和服务器版本一致则直接安装,否则删除apk并重新下载
// 获取存储的下载ID
long downloadId = SpUtil.getLong("downloadId", -1L);
Log.e(TAG, "获取存储的下载ID ====downloadId==" + downloadId);
if (downloadId != -1L) {
FileDownloadManager fdm = FileDownloadManager.getInstance(App.getContext());
int status = fdm.getDownloadStatus(downloadId);
Log.e(TAG, "===downloadApk: =由id获取状态=status=="+status);
if (status == DownloadManager.STATUS_SUCCESSFUL) {
final Uri uri = fdm.getDownloadUri(downloadId);
if (uri != null) {
//比较本地已存在的安装包版本和后台apk版本及已安装版本和本地包的版本,以确认是否需要重新下载
if (compare(getApkInfo(UpdateActivity.this, uri.getPath()), UpdateActivity.this)) {
new AlertFragmentDialog.Builder(UpdateActivity.this)
.setCancel(true).setContent("检测到您已下载最新安装包,点击确定安装!")
.setTitle("安装apk")
.setLeftBtnText("取消")
.setRightBtnText("确定")
.setRightCallBack(new AlertFragmentDialog.RightClickCallBack() {
@Override
public void dialogRightBtnClick() {
startInstall(UpdateActivity.this, uri);
}
}).build();
} else {
//移除本地过时apk,重新下载
fdm.remove(downloadId);
start(UpdateActivity.this, apkUrl, "title", App.getAPPName());
}
} else {//文件路径不存在,启动下载
start(UpdateActivity.this, apkUrl, "title", App.getAPPName());
}
} else if (status == DownloadManager.STATUS_FAILED) {//下载失败,重新下载
start(UpdateActivity.this, apkUrl, "title", App.getAPPName());
} else {//其他状态
Log.d(TAG, "apk is already downloading");
}
} else {
start(UpdateActivity.this, apkUrl, "title", App.getAPPName());
}
}
/**
* 获取apk程序信息[packageName,versionName...]
*
* @param context Context
* @param path apk path
*/
private PackageInfo getApkInfo(Context context, String path) {
PackageManager pm = context.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
if (info != null) {
return info;
}
return null;
}
/**
* 下载的apk和当前程序版本比较
*
* @param apkInfo apk file's packageInfo
* @param context Context
* @return 如果当前应用版本小于apk的版本则返回true
*/
private boolean compare(PackageInfo apkInfo, Context context) {
if (apkInfo == null) {
return false;
}
String localPackage = context.getPackageName();
if (apkInfo.packageName.equals(localPackage)) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0);
Log.e(TAG, "compare: apkInfo.versionCode=" + apkInfo.versionCode + ",packageInfo.versionCode=" + packageInfo.versionCode);
//判断逻辑是已下载的apk版本号大于已安装的app版本号,而且已下载的apk版本号和服务器的版本号一致,才确定已下载的apk是最新的apk
if (apkInfo.versionCode > packageInfo.versionCode && apkInfo.versionCode == 10073) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
return false;
}
private void start(Context context, String url, String title, String appName) {
long id = FileDownloadManager.getInstance(context).startDownload(url,
title, "下载完成后点击打开", appName);
Log.e(TAG, "==apk start download==" + id);
startProgressTimer();
}
private void startInstall(Context context, Uri uri) {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(uri, "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
/**********************定时查询下载进度***************************/
private static final int TIMER_WHAT = 2;
private Timer timer;
private TimerTask timerTask;
private UpdateDialog updateDialog;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case TIMER_WHAT:
if (msg.arg1 < 100) {
updateDialog.setProgress(msg.arg1);
} else {
timer.cancel();
timerTask.cancel();
updateDialog.setProgress(msg.arg1);
updateDialog.dismissAllowingStateLoss();
}
break;
}
}
};
private void startProgressTimer() {
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
int cProgress = FileDownloadManager.getInstance(App.getContext()).getProgress(SpUtil.getLong("downloadId", -1L));
Message message = mHandler.obtainMessage(TIMER_WHAT);
message.arg1 = cProgress;
mHandler.sendMessage(message);
}
};
//200毫秒查询一次进度
timer.schedule(timerTask, 0, 200);
updateDialog = new UpdateDialog.Builder(UpdateActivity.this).setCancelDownloadListener(new UpdateDialog.CancelDownloadListener() {
@Override
public void onCancelDownloadListener() {
ToastUtil.showToast("取消!");
long dId = SpUtil.getLong("downloadId", -1L);
FileDownloadManager.getInstance(App.getContext()).remove(dId);
timer.cancel();
timerTask.cancel();
}
}).build();
}
三,自动安装
DM中有三个Action常量分别是:
//两个广播 Action
/**
* 下载完成时发出的广播的Action,注意这里的下载完成包括下载成功和取消下载
*/
public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
/**
* 这个Action 是用户点击了通知栏里正在下载的任务是发出的广播所持有的Action
*/
public final static String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
//一个Activity Action
/**
* 显示所有下载任务Activity的Action
*/
public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
这样我们就可以通过注册 ACTION_DOWNLOAD_COMPLETE 广播来监听下载完成(也可以在上面获取进度为100时判断下载完成);广播的实现如下
public class DownLoadBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long completeId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
long downloadId = SpUtil.getLong("downloadId");
//下载完成广播(包括下载成功,取消下载(取消下载intent携带的downloadId会置0这样就可以区分是下载成功还是取消下载))
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
if (downloadId==completeId){
DownloadManager manager =
(DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Intent installIntent=new Intent(Intent.ACTION_VIEW);
Uri downloadFileUri = manager
.getUriForDownloadedFile(completeId);
installIntent.setDataAndType(downloadFileUri,
"application/vnd.android.package-archive");
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(installIntent);
}
}else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)){//点击正在进行的下载启动系统下载Activity
Intent intentDllActivity = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
intentDllActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intentDllActivity);
}
}
}
记得注册广播
<receiver android:name=".receiver.DownLoadBroadcastReceiver">
<intent-filter >
<!--下载完成,取消下载-->
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
<!--点击正在下载的通知栏-->
<action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>
</intent-filter>
</receiver>
致谢:
参考博文https://blog.csdn.net/u013278099/article/details/52692008#t2