下载安装APK(兼容Android7.0)
我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载。
一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和本地的版本号比较,来判断是否需要弹出提示框下载,当然也可以通过推送的自定义消息来实现。
我们这里主要讨论的是应用程序下载,并在通知栏提醒下载完成。
实现过程大致分为三步:
- 创建一个service
- 在service启动的时候创建一个广播接受者,用于接受下载完成的广播
- 当BroadcastReceiver接受到下载完成的广播时,开始执行安装。
主要通过系统提供的DownloadManager
进行下载,DownloadManager
下载完成会发送广播,具体使用看下面完整的代码。如果详细了解可以参考Android系统下载管理DownloadManager功能介绍及使用示例下面创建新的文件DownloadService.java
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文件中进行配置
dependencies {
//...
compile 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar'
compile 'io.reactivex:rxjava:1.1.6' //需要引入RxJava
}
记得要配置服务
<application
...>
...
<service android:name=".DownLoadService"/>
</application>
最后在MainActivity中添加按钮,执行操作。运行结果:
当下载的时候,会有通知栏进度条提示。下载完成会提示安装。不过当前程序如果在Android7.0上就会报错。下面是报错的日志:
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。
//...
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四大组件之一,可以简单把它理解为向外提供数据的组件,这种组件在实际开发中用的频率并不高,四大组件都可以在清单文件中进行配置。
<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>
注意:
exported
:要求必须为false,为true则会报安全异常。grantUriPermissions:true
,表示授予 URI 临时访问权
限。authorities
组件标识,按照江湖规矩,都以包名开头,避免和其它应用发生冲突。
第二步:指定共享的目录
上面配置文件中 android:resource="@xml/file_paths"
指的是当前组件引用 res/xml/file_paths.xml
这个文件。
我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下:
- 代表的根目录: Context.getFilesDir()
- 代表的根目录: Environment.getExternalStorageDirectory()
- 代表的根目录: getCacheDir()
上述代码中path=”“,是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。
如果你将path设为path="pictures"
,那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
第三步:使用FileProvider
上述准备工作做完之后,现在我们就可以使用FileProvider了。
我们需要将上述安装APK代码修改为如下
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);
}
上述代码中主要有两处改变:
1. 将之前Uri改成了有FileProvider创建一个content类型的Uri。
2. 添加了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() 方法代码。
/**
* 通过隐式意图调用系统安装程序安装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);
}
参考文献
android应用开发app手动更新通知栏下载实践
Android7.0适配教程,心得
更多精彩请关注微信公众账号likeDev
下载安装APK(兼容Android7.0)的更多相关文章
- Android 升级安装APK兼容Android7.0,解决FileUriExposedException
我们在开发app时避免不了需要添加应用内升级功能.当app启动时,如果检测到最新版本,将apk安装包从服务器下载下来,执行安装.安装apk的代码一般写法如下,网上随处可以搜到 public stati ...
- 升级安装APK兼容Android7.0,解决FileUriExposedException
见http://blog.csdn.net/ruancoder/article/details/67639621?utm_source=itdadao&utm_medium=referral
- Windows 7下载安装 Visual C++ 6.0(VC6) 全程图解
说实话我也一直没有试过,所以也想当然的认为Win7下就不能安装VC6,压根就100%不兼容?一直使用高版本的VS(如VS2008和现在用的VS2010)的我今天亲身在Win7下安装一次试试. 注:文中 ...
- 下载安装APK
protected void downloadApk() { //apk下载链接地址,放置apk的所在路径 //1,判断sd卡是否可用,是否挂在上 if(Environment.getExternal ...
- 基于nginx实现二维码下载安装apk文件
将apk文件置于nginx目录下 <!--进入nginx安装路径--> /usr/local/nginx <!--新建放apk的目录--> mkdir -p resources ...
- Win10环境下载安装MySQL Community 8.0.12
1.下载MySQL Community 8.0.12的免安装版,下载地址:https://dev.mysql.com/downloads/mysql/ 2.解压到D:\Program Files\My ...
- mysql 安装流程 兼容8.0.0以上版本 解决修改密码规则问题
背景介绍: 第一次安装mysql服务端,版本8.0.6 遇到了问题:1:不知道流程:2:8.0以上版本密码加密规则修改的解决方案: 1:下载mysql 服务端 https://dev.mysql. ...
- android 开发 程序中下载安装APK文件 问题汇总 解析程序包时出现问题
1 若把APK文件保存到应用程序的files目录下,则一定注意保存时使用 FileOutputStream os = openFileOutput(fileName, MODE_WORLD_READA ...
- 扫描二维码下载安装apk的app
将apk文件放到服务器上,下载链接直接生成二维码,用微信扫描时不能直接下载.页面只是刷新一下. 想实现微信扫描下载apk的app客户端,需要把下载链接做到一个网页上, 将网页生成一个二维码. 直接下载 ...
随机推荐
- Odoo QWeb
1.web 模块 注意,OpenERP 模块中 web 部分用到的所有文件必须被放置在模块内的 static 文件夹里.这是强制性的,出于安全考虑. 事实上,我们创建的文件夹 CSS,JS 和 XML ...
- idea打包可执行jar
(1)在项目上鼠标右键 --> Open Module Settings 或者点击工具栏上的 (2)Artifacts --> + --> JAR --> From modul ...
- JavaSE_04_JDK1.8新特性Lambda表达式
1.1体验Lambda的更优写法 借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效: 1.2 Lambda标准格式 Lambda省去面向对 ...
- 苹果系统 IOS 12 的H5 BUG :键盘把页面顶上去了,底下留有一块空白区域
苹果以往的系统是没问题的,一般情况下,点击input唤起键盘后是会自动显示到输入框的地方,然后收起键盘页面就会恢复到底部. 但是如果苹果是已经更新到最新的IOS12的话就会发生一个BUG ,就是键盘唤 ...
- python 日记 day4。
1.为何数据要分类 数据是用来表示状态的,不同的状态应该用不同类型的数据来表示. 2.数据类型 数字 字符串 列表 元组 字典 集合 列表:列表相比于字符串,不仅可以储存不同的数据类型,而且可以储存大 ...
- git 创建.gitignore忽略不必要的文件
问题: 创建java项目,使用git提交,有时需要忽略不必要的文件或文件夹,只保留一些基本. 例如maven创建好后,实际开发中我们只需提交:src,.gitignore,pom.xml等文件 但是有 ...
- PAT甲级——A1051 Pop Sequence
Given a stack which can keep M numbers at most. Push N numbers in the order of 1, 2, 3, ..., N and p ...
- PAT甲级——A1026 Table Tennis
A table tennis club has N tables available to the public. The tables are numbered from 1 to N. For a ...
- Python3实用编程技巧进阶
Python3实用编程技巧进阶 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的时候可以 ...
- pycharm2018激活
pyCharm最新2018最新激活码 选择 Activate new license with License server (用license server 激活) 在 License sever ...