Android 使用DownloadManager进行版本更新的完整方案
在Android App都会有版本更新的功能,以前我们公司是用友盟SDK更新功能,自己服务器没有这样的功能。版本检测、Apk下载都是使用友盟。最近看到友盟的版本更新SDK文档:十月份更新功能将会停止服务器,建议开发者迁移到自己的服务器中。
本文章的主要逻辑:
第一次下载成功,弹出安装界面;
如果用户没有点击安装,而是按了返回键,在某个时候,又再次使用了我们的APP
如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。
下载功能,Google官方推荐使用
DownloadManager服务。
1. 如何使用DownloadManager
FileDownloadManager.java
public long startDownload(String uri, String title, String description) {
DownloadManager.Request req = new DownloadManager.Request(Uri.parse(uri));
req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
//req.setAllowedOverRoaming(false);
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//设置文件的保存的位置[三种方式]
//第一种
//file:///storage/emulated/0/Android/data/your-package/files/Download/update.apk
req.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, "update.apk");
//第二种
//file:///storage/emulated/0/Download/update.apk
//req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk");
//第三种 自定义文件路径
//req.setDestinationUri()
// 设置一些基本显示信息
req.setTitle(title);
req.setDescription(description);
req.setMimeType("application/vnd.android.package-archive");
//加入下载队列
return dm.enqueue(req);
//long downloadId = dm.enqueue(req);
//Log.d("DownloadManager", downloadId + "");
//dm.openDownloadedFile()
}
Android自带的DownloadManager模块来下载, 我们通过通知栏知道, 该模块属于系统自带, 它已经帮我们处理了下载失败、重新下载等功能。
2. 如何检测下载完成,然后启动安装界面
DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE 新建一个广播接收者即可:
public class ApkInstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
// @TODO SOMETHING
}
}
}
3. 完善App更新逻辑
1> 第一次下载成功,弹出安装界面
这个逻辑在ApkInstallReceiver里做即可:
public class ApkInstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
installApk(context, downloadApkId);
}
}
private static void installApk(Context context, long downloadApkId) {
DownloadManager dManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Intent install = new Intent(Intent.ACTION_VIEW);
Uri downloadFileUri = dManager.getUriForDownloadedFile(downloadApkId);
if (downloadFileUri != null) {
Log.d("DownloadManager", downloadFileUri.toString());
install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
} else {
Log.e("DownloadManager", "download error");
}
}
}
2> 如果用户没有点击安装,而是按了返回键
这个时候用户可能没有安装,而是退出了安装界面,用户退出了我们的APP,在某个时候,又再次使用了我们的APP,这个时候我们不应该去下载新版本,而是使用已经下载已经存在本地的APK。
第一次下载的 downloadManager.enqueue(req)会返回一个downloadId,把downloadId保存到本地,用户下次进来的时候,取出保存的downloadId,然后通过downloadId来获取下载的状态信息。
ApkUpdateUtils.java
public static void download(Context context, String url, String title) {
long downloadId = SpUtils.getInstance(context).getLong(KEY_DOWNLOAD_ID, -1L);
if (downloadId != -1L) {
FileDownloadManager fdm = FileDownloadManager.getInstance(context);
int status = fdm.getDownloadStatus(downloadId);
if (status == DownloadManager.STATUS_SUCCESSFUL) {
//启动更新界面
Uri uri = fdm.getDownloadUri(downloadId);
if (uri != null) {
if (compare(getApkInfo(context, uri.getPath()), context)) {
startInstall(context, uri);
return;
} else {
fdm.getDm().remove(downloadId);
}
}
start(context, url, title);
} else if (status == DownloadManager.STATUS_FAILED) {
start(context, url, title);
} else {
if (Config.DEV_MODE) {
Log.d(TAG, "apk is already downloading");
}
}
} else {
start(context, url, title);
}
}
获取APK包名、版本号
/**
* 获取apk程序信息[packageName,versionName...]
*
* @param context Context
* @param path apk path
*/
private static PackageInfo getApkInfo(Context context, String path) {
if (Config.DEV_MODE) {
Log.d(TAG, "apk download path: " + path);
}
PackageManager pm = context.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
if (info != null) {
//String packageName = info.packageName;
//String version = info.versionName;
//Log.d(TAG, "packageName:" + packageName + ";version:" + version);
//String appName = pm.getApplicationLabel(appInfo).toString();
//Drawable icon = pm.getApplicationIcon(appInfo);//得到图标信息
return info;
}
return null;
}
获取下载完成的APK地址
/**
* 获取保存文件的地址
*
* @param downloadId an ID for the download, unique across the system.
* This ID is used to make future calls related to this download.
* @see FileDownloadManager#getDownloadPath(long)
*/
public Uri getDownloadUri(long downloadId) {
return dm.getUriForDownloadedFile(downloadId);
}
public DownloadManager getDm() {
return dm;
}
获取下载状态信息
public int getDownloadStatus(long downloadId) {
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null) {
try {
if (c.moveToFirst()) {
return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
}
} finally {
c.close();
}
}
return -1;
}
如果下载失败,则重新下载并且把downloadId存起来。
private static void start(Context context, String url, String title) {
long id = FileDownloadManager.getInstance(context).startDownload(url,
title, "下载完成后点击打开");
SpUtils.getInstance(context).putLong(KEY_DOWNLOAD_ID, id);
if (Config.DEV_MODE) {
Log.d(TAG, "apk start download " + id);
}
}
如果下载成功,则
判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。
/**
* 下载的apk和当前程序版本比较
*
* @param apkInfo apk file's packageInfo
* @param context Context
* @return 如果当前应用版本小于apk的版本则返回true
*/
private static 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);
if (apkInfo.versionCode > packageInfo.versionCode) {
return true;
} else {
if (Config.DEV_MODE) {
Log.d(TAG, "apk's versionCode <= app's versionCode");
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
if (Config.DEV_MODE) {
Log.d(TAG, "apk's package not match app's package");
}
return false;
}
启动安装界面
public static 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);
}
为了严谨起见,在ApkInstallReceiver里不仅要对downloadId判断,还应当把当前程序和本地apk包名和版本号对比。
如果用户禁用了下载服务[下载管理程序]
可以通过如下代码进入 启用/禁用 下载管理程序 界面:
String packageName = "com.android.providers.downloads";
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
我们先停止下载管理程序,然后点击demo里的Download 按钮报出如下错误:
Caused by: java.lang.IllegalArgumentException: Unknown URL content://downloads/my_downloads
at android.content.ContentResolver.insert(ContentResolver.java:1227)
at android.app.DownloadManager.enqueue(DownloadManager.java:946)
at com.chiclam.download.FileDownloadManager.startDownload(FileDownloadManager.java:61)
at com.chiclam.download.ApkUpdateUtils.start(ApkUpdateUtils.java:47)
at com.chiclam.download.ApkUpdateUtils.download(ApkUpdateUtils.java:42)
at com.chiclam.download.MainActivity.download(MainActivity.java:34)
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:5204)
at android.view.View$PerformClick.run(View.java:21153)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
也就是说如果停止了下载管理程序 调用dm.enqueue(req);就会上面的错误,从而程序闪退.
所以在使用该组件的时候,需要判断该组件是否可用:
private boolean canDownloadState() {
try {
int state = this.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;
}
具体详情, 查看代码.
Android 使用DownloadManager进行版本更新的完整方案的更多相关文章
- Android:使用 DownloadManager 进行版本更新,出现 No Activity found to handle Intent 及解决办法
项目中,进行版本更新的时候,用的是自己写的下载方案,最近看到了使用系统服务 DownloadManager 进行版本更新,自己也试试. 在下载完成以后,安装更新的时候,出现了一个 crash,抓取的 ...
- Android:使用 DownloadManager 进行版本更新
app 以前的版本更新使用的自己写的代码从服务器下载,结果出现了下载完成以后,提示解析包错误的问题,但是呢,找到该 apk 点击安装是可以安装成功的,估计就是最后几秒安装包没有下载完成然后点击了安装出 ...
- Android 高清加载巨图方案 拒绝压缩图片
Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/49300989: 本文出自:[张 ...
- Android 基于Netty的消息推送方案之对象的传递(四)
在上一篇文章中<Android 基于Netty的消息推送方案之字符串的接收和发送(三)>我们介绍了Netty的字符串传递,我们知道了Netty的消息传递都是基于流,通过ChannelBuf ...
- Android 基于Netty的消息推送方案之字符串的接收和发送(三)
在上一篇文章中<Android 基于Netty的消息推送方案之概念和工作原理(二)> ,我们介绍过一些关于Netty的概念和工作原理的内容,今天我们先来介绍一个叫做ChannelBuffe ...
- Android 基于Netty的消息推送方案之概念和工作原理(二)
上一篇文章中我讲述了关于消息推送的方案以及一个基于Netty实现的一个简单的Hello World,为了更好的理解Hello World中的代码,今天我来讲解一下关于Netty中一些概念和工作原理的内 ...
- Android架构设计和软硬整合完整训练
Android架构设计和软硬整合完整训练 Android架构设计和软硬整合完整训练:HAL&Framework&Native Service&Android Service&a ...
- Android手游《》斗地主完整的源代码(支持单机和网络对战)
Android手游<斗地主>完整的源代码(支持单机和网络对战)下载.一个很不错的源代码. 斗地主掌游是一个独特的国内社会斗地主棋牌游戏,之后玩家可以下载网上斗地主和全世界.掌游斗地主特点: ...
- android SDK与ADT版本更新问题
android SDK与ADT版本更新问题 问题:This Android SDK requires Android Developer Toolkit version 14.0.0 or above ...
随机推荐
- 做了两年多salesforce平台开发,转Java的经历
2015年毕业,转眼已经三年多了.三年对于现在的我,真的很快,一开始对软件开发的执着一直没有变.我是一个很普通很普通长沙的一个专科毕业.刚进大学,对于软件开发真的是小白,仅仅只是存在对于游戏,和桌面软 ...
- shell实现脚本监控服务器及web应用
实际工作中我们需要知道部署在服务器上的应用有没有问题,但是人为的操作太麻烦有咩有简单的方式呢shell来监控我们服务器运行状态以及服务器上部署的应用,如果出现异常就会自动发送一个邮件给我们,开始搞起. ...
- 6.QT-简易计算器实现(详解)
界面展示 1.用户界面类设计 需要使用QWidget组件作为顶层窗口,QLineEdit组件作为输入框,QPsuhButton作为按钮 1.1 在代码里处理按键消息时,需要处理下用户输入的格式(方便逻 ...
- 网络安全实验室_上传关writeup
请上传一张jpg格式的图片 先传个图片码试试 我肯定乖嘛(#`Д´)ノ 气到改后缀 请上传一张jpg格式的图片 我猜是00截断,不信来试试 先在赋值1.php .jpg,接着去hex中找到空格改成00 ...
- codevs 2964 公共素数因数
提交地址:http://codevs.cn/problem/2964/ 2964 公共素数因数 时间限制: 1 s 空间限制: 32000 KB 题目等级 : 白银 Silver 题解 ...
- [NOI 2015]软件包管理器
Description Linux用户和OSX用户一定对软件包管理器不会陌生. 通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖 ...
- [NOI2016]循环之美
Description 牛牛是一个热爱算法设计的高中生.在他设计的算法中,常常会使用带小数的数进行计算.牛牛认为,如果在 k 进制下,一个数的小数部分是纯循环的,那么它就是美的.现在,牛牛想知道:对 ...
- ●BZOJ 2560 串珠子
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2560 题解: 容斥,状压计数dp 首先求出一个数组 g[s] 表示集合内的点的连边方案数(两 ...
- ORACLE 触发器 基础
--触发器--语法 CREATE OR REPLACE TRIGGER TRIGGER_NAME AFTER|BEFORE|INSTEAD OF [INSERT][OR UPDATE [OF COLU ...
- Python【第三课】 函数基础
本篇内容 函数基本语法及特性 嵌套函数 递归函数 匿名函数 高阶函数 内置函数 1.函数的基本语法及特性 1.1 函数概念 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提 ...