您的位置:首页 > 移动开发 > Android开发

Android7.0+权限适配

2018-03-31 00:35 211 查看
谷歌在Android7.0(API 24)做了一些权限的更改,对用户私有目录或私有文件的访问和共享做了限制,具体可看官网描述权限更改这里简单解释一下,就是涉及传递file://URI在7.0+上就会抛FileUriExposedException异常。这样说有些刚入门的小伙伴或许还不是一脸懵逼,下面举两个常见的开发场景来解释一下传递file://URI具体什么操作(下面例子不涉及具体实现,只贴出关键代码做解释)。

拍照

当实现拍照需要设置拍照后照片存放目录,一般会涉及如下代码Uri uri = Uri.fromFile(new File("指定你要保存的目录"));
//设置拍照后保存目录
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);上面代码最终会转为file:///你指定的目录/指定的照片名,在7.0以下运行正常,7.0及以上会抛出上面所说的FileUriExposedException

应用更新

应用更新下载完安装包启动系统安装界面涉及下面一句代码intent.setDataAndType(Uri.fromFile(new File("安装包路径")), "application/vnd.android.package-archive");这里跟上面拍照例子也用到了Uri.fromFile();这个api,同样在7.0下运行正常,7.0及以上依旧抛FileUriExposedException异常

小结

凡是涉及Uri.fromUri(),又跟Intent相关的,如在安卓7.0及以上不做适配,就会抛异常。下面具体讲解如何适配

声明Provider

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapplication.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
将以上复制粘贴到清单文件<application>节点下再修改下即可
几点说明:
1.其中下面这一行中的com.example.myapplication必须改为自己项目的包名,包名在Module下build.gradle下android:authorities="com.example.myapplication.fileprovider"


2.倒数第二行涉及一个xml文件声明,这个在下一个点介绍android:resource="@xml/file_paths"

编写xml

Android Studio可以鼠标点一个上面那个@xml/file_paths,然后按快捷键Alt+Enter,就会提示创建文件夹和文件,按回车,再点击OK即可自动在项目res目录下新建一个xml目录和一个名为file_paths的xml文件(不会快捷键的按照目录结构逐个创建即可)





然后打开xml文件<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="" path=""/>
<cache-path name="" path="" />
<external-path name="" path="" />
<external-files-path name="" path="" />
<external-cache-path name="" path="" />
<external-media-path name="" path="" />
</paths>标签说明
<paths>为顶层标签,它下面可添加任意个(0,1或多个)上面出现的标签
<paths>子标签解释
可以看到上面每个标签都含有一个name和path属性
name:这个可以随意写(用于隐藏真实的目录,后面再演示)
path:要共享的目录,只能是目录,不能是某个具体的文件
<files-path> 代表Context.getFilesDir()所指向的目录
<cache-path>代表Context.getCacheDir()所指向的目录
<external-path>代表Environment.getExternalStorageDirectory()所指向的目录
<external-files-path>代表Context.getExternalFilesDir()所指向的目录
<external-cache-path>代表Context.getExternalCacheDir()所指向的目录
<external-media-path>代表Context.getExternalMediaDirs()所指向的目录(API21+设备才能使用,所以一般不用)
以上解释可能有的小伙伴还是一脸懵逼,下面举个具体的下载例子说一下(不涉及代码)
比如我现在把新版本安装包下载在SD卡根目录下的Download文件夹下,那么我的xml如下设计即可
其中path就得指定为Download,name的值可随意写



关于name和path最终去向这里盗用博客里面一张图(如涉及侵权麻烦博主找我撤回)
由下图可知最终我们将原本的file://URI转为了content://URI,这个正是安卓7.0要求的正确传递方式,而我们上面的name最终传递到了URI的后面作为一个隐藏的目录,隐藏了原文件的真实目录



使用

上面说了这么多,都只是热身,单纯配置而已,还没涉及一行代码,接下来就得开始用代码实现我们的功能了,这里也主要针对上面提到的两个例子(其它例子看完可举一反三,下面只展示修改部分,不涉及全部代码)这里得用到一个FileProvider类,它继承自ContentProvider,这也是为什么我们第一步得在清单文件注册provider的原因。

拍照

//创建图片存放file
File imgFile = new File("照片存放目录");
Uri uri;
//根据当前系统版本决定使用哪个api,N是Android7.0的代号
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//第一个参数是上下文,第二个参数来自清单文件,必须完全一样,第三个参数为上面创建的照片file
uri = FileProvider.getUriForFile(this, "com.example.myapplication.fileprovider", imgFile);
} else {
//Android7.0还用原先的api
uri = Uri.fromFile(imgFile);
}
//设置拍照后保存目录
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

应用更新

//创建安装包file
File apkFile = new File("安装包路径");
Uri uri;
//根据当前系统版本决定使用哪个api,N是Android7.0的代号
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//第一个参数是上下文,第二个参数来自清单文件,必须完全一样,第三个参数为上面创建的安装包file
uri = FileProvider.getUriForFile(this, "com.example.myapplication.fileprovider", apkFile);
} else {
//Android7.0还用原先的api
uri = Uri.fromFile(apkFile);
}
//授权Intent读取URI和写URI的权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//设置拍照后保存目录
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
几点说明
(1)不管是拍照还是应用更新,Android7.0及以上都使用FileProvider提供的API获取到最终的uri,这里要特别强调,也是上面第二个参数必须和清单文件设置的完全一样,即应用包名+fileprovider,也即如下红框中字符串




(2)眼尖的小伙伴肯定注意到应用更新中比拍照多了一行授权的代码。这是因为FileProvider内部进行了一个授权的判断,如果未授权,则会抛出异常,所以才需要加上那一行授权代码的。具体看下图代码和注释



到这里有的小伙伴又纳闷了,那都抛异常了,怎么拍照不也跟随潮流设置一波呢?那是因为拍照在创建Intent时传入的Action为ACTION_IMAGE_CAPTURE。添加这个值之后startActivity的时候会调用到Intent的一个方法migrateExtraStreamToClipData()方法,方法最后有段代码是进行判断Action是否为我们设置的这个值,如果是,会自动为我们添加上面的权限,所以我们才不用再次手动添加权限,具体如下图所示。由于安装apk我们只用到读取的权限即可,所以上面就没多添加写的权限



最后小伙伴就可以愉快地适配Android7.0关于file://URI的异常啦,不知道我举的两个例子能否让你能够举一反三呢?如发现内容有误,请指正,在此小弟先谢过了。如果不懂可留言,看到我会及时回复。
然后贴出官网的适配(原滋原味来一波)  适配步骤
关于适配还有更加牛逼的操作,本篇博客也有部分是摘抄自鸿洋大神的博客,鸿洋大神博客最后有个完美适配的解决方案,感兴趣的小伙伴可以自行查看,现在贴出链接   鸿洋大神完美适配
最后按照国际惯例(其实是自己的喜好),这里博主推荐大家俩款可谓牛逼轰轰滴神器。
正所谓工欲善其事必先利其器,天下武功唯快不破。平时阻碍我们开发进度的往往不是我们滴智商+情商(关情商鸟事。。。),而往往是永无至今龟速滴编译,有没有办法让编译速度加快到秒级呢?那是肯定的,请拿好以下两款神器。
JRebel for Android(秒级神器,唯一缺陷是得破解,要不然就翻墙提供一个FaceBook账号,让人家跟踪你的使用即可免费)
破解正确打开方式(失效勿喷)

JRebel官网(无需翻墙)

FindLine(阿里巴巴秒级神器,号称最快编译神器(比JRebel for Android还快,简直不是人。。。),具体多快传闻只有亲身体验滴人才能知晓。。。)
FindLine GitHub
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息