就在昨天,手机接收到Android7.0的推送,我也是第一时间进行了更新。体验了一番,应用的安装速度确实是得到了提高。然后,写了图片选择的小demo,发现再Android7.0上会出现问题。查了一下,发现是Android7.0的一个新特性,将解决办法记录一下。
这里模仿一下上传头像的过程,既可以拍照上传,也可以从图库中直接选择。然后就是一个裁切的动作。在Android7.0以前可以这样写:
Android 7.0 以前
|
|
上面choosePhoto()中进行了运行时权限的处理,以防止在Android6.0上出现问题。然后一切很简单了,就是调用系统的相机以及相册,然后剪切,最后将剪切后的图片设置到一个ImageView中。在Android7.0以前上面的代码是没有问题的。但是,如果将上述代码运行在7.0以上的设备中就会报错了。
报出了 FileUriExposedException异常。后来一查,发现在Android7.0,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。所以,上述异常的问题在于我们使用的URI是 file:// URI类型的从而导致错误。在7.0上解决的方法是应用FileProvider。FileProvider,就是 ContentProvider 的一个特殊子类,帮助我们将访问受限的 file:// URI 转化为可以授权共享的 content:// URI。
Android 7.0
一.注册provider
既然FileProvider是ContentProvider的子类,所以它也是需要注册的。
|
|
这里,name的写法是固定的,authorities大都是包名后接上fileprovider。可以不写死,而是动态应用${applicationId}.fileprovider。exported为false与grantUriPermissions为true,这都是固定的。之后
二.添加引用文件
上边引用了file_path文件,需要新建一个xml文件夹,在文件夹下新建一个file_path文件。
|
|
这里
:内部存储空间应用私有目录下的 files/ 目录,等同于 Context.getFilesDir() 所获取的目录路径; :内部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getCacheDir() 所获取的目录路径; :外部存储空间根目录,等同于 Environment.getExternalStorageDirectory() 所获取的目录路径; :外部存储空间应用私有目录下的 files/ 目录,等同于 Context.getExternalFilesDir(null) 所获取的目录路径; :外部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getExternalCacheDir();
三.使用FileProvider
|
|
这里做了一下判断,在7.0上才使用FileProvider,以下还是使用原来的方法。看一下具体的使用,只是用FileProvider.getUriForFile()方法代替了Uri.fromfile()。这里的参数分别是Context,authorities,file,这里的authorities就是我们刚刚注册provider时的那个authorities。这样就将一个File://URI装换成一个Content://URI。这里看一下Log,生成的uri是下面的样子。content://com.example.cameraalbumtest.fileprovider/my_images/MyImage/1501138971453.jpg
可以看出前一部分是一个authorities,后面紧接着name值(在path_file中设置的,name值代替真实的path值)。然后后面接着图片的具体目录。那看一下原先的file:URI:file:///storage/emulated/0/MyImage/1501140051699.jpg
两者对比发现,FileProvider很好的隐藏掉了图片的存储位置,增加了安全性。
这里就介绍完了FileProvider的基本使用。但是运行上面代码在7.0设备上还是会出错,这次出错在剪切逻辑上。会发现拍照完成之后并不能进行剪切,而从图库中选择的图片就可以剪切。解决方法如下:
|
|
之所以加上addFlags()是为了授予其他应用访问权限。
授权方式有两种:
第一种方式,使用 Context 提供的 grantUriPermission(package, Uri, mode_flags) 方法向其他应用授权访问 URI 对象。三个参数分别表示授权访问 URI 对象的其他应用包名,授权访问的 Uri 对象,和授权类型。其中,授权类型为 Intent 类提供的读写类型常量:
FLAG_GRANT_READ_URI_PERMISSION
FLAG_GRANT_WRITE_URI_PERMISSION
或者二者同时授权。这种形式的授权方式,权限有效期截止至发生设备重启或者手动调用 revokeUriPermission() 方法撤销授权时。
第二种方式,配合 Intent 使用。通过 setData() 方法向 intent 对象添加 Content URI。然后使用 setFlags() 或者 addFlags() 方法设置读写权限,可选常量值同上。这种形式的授权方式,权限有效期截止至其它应用所处的堆栈销毁,并且一旦授权给某一个组件后,该应用的其它组件拥有相同的访问权限。
拥有授予权限的 Content URI 后,便可以通过 startActivity() 或者 setResult() 方法启动其他应用并传递授权过的 Content URI 数据。
鸿洋大神的这一篇可以看一看,对Android7.0的适配给出了解决办法。
Android 7.0 行为变更 通过FileProvider在应用间共享文件吧