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 ...
随机推荐
- [LeetCode] Closest Leaf in a Binary Tree 二叉树中最近的叶结点
Given a binary tree where every node has a unique value, and a target key k, find the value of the n ...
- python AES加密解密 pycryptodome
环境 pyhton3.6 博主为了解码 AES 用了1天的时间,安了各种包,然而走了很多坑,在这里给大家提供一个简便的方法 首先在命令行(推荐) pip install Crypto 你会发现安装下 ...
- 实验吧_密码忘记了(vim编辑器+代码审计)&天网管理系统(php弱比较+反序列化)
密码忘记了 一开始尝试了各种注入发现都无效,在网页源码中找到了admin 的地址,输入地址栏发现并没有什么有用的信息,随便输个邮箱,网页返回了一个地址 ./step2.php?email=youmai ...
- 2015 多校联赛 ——HDU5414()
Problem Description CRB has two strings s and t. In each step, CRB can select arbitrary character c ...
- ●HDU 6021 MG loves string
题链: http://acm.hdu.edu.cn/showproblem.php?pid=6021 题解: 题意:对于一个长度为 N的由小写英文字母构成的随机字符串,当它进行一次变换,所有字符 i ...
- hdu 5441 (并查集)
题意:给你n个点,m条边构成无向图.q个询问,每次一个值,求有多少条路,路中的边权都小于这个值 a->b 和 b->a算两种 思路:把权值从小到大排序,询问从小到大排序,如果相连则用并查集 ...
- SpringCloud学习之soa基础
一.soa简单介绍 1)面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.SOA是解决复杂业务模块,提高扩展性,维护性,可伸缩 ...
- python dataframe数据条件筛选
一般情况下我们从一堆数据中选择我们获取想要的数据会通过一下方式: (1)创建链表或数组: (2)用for 循环遍历所有数据,将想要的存入链表或数组. 但是python中我们不需要这么做,我们可以用Pa ...
- break用法
break用于循环,则是终止循环 break用于switch,则终止switch break不能用于if.当break用于if语句,但是终止的是if的外部循环 break 只能终止最近的循环
- BI之SSIS入门最新版Visual Studio调试技巧
简介 最近公司业务需要用到BI SSIS,SSIS是什么?"SSIS是Microsoft SQL Server Integration Services的简称,是生成高性能数据集成解决方案( ...