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 ...
随机推荐
- SocketServer源码学习(二)
SocketServer 中非常重要的两个基类就是:BaseServer 和 BaseRequestHandler在SocketServer 中也提供了对TCP以及UDP的高级封装,这次我们主要通过分 ...
- ognl版本错误
错误信息: 2014-2-6 21:20:10 org.apache.catalina.core.StandardWrapperValve invoke严重: Servlet.service() fo ...
- [Codeforces 863B]Kayaking
Description Vadim is really keen on travelling. Recently he heard about kayaking activity near his t ...
- [Luogu 1730]最小密度路径
Description 给出一张有N个点M条边的加权有向无环图,接下来有Q个询问,每个询问包括2个节点X和Y,要求算出从X到Y的一条路径,使得密度最小(密度的定义为,路径上边的权值和除以边的数量). ...
- LOJ #6041. 事情的相似度
Description 人的一生不仅要靠自我奋斗,还要考虑到历史的行程. 历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势. 你发现在历史的不同时刻,不断的 ...
- ●BZOJ 2005 NOI 2010 能量采集
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2005 题解: 一个带有容斥思想的递推.%%% 首先,对于一个点 (x,y) 在路径 (0,0 ...
- [BZOJ]1017 魔兽地图DotR(JSOI2008)
BZOJ第一页做着做着就能碰到毒题,做到BZOJ1082小C就忍了,没想到下一题就是这种东西.这种题目不拖出来枭首示众怎么对得起小C流逝的青春啊. Description DotR (Defense ...
- bzoj省选十连测推广赛
A.普通计算姬 题意:给丁一棵树,每个点有一个权值,用sum(x)表示以x为根的子树的权值和,要求支持两种操作: 1 u v :修改点u的权值为v. 2 l r : 求∑sum[i] l&l ...
- java 之 MyBatis(sql 可以执行,在eclipse执行报错问题)
前段时间写 mybatis Sql 查询语句的时候,发现一个很奇怪的现象,我写的SQL 语句在 pl/Sql 中明明可以执行,但是放到 eclipse 中执行却报错,因为时间比较久,依稀记得是文字与字 ...
- SparkSQL——用之惜之
SparkSql作为Spark的结构化数据处理模块,提供了非常强大的API,让分析人员用一次,就会为之倾倒,为之着迷,为之至死不渝.在内部,SparkSQL使用额外结构信息来执行额外的优化.在外部,可 ...