Android7.0权限适配

谷歌在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

    

将以上复制粘贴到清单文件节点下再修改下即可

几点说明:

1.其中下面这一行中的com.example.myapplication必须改为自己项目的包名,包名在Module下build.gradle下

android:authorities="com.example.myapplication.fileprovider"
20180330161203887.png

2.倒数第二行涉及一个xml文件声明,这个在下一个点介绍

android:resource="@xml/file_paths" 
编写xml

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


20180330161730902.png
20180330161831891.png

然后打开xml文件



    
    
    
    
    
    

标签说明

为顶层标签,它下面可添加任意个(0,1或多个)上面出现的标签

子标签解释

可以看到上面每个标签都含有一个name和path属性

name:这个可以随意写(用于隐藏真实的目录,后面再演示)

path:要共享的目录,只能是目录,不能是某个具体的文件

代表Context.getFilesDir()所指向的目录

代表Context.getCacheDir()所指向的目录

代表Environment.getExternalStorageDirectory()所指向的目录

代表Context.getExternalFilesDir()所指向的目录

代表Context.getExternalCacheDir()所指向的目录

代表Context.getExternalMediaDirs()所指向的目录(API21+设备才能使用,所以一般不用)

以上解释可能有的小伙伴还是一脸懵逼,下面举个具体的下载例子说一下(不涉及代码)

比如我现在把新版本安装包下载在SD卡根目录下的Download文件夹下,那么我的xml如下设计即可

其中path就得指定为Download,name的值可随意写


20180330163531905.png

关于name和path最终去向这里盗用博客里面一张图(如涉及侵权麻烦博主找我撤回)

由下图可知最终我们将原本的file://URI转为了content://URI,这个正是安卓7.0要求的正确传递方式,而我们上面的name最终传递到了URI的后面作为一个隐藏的目录,隐藏了原文件的真实目录

20180330163650829.png

使用

上面说了这么多,都只是热身,单纯配置而已,还没涉及一行代码,接下来就得开始用代码实现我们的功能了,这里也主要针对上面提到的两个例子(其它例子看完可举一反三,下面只展示修改部分,不涉及全部代码)这里得用到一个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(context, "com.example.myapplication.fileprovider", apkFile);
} else {
    //Android7.0还用原先的api
    uri = Uri.fromFile(apkFile);
}
//当前代码在Activity里则下面这句可省略
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//授权Intent读取URI和写URI的权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//设置拍照后保存目录
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
context.startActivity(intent);

几点说明

(1)不管是拍照还是应用更新,Android7.0及以上都使用FileProvider提供的API获取到最终的uri,这里要特别强调,也是上面第二个参数必须和清单文件设置的完全一样,即应用包名+fileprovider,也即如下红框中字符串


20180330235147734.png

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

20180330235620267.png

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


20180331000552868.png

最后小伙伴就可以愉快地适配Android7.0关于file://URI的异常啦,不知道我举的两个例子能否让你能够举一反三呢?如发现内容有误,请指正,在此小弟先谢过了。如果不懂可留言,看到我会及时回复。

然后贴出官网的适配(原滋原味来一波) 适配步骤

你可能感兴趣的:(Android7.0权限适配)