转载请标明出处:
http://blog.csdn.net/djy1992/article/details/72533310
本文出自:【奥特曼超人的博客】

权限更改

Android 7.0 做了一些权限更改,这些更改可能会影响您的应用。Android7.1的可以看这篇文章:
《android 7.1悬浮窗系统权限问题》


系统权限更改

目录权限

为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问 (0700)。此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。此权限更改有多重副作用:

私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 和或 MODE_WORLD_WRITEABLE 而进行的此类尝试将触发 SecurityException

  • 注:迄今为止,这种限制尚不能完全执行。应用仍可能使用原生 API 或 File API 来修改它们的私有目录权限。但是,我们强烈反对放宽私有目录的权限。

文件权限更改

FileUriExposedException 异常

传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。

因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。

DownloadManager 不再按文件名分享私人存储的文件。旧版应用在访问 COLUMN_LOCAL_FILENAME 时可能出现无法访问的路径。

面向 Android 7.0 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException。

通过使用
DownloadManager.Request.setDestinationInExternalFilesDir()
或者
DownloadManager.Request.setDestinationInExternalPublicDir()

将下载位置设置为公共位置的旧版应用仍可以访问 COLUMN_LOCAL_FILENAME 中的路径,但是我们强烈反对使用这种方法。

对于由 DownloadManager 公开的文件,首选的访问方式是使用ContentResolver.openFileDescriptor()

在应用间共享文件 StrictMode API

对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。

如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。

要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。

进行此授权的最简单方式是使用 FileProvider 类。如需了解有关权限和共享文件的详细信息,请谷歌开发中的《共享文件权限操作方法》

  • 设置文件共享
      了解如何设置您的应用程序共享文件。

  • 共享文件
      了解如何通过生成文件的内容URI,向URI授予访问权限,并向应用程序发送URI,从而向另一个应用程序提供文件。

  • 请求共享文件
      了解如何请求另一个应用程序共享的文件,接收该文件的内容URI,并使用内容URI打开文件。

  • 获取文件信息
      学习如何应用程序可以使用由fileprovider检索包括MIME类型和文件大小,文件信息生成的内容URI。


FileProvider

FileProvider,是Android 7.0新增的一个类,该类位于v4包下的android.support.v4.content.FileProvider,使用方法和ContentProvider类似,操作步骤如下:

一、在资源文件夹res/xml下新建file_provider.xml文件,文件声明权限请求的路径,代码如下:

    <?xml version="1.0" encoding="utf-8"?><paths xmlns:android="http://schemas.android.com/apk/res/android"><!--对应外部内存卡根目录:Environment.getExternalStorageDirectory()--><external-path name="ext_root" path="/" /><!--指定文件存储的区块和区块的相对路径--><files-path name="my_path" path="images/"/></paths>

属性说明:

指定文件存储的区块和区块的相对路径

  • name:是一个虚设的文件名(可以自由命名),对外可见路径的一部分,隐藏真实文件目录
  • path:是一个相对目录,相对于当前的子标签根目录,:表示内部内存卡根目录,对应根目录等价于Context.getFilesDir(),完整路径:/data/use/mnt/sdcard/com.immqy.dujinyang/files

这里的path要对应上相关路径,这里是想根目录,不指定特别目录,所以直接用"/"即可。

<paths>根标签下可以添加的子标签也是有限的,除了上述的提到的这个子标签外,还包括下面几个:

  1. <cache-path>,表示应用默认缓存根目录,对应根目录等价于getCacheDir(),查看完整路径:/data/user/0/cn.teachcourse.demos/cache

  2. <external-path>,表示外部内存卡根目录,对应根目录等价于
    Environment.getExternalStorageDirectory(), 路径:/storage/emulated/0

  3. <external-files-path>,表示外部内存卡根目录下的APP公共目录,对应根目录等价于
    Context的getExternalFilesDir(String) , 路径:
    /storage/emulated/0/Android/data/com.immqy.www

  4. <external-cache-path>,表示外部内存卡根目录下的APP缓存目录,对应根目录等价于Context.getExternalCacheDir(),路径:
    /storage/emulated/0/Android/data/cn.teachcourse.demos/cache

示例:
最终,在file_provider.xml文件中,添加上述5种类型的临时访问权限的文件目录,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><!--1、name对应的属性值,开发者可以自由定义;2、path对应的属性值,当前external-path标签下的相对路径比如:/storage/emulated/0/92Recycle-release.apksdcard路径:/storage/emulated/0(WriteToReadActivity.java:176)at cn.teachcourse.nougat.WriteToReadActivity.onClick(WriteToReadActivity.java:97)at android.view.View.performClick(View.java:5610)at android.view.View$PerformClick.run(View.java:22265)相对路径:/--><!--1、对应内部内存卡根目录:Context.getFileDir()--><files-path
        name="int_root"path="/" /><!--2、对应应用默认缓存根目录:Context.getCacheDir()--><cache-path
        name="app_cache"path="/" /><!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()--><external-path
        name="ext_root"path="pictures/" /><!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)--><external-files-path
        name="ext_pub"path="/" /><!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()--><external-cache-path
        name="ext_cache"path="/" />
</paths>

所以生成指定文件的Content URI的步骤:

  • 确定上述用哪一种 (5种类型)
  • 明确指定文件的完整路径(包括目录、文件名)
  • 调用getUriForFile()方法生成

除了普通的授权,还有一种是Intent发送对外授权,对外提供可访问的Content URI,在重写的startActivityResult()方法中获取授予临时权限的Content URI或向用户提供可访问的接口来获取文件,后面的这种方式获取文件后转换成Content URI。

  • 请求授予访问公共目录的权限,代码如下
 if (Build.VERSION.SDK_INT > 23) {/**Android 7.0以上的方式**/mStorageManager = this.getSystemService(StorageManager.class);StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume();Intent intent = storageVolume.createAccessIntent(Environment.DIRECTORY_PICTURES);startActivityForResult(intent, REQUEST_DCODE_GRAINT_URI);}
  • 重写的startActivityResult()方法中获取授予临时权限的Content URI,代码如下:
   @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case REQUEST_DCODE_GRAINT_URI:updateDirectoryEntries(data.getData());Log.d(TAG, "onActivityResult:Uri= "+data.getData());break;}}
  • 查询Environment.DIRECTORY_PICTURES目录,返回的Content URI包含的文件和文件类型相关信息,代码如下:
private static final String[] DIRECTORY_SELECTION = new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME,DocumentsContract.Document.COLUMN_MIME_TYPE,DocumentsContract.Document.COLUMN_DOCUMENT_ID,};@TargetApi(Build.VERSION_CODES.LOLLIPOP)private void updateDirectoryEntries(Uri uri) {ContentResolver contentResolver = this.getContentResolver();Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,DocumentsContract.getTreeDocumentId(uri));Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,DocumentsContract.getTreeDocumentId(uri));try (Cursor docCursor = contentResolver.query(docUri, DIRECTORY_SELECTION, null, null, null)) {while (docCursor != null && docCursor.moveToNext()) {mPath_tv.setText(docCursor.getString(docCursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)));}}try (Cursor childCursor = contentResolver.query(childrenUri, DIRECTORY_SELECTION, null, null, null)) {while (childCursor != null && childCursor.moveToNext()) {String fileName = childCursor.getString(childCursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));String mimeType = childCursor.getString(childCursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE));Log.e(TAG, "Directory: "+fileName+"\n"+mimeType);}}}

生成Content URI的临时授权:
上一步并没有获得指定文件的读写权限,想要获得文件的读写权限需要调用Context.grantUriPermission(package, Uri, mode_flags)方法,该方法向指定包名的应用程序申请获得读取或者写入文件的权限,参数说明如下:

  • package:指定应用程序的包名,Android Studio真正的包名指build.gradle声明的applicationId属性值;getPackageName():指AndroidManifest.xml文件声明的package属性值,如果两者不一致,就不能提供getPackageName()获取包名,否则报错!
  • Uri:指定请求授予临时权限的URI,例如:contentUri
  • mode_flags:指定授予临时权限的类型,选择其中一个常量或两个:Intent.FLAG_GRANT_READ_URI_PERMISSION,Intent.FLAG_GRANT_WRITE_URI_PERMISSION
    授予文件的临时读取或写入权限,如果不再需要了,TeachCourse该如何撤销授予呢?撤销权限有两种方式:第一种:通过调用revokeUriPermission()撤销,第二种:重启系统后自动撤销
File imagePath = new File(Environment.getExternalStorageDirectory(), "download");
File newFile = new File(imagePath, "miqiyun_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.immqy.karl-dujinyang", newFile); 

二、AndroidManifest.xml 添加组件 provider 相关信息,类似组件 activity ,指定resource属性引用上一步创建的xml文件(后面会详细介绍各个属性的用法),代码如下:

   <!-- 定义FileProvider --><provider
        android:name="android.support.v4.content.FileProvider"android:authorities="@string/pack_name"android:exported="false"android:grantUriPermissions="true"><meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_provider" /></provider>

属性说明:

  • android:name:对应属性值:android.support.v4.content.FileProvider或者子类完整路径
  • android:authorities:对应属性值是一个常量,通常定义的方式packagename.fileprovider,如:com.immqy.dujinyang.fileprovider
  • android:exported:对应属性值是一个boolean变量,设置为false
  • android:grantUriPermissions:对应属性值也是一个boolean变量,设置为true,允许获得文件临时的访问权限

    关联res/xml文件夹下创建的file_provider.xml文件,需要在标签内,添加子标签,设置标签的属性值,包括:

  • 标签 android:name:对应属性值是一个固定的系统常量android.support.FILE_PROVIDER_PATHS
  • 标签 android:resource:对应属性值指向我们的xml文件 @xml/file_provider

三、代码上做动态权限申请,使用getUriForFile()和grantUriPermission(),代码如下:

 if (Build.VERSION.SDK_INT > 23) {/**Android 7.0以上的方式**/Uri contentUri = getUriForFile(this, getString(R.string.pack_name), file);grantUriPermission(getPackageName(), contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);} 

注意:申请的是FLAG_GRANT_WRITE_URI_PERMISSION

测试:修改build.gradle文件compileSdkVersion大于或等于24,targetSdkVersion等于24,然后使用Android 7.0模拟器运行Demo,完成无误。

|| 版权声明:本文为博主杜锦阳原创文章,转载请注明出处。

Android 7.0之访问文件的权限和FileProvider类相关推荐

  1. Android 10.0 PackageManagerService(二)权限扫描-[Android取经之路]

    摘要:PackageManagerService在systemReady()后,进行了/system/etc/permissions中的各种xml进行扫描,进行相应的权限存储,供以后使用 阅读本文大约 ...

  2. android 兼容性定义,谷歌释出 Android 7.0 兼容性定义文件,史上最严

    原标题:谷歌释出 Android 7.0 兼容性定义文件,史上最严 每次跟随 Android 系统的更新,谷歌每年也会更新 Android 兼容性定义文档来确保 OEM 厂商的设备能够正常预装谷歌全家 ...

  3. android webview权限申请_Android应用开发之android 6.0下webview的定位权限设置方法

    本文将带你了解Android应用开发android 6.0下webview的定位权限设置方法,希望本文对大家学Android有所帮助. 如下所示: WebView webView =   (WebVi ...

  4. Android 6.0系统读写文件问题( Permission denied)

    再折腾相机的项目中,一直被文件存储弄的焦头烂额,最后才就发现是这个问题,记录一下. 异常 java.io.FileNotFoundException: /storage/emulated/0/Vide ...

  5. Android 6.0以上动态申请文件读写权限

    自Android 6.0开始,Google开始对系统权限做出严格的要求,有些权限必须用户同意才能调用相应功能,所以开发者需要调用权限申请的代码,弹出一个小窗口,向用户动态申请权限. 以下是动态申请文件 ...

  6. android 4.0下访问主进程访问网络和开启另外另外的线程

    为什么80%的码农都做不了架构师?>>>    在android 2.3上设计的下载程序,在android 4.0上运行时报android.os.NetworkOnMainThrea ...

  7. android录音权限不弹框,android 6.0以下,拒绝录音权限后处理

    搜了很久,都是牛头不对马嘴的复制黏贴. 大概感觉就是android 6.0以下要获取权限是否被拒绝了很难.. 最后找到个这个方法,凑活着用. 通过判断录音的分贝来判断是否开启了录音权限 MediaRe ...

  8. android解除录音权限,android 6.0以下,拒绝录音权限后处理

    搜了很久,都是牛头不对马嘴的复制黏贴. 大概感觉就是android 6.0以下要获取权限是否被拒绝了很难.. 最后找到个这个方法,凑活着用. 通过判断录音的分贝来判断是否开启了录音权限 MediaRe ...

  9. android 动态录音权限,Android如何判断手机是否有录音权限的工具类

    作用 判断手机是否有录音权限的工具类,兼容6.0以上以及以下android系统 测试环境 这篇文章是评论中的网友提出质疑后,经过重写修改与重写测试后编写的,我的调试环境是小米note3,Android ...

最新文章

  1. eclipse 收缩 选择行_一文解决Java初学者的选择难题
  2. Spring Boot实现简单的用户权限管理(超详细版)
  3. MYSQL数据库的优化(二)
  4. mysql 大数据量插入遇到瓶颈 可行性方案探究
  5. 面试题56 - I. 数组中数字出现的次数
  6. 20170810上课笔记
  7. 数资 | 已知现期量如何求出增长量?
  8. 【Pytorch-手写字体识别】手写字体识别项目
  9. 基于SSH框架的电影订票系统网站的设计与实现
  10. 移动开发需要关心的热门技术(1)
  11. 爬虫python淘宝_python爬虫爬取淘宝失败原因分析
  12. JPEG/Exif/TIFF格式解读(1):JEPG图片压缩与存储原理分析
  13. 计算机信息系统打印输出的涉密文件,计算机信息系统打印输出的涉密文件,应当按相应的()文件进行管理。...
  14. RK3588参数 rk3288处理器属于什么档次
  15. Redis 如何实现库存扣减操作和防止被超卖?
  16. 阿里巴巴产品实习生4天
  17. 关于调用阿里云短信服务接口实现短信验证码的过程
  18. 源码学习 - 【FreeRTOS】PRIVILEGED_FUNCTION 含义理解
  19. [亲身经历]在上海使用公交卡乘坐出租车千万要小心不良司机给你换卡
  20. HackTheBox-Beatles

热门文章

  1. BusterNet网络Python模型实现学习笔记之二
  2. 请教各位仁兄:如何取得本机所有硬盘的所有罗辑盘符?
  3. preventDefault()恢复
  4. groovy 规则引擎 java_Java内嵌Groovy脚本引擎进行业务规则剥离(一)
  5. 【Flask】YOLO挖掘机目标检测模型Python flask部署(附项目链接)
  6. 标准库 - fmt/scan.go 解读
  7. IOS推送通知开发流程
  8. RuoYi-Vue搭建(若依)
  9. 海泰方圆受邀出席2023黄河观潮·信创与商用密码融合发展高峰研讨会
  10. 深大c语言程序设计题库,深大复试C语言程序.doc