我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载。 一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和本地的版本号比较,来判断是否需要弹出提示框下载,当然也可以通过推送的自定义消息来实现。 我们这里主要讨论的是应用程序下载,并在通知栏提醒下载完成。实现过程大致分为三步:
创建一个service 在service启动的时候创建一个广播接受者,用于接受下载完成的广播 当BroadcastReceiver接受到下载完成的广播时,开始执行安装。
主要通过系统提供的DownloadManager进行下载,DownloadManager下载完成会发送广播,具体使用看下面完整的代码。如果详细了解可以参考Android系统下载管理DownloadManager功能介绍及使用示例点击打开链接下面创建新的文件DownloadService.Java
[java] view plain copy public class DownLoadService extends Service { /**广播接受者*/ private BroadcastReceiver receiver; /**系统下载管理器*/ private DownloadManager dm; /**系统下载器分配的唯一下载任务id,可以通过这个id查询或者处理下载任务*/ private long enqueue; /**TODO下载地址 需要自己修改,这里随便找了一个*/ private String downloadUrl="http://dakaapp.troila.com/download/daka.apk?v=3.0"; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { install(context); //销毁当前的Service stopSelf(); } }; registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); //下载需要写SD卡权限, targetSdkVersion>=23 需要动态申请权限 RxPermissions.getInstance(this) // 申请权限 .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .subscribe(new Action1<Boolean>() { @Override public void call(Boolean granted) { if(granted){ //请求成功 startDownload(downloadUrl); }else{ // 请求失败回收当前服务 stopSelf(); } } }); return Service.START_STICKY; } /** * 通过隐式意图调用系统安装程序安装APK */ public static void install(Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); // 由于没有在Activity环境下启动Activity,设置下面的标签 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.fromFile( new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "myApp.apk")), "application/vnd.android.package-archive"); context.startActivity(intent); } @Override public void onDestroy() { //服务销毁的时候 反注册广播 unregisterReceiver(receiver); super.onDestroy(); } private void startDownload(String downUrl) { //获得系统下载器 dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); //设置下载地址 DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downUrl)); //设置下载文件的类型 request.setMimeType("application/vnd.android.package-archive"); //设置下载存放的文件夹和文件名字 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "myApp.apk"); //设置下载时或者下载完成时,通知栏是否显示 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); request.setTitle("下载新版本"); //执行下载,并返回任务唯一id enqueue = dm.enqueue(request); } }上面代码使用了RxPermissions第三方库动态申请权限,需要在app/build.gradle文件中进行配置
[java] view plain copy dependencies { //... compile 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar' compile 'io.reactivex:rxjava:1.1.6' //需要引入RxJava }记得要配置服务
[java] view plain copy <application ...> ... <service android:name=".DownLoadService"/> </application>最后在MainActivity中添加按钮,执行操作。
当下载的时候,会有通知栏进度条提示。下载完成会提示安装。不过当前程序如果在Android7.0上就会报错。下面是报错的日志:
[java] view plain copy Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/Download/myApp.apk exposed beyond app through Intent.getData()这是由于Android7.0执行了“StrictMode API 政策禁”的原因,不过小伙伴们不用担心,可以用FileProvider来解决这一问题, 现在我们就来一步一步的解决这个问题。 android 7.0错误原因 随着Android版本越来越高,Android对隐私的保护力度也越来越大。 比如:Android6.0引入的动态权限控制(Runtime Permissions),Android7.0又引入“私有目录被限制访问”,“StrictMode API 政策”。 这些更改在为用户带来更加安全的操作系统的同时也为开发者带来了一些新的任务。如何让你的APP能够适应这些改变而不是crash,是摆在每一位Android开发者身上的责任。 “私有目录被限制访问“ 是指在Android7.0中为了提高私有文件的安全性,面向 Android N 或更高版本的应用私有目录将被限制访问。这点类似iOS的沙盒机制。 " StrictMode API 政策" 是指禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常。 上面用到的代码中的Uri.fromFile 其实就是生成一个file://URL。
[java] view plain copy //... intent.setDataAndType(Uri.fromFile( new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS), "myApp.apk")), "application/vnd.android.package-archive"); //....
一旦我们通过这种办法打开其它程序(这里打开系统包安装器)就认为file:// URI类型的 Intent 离开你的应用。这样程序就会发生异常。 接下来就用FileProvider来解决这一问题。
使用FileProvider 使用FileProvider的大致步骤如下: 第一步:在AndroidManifest.xml清单文件中注册provider,因为provider也是Android四大组件之一,可以简单把它理解为向外提供数据的组件,这种组件在实际开发中用的频率并不高,四大组件都可以在清单文件中进行配置。
[java] view plain copy <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.yll520wcf.test.fileprovider" android:grantUriPermissions="true" android:exported="false"> <!--元数据--> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
第二步:指定共享的目录上面配置文件中 android:resource="@xml/file_paths" 指的是当前组件引用 res/xml/file_paths.xml 这个文件。 我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,
<files-path/>代表的根目录: Context.getFilesDir() <external-path/>代表的根目录: Environment.getExternalStorageDirectory() <cache-path/>代表的根目录: getCacheDir()
上述代码中path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。 如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。 第三步:使用FileProvider上述准备工作做完之后,现在我们就可以使用FileProvider了。我们需要将上述安装APK代码修改为如下
[java] view plain copy public static void install(Context context) { File file= new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) , "myApp.apk"); //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件 Uri apkUri = FileProvider.getUriForFile(context, "com.com.yll520wcf.test.fileprovider", file); Intent intent = new Intent(Intent.ACTION_VIEW); // 由于没有在Activity环境下启动Activity,设置下面的标签 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //添加这一句表示对目标应用临时授权该Uri所代表的文件 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); context.startActivity(intent); }
上述代码中主要有两处改变:
将之前Uri改成了有FileProvider创建一个content类型的Uri。 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。
上述代码通过FileProvider的Uri getUriForFile (Context context, String authority, File file)静态方法来获取Uri该方法中authority参数就是清单文件中注册provider时填写的authority android:authorities="com.yll520wcf.test.fileprovider" 按照上面步骤修改就可以兼容Android7.0了。 后期修改,之前没有考虑7.0以下的版本 但是如果此程序在Android7.0以下运行又会报错了,我们需要通过版本判断,当Android7.0及以上需要调用上面的代码,Android7.0以下需要调用7.0以下的代码。这样就OK了。修改install() 方法代码。
[java] view plain copy /** * 通过隐式意图调用系统安装程序安装APK */ public static void install(Context context) { File file = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) , "myApp.apk"); Intent intent = new Intent(Intent.ACTION_VIEW); // 由于没有在Activity环境下启动Activity,设置下面的标签 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if(Build.VERSION.SDK_INT>=24) { //判读版本是否在7.0以上 //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件 Uri apkUri = FileProvider.getUriForFile(context, "com.a520wcf.chapter11.fileprovider", file); //添加这一句表示对目标应用临时授权该Uri所代表的文件 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); }else{ intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); } context.startActivity(intent); }顶 5 踩 1 上一篇android 6.0运行时权限下一篇Android实现点击AlertDialog上按钮时不关闭对话框的方法 相关文章推荐 • Android7.0之FileProvider • FileProvider的使用 • 使用FileProvider共享文件 • Android 7.0 安装失败的两个原因 • Content Provider(二)之 FileProvider 实现应用文件共享 • FileProvider使用 • 根据 Android Training课程写的FileProvider小例子 • FileProvider • Android 应用间共享文件(FileProvider) • FileProvider使用 猜你在找 【直播】机器学习&数据挖掘7周实训--韦玮 【套餐】系统集成项目管理工程师顺利通关--徐朋 【直播】3小时掌握Docker最佳实战-徐西宁 【套餐】机器学习系列套餐(算法+实战)--唐宇迪 【直播】计算机视觉原理及实战--屈教授 【套餐】微信订阅号+服务号Java版 v2.0--翟东平 【直播】机器学习之矩阵--黄博士 【套餐】微信订阅号+服务号Java版 v2.0--翟东平 【直播】机器学习之凸优化--马博士 【套餐】Javascript 设计模式实战--曾亮 查看评论 1楼 车凤龙 2017-03-30 13:59发表 [回复] 学习了 * 以上用户言论只代表其个人观点,不代表网站的观点或立场 个人资料 小队长1号 访问:21320次积分:578 等级: 排名:千里之外 原创:33篇转载:4篇译文:0篇评论:6条 文章搜索 文章分类 设计模式(4) 文章存档 2016年12月(3)2016年11月(5)2016年10月(5)2016年09月(10)2016年06月(2) 展开 阅读排行 Android 7.0 FileProvider的使用(6265)RXBUS的简单使用(3095)Android如何关闭硬件加速(2334)寻找android中的设计模式(一)(1282)Activity过度动画应用(828)基于N源码的AccountManagerService简单认识和账户添加流程分析(663)基于N源码的Activity的启动过程分析(594)AIDL简单使用(584)基于N源码的ContentProvider调用流程分析(524)Android电话本数据查询总结(307) 评论排行 Android电话本数据查询总结(3)寻找android中的设计模式(一)(1)基于N源码的Activity的启动过程分析(1)Android 7.0 FileProvider的使用(1)android中的动画(0)android数据库更新简介(0)重要的View(0)Broadcast和ContentProvider(0)Service和AIDL(0)寻找android中的设计模式(三)(0) 推荐文章 * 日报20170725——《新的开始,从研究生到入职亚马逊》* 深入剖析基于并发AQS的重入锁(ReetrantLock)及其Condition实现原理* Android版本的"Wannacry"文件加密病毒样本分析(附带锁机)* 工作与生活真的可以平衡吗?* 《Real-Time Rendering 3rd》 提炼总结——高级着色:BRDF及相关技术* 《三体》读后思考-泰勒展开/维度打击/黑暗森林 最新评论 Android 7.0 FileProvider的使用
车凤龙:学习了
Android电话本数据查询总结zhouyl372:03-22 11:26:07.166 4442 4442 D zyl : at jav...
Android电话本数据查询总结zhouyl372:03-22 11:26:07.166 4442 4442 D zyl : at and...
Android电话本数据查询总结zhouyl372:打堆栈可以看到ContactsProvider2是由ActivityThread启动,Activit...
基于N源码的Activity的启动过程分析bwv1052:写的不错
寻找android中的设计模式(一)yymmbb:下面的图挂了
公司简介 | 招贤纳士 | 广告服务 | 联系方式 | 版权声明 | 法律顾问 | 问题报告 | 合作伙伴 | 论坛反馈 网站客服 杂志客服 微博客服 webmaster@csdn.net 400-660-0108 | 北京创新乐知信息技术有限公司 版权所有 | 江苏知之为计算机有限公司 | 江苏乐知网络技术有限公司 京 ICP 证 09002463 号 | Copyright © 1999-2017, .NET, All Rights Reserved