Android 使用DownloadManager进行版本更新的完整方案
2016-05-21 21:58
405 查看
在Android App都会有版本更新的功能,以前我们公司是用友盟SDK更新功能,自己服务器没有这样的功能。版本检测、Apk下载都是使用友盟。最近看到友盟的版本更新SDK文档:十月份更新功能将会停止服务器,建议开发者迁移到自己的服务器中。
本文章的主要逻辑:
下载功能,Google官方推荐使用
Android自带的DownloadManager模块来下载, 我们通过通知栏知道, 该模块属于系统自带, 它已经帮我们处理了下载失败、重新下载等功能。
第一次下载的 downloadManager.enqueue(req)会返回一个downloadId,把downloadId保存到本地,用户下次进来的时候,取出保存的downloadId,然后通过downloadId来获取下载的状态信息。
如果下载失败,则重新下载并且把downloadId存起来。
如果下载成功,则
为了严谨起见,在ApkInstallReceiver里不仅要对downloadId判断,还应当把当前程序和本地apk包名和版本号对比。
完整源码下载:https://github.com/chiclaim/android-app-update
本文章的主要逻辑:
第一次下载成功,弹出安装界面; 如果用户没有点击安装,而是按了返回键,在某个时候,又再次使用了我们的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包名和版本号对比。
完整源码下载:https://github.com/chiclaim/android-app-update
相关文章推荐
- Android随笔小记 Permission
- [置顶] Android 属性动画详解
- Error:Cause: failed to find target with hash string 'android-15'
- Android中Toast的用法简介
- Android拼图游戏
- GitHub上最火的Android开源项目(一)
- android 中vector的用法 ,坑 ,怎么替代,关于这几方面的一些看法
- android开发笔记之ListView的优化+分类显示
- 安卓Android本地Sqlite实现注册账号和登录功能
- GitHub上最火的Android开源项目整理
- 有关Android View 绘制流程 & 自定义View
- Ubuntu make 降级方法
- 《Android5从入门到精通》
- Android开发笔记(一)
- android点击菜单子项无响应的解决办法listView
- Android自定义控件之滑动解锁
- android drawBitmapMesh()图片旗帜飘扬效果
- service服务的生命周期
- android 代码设置、打开wifi热点及热点的连接
- java/android 设计模式学习笔记(3)---工厂方法模式