Android 7.0动态权限大总结

应公司项目需求,做了下Android 7.0适配。对于我们程序员,适配7.0主要就是对手机本地文件的Uri做转换处理。注意红色字体,意思也就是说对于http开头的等等,非手机存储中的文件就不用管了。Uri.parse("package") 这样的也不用管!!!!!

7.0的适配,就是对手机存储中的私有文件路径的保护,当系统发现你通过intent带走了一个uri,地址是本地的文件,就会限制的。其他的原理普及请搜索其他文章吧,此处略。

下面贴一下使用步骤和我的工具类。

一、需要修改当前module的AndroidManifest.xml文件,添加provider标签,映射路径。

[html] view plain copy

  1. <application android:allowBackup="true" android:label="@string/app_name"
  2. android:supportsRtl="true">
  3. <provider
  4. <span style="color:#ff0000;">android:name="android.support.v4.content.FileProvider"</span>
  5. android:authorities="${applicationId}.myFileProvider"
  6. <span style="color:#ff0000;">android:exported="false"
  7. android:grantUriPermissions="true"</span>>
  8. <meta-data
  9. <span style="color:#ff0000;">android:name="android.support.FILE_PROVIDER_PATHS"</span>
  10. android:resource="@xml/path_file" />
  11. </provider>
  12. </application>

文中红色部分是固定写法,官网就是这么说的,别乱试了。android:resource标签的值是main/res/xml/path_file.xml文件,即在 res下新建xml文件夹,再新建一个xml文件。

二、在res/xml下新建一个path_file.xml文件,文件名随便去,但是与上步骤一致即可。

[html] view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <paths>
  4. <!--
  5. <files-path/>代表的根目录: Context.getFilesDir()
  6. <cache-path/>代表的根目录: getCacheDir()
  7. <external-path/>代表的根目录: Environment.getExternalStorageDirectory()
  8. <external-files-path/>代表的根目录: Context.getExternalFilesDir(String) Context.getExternalFilesDir(null).
  9. <external-cache-path />代表的根目录: Context.getExternalCacheDir().
  10. <root-path />代表设备的根目录new File("/");
  11. -->
  12. <!-- path=""代表根目录,也可以指定特定目录,name="camera_picture"是虚拟目录camera_picture -->
  13. <root-path name="root" path="" />
  14. <files-path name="files" path="" />
  15. <cache-path name="cache" path="" />
  16. <external-path name="external" path="" />
  17. <external-files-path name="external_files" path="" />
  18. <external-cache-path name="external_cache" path="" />
  19. </paths>
  20. </resources>

里面共有6个path标签,含义都写了,根据你的需要些对应的标签即可。比喻<external-path/>标签,里面的path=""时,这个标签映射的就是外挂sd卡根目录了,name属性没什么用,来迷惑第三方应用的虚拟目录,来掩盖文件的真实路径。

三、工具类来了。

FileUriPermissionCompat.Java

[html] view plain copy

  1. /**
  2. * @Author: duke
  3. * @DateTime: 2017-06-06 14:43
  4. * @Description: android 7.0 uri权限适配, <br/>
  5. * (通过intent暴漏uri或file给第三方app时的)私有目录被禁止访问 <br/>
  6. * 已对local path和net path做了适配 <br/>
  7. */
  8. public class FileUriPermissionCompat {
  9. private static final String TAG = FileUriPermissionCompat.class.getSimpleName();
  10. // TODO: 此处需要更改为对应值
  11. //此处需要改成AndroidManifest.xml中申请的对应的provider的authorities值
  12. private static final String AUTHORITIES = "com.duke.personalkeeper.myFileProvider";
  13. /**
  14. * 是否需要适配7.0权限
  15. *
  16. * @return
  17. */
  18. public static boolean isNeedAdapt() {
  19. //24以上版本
  20. return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
  21. }
  22. public static Uri adaptUriAndGrantPermission(Context context, Intent intent, File file) {
  23. Uri uri = adaptUri(context, file);
  24. if (uri == null) {
  25. return null;
  26. }
  27. grantUriPermission(context, intent, uri);
  28. return uri;
  29. }
  30. public static Uri adaptUri(Context context, File file) {
  31. if (context == null || file == null) {
  32. return null;
  33. }
  34. //网络路径的特殊处理,不需要7.0适配,但必须用parse()方法
  35. if (file.getPath().startsWith("http")) {
  36. return Uri.parse(file.getPath());
  37. }
  38. Uri uri = null;
  39. try {
  40. if (isNeedAdapt()) {
  41. //需要7.0特殊适配
  42. //通过系统提供的FileProvider类创建一个content类型的Uri对象
  43. uri = FileProvider.getUriForFile(context, AUTHORITIES, file);
  44. } else {
  45. //不需要适配
  46. uri = Uri.fromFile(file);
  47. }
  48. } catch (Exception e) {
  49. Log.e(TAG, "authorities value error, so can‘t convert uri !");
  50. e.printStackTrace();
  51. }
  52. return uri;
  53. }
  54. /**
  55. * 对第三方应用赋予对uri读写的权限
  56. *
  57. * @param context
  58. * @param intent
  59. * @param saveUri 适配后的uri
  60. */
  61. public static void grantUriPermission(Context context, Intent intent, Uri saveUri) {
  62. if (!isNeedAdapt()) {
  63. return;
  64. }
  65. if (context == null || intent == null || saveUri == null) {
  66. return;
  67. }
  68. //网络路径的特殊处理,不需要权限
  69. if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {
  70. //不需要授权
  71. return;
  72. }
  73. //1、授权(系统相册、相机、裁剪时需要)  -- 这种写法待分析
  74. List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
  75. for (ResolveInfo resolveInfo : resInfoList) {
  76. String packageName = resolveInfo.activityInfo.packageName;
  77. if (TextUtils.isEmpty(packageName)) {
  78. continue;
  79. }
  80. context.grantUriPermission(packageName, saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  81. }
  82. //2、授权(安装apk时需要)
  83. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  84. }
  85. public static void revokeUriPermission(Context context, Intent intent, Uri saveUri) {
  86. if (!isNeedAdapt()) {
  87. return;
  88. }
  89. if (context == null || intent == null || saveUri == null) {
  90. return;
  91. }
  92. //网络路径的特殊处理,不需要权限
  93. if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {
  94. //不需要授权
  95. return;
  96. }
  97. try {
  98. context.revokeUriPermission(saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  99. } catch (Exception e) {
  100. e.printStackTrace();
  101. }
  102. }
  103. }

核心代码:

[html] view plain copy

  1. uri = FileProvider.getUriForFile(context, AUTHORITIES, file);

file就是你想要暴露给其他应用的文件地址,比喻你要拍照,把结果保存到file文件中。

AUTHORITIES就是上面第一步中,android:authorities="${applicationId}.myFileProvider"的实际值,

${applicationId}取得是app/build.gradle中defaultConfig标签的applicationid值。通过系统提供的FileProvider类的静态方法转换file地址为一个以content://开头的特殊的uri。如果不转换的话,直接用Uri.fromFile(file),你得到的是一个file:///xxxxx这样的uri。就这差别。

转换了uri之后,还需要授权:

[html] view plain copy

  1. /**
  2. * 对第三方应用赋予对uri读写的权限
  3. *
  4. * @param context
  5. * @param intent
  6. * @param saveUri 适配后的uri
  7. */
  8. public static void grantUriPermission(Context context, Intent intent, Uri saveUri) {
  9. if (!isNeedAdapt()) {
  10. return;
  11. }
  12. if (context == null || intent == null || saveUri == null) {
  13. return;
  14. }
  15. //网络路径的特殊处理,不需要权限
  16. if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {
  17. //不需要授权
  18. return;
  19. }
  20. //1、授权(系统相册、相机、裁剪时需要)  -- 这种写法待分析
  21. List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
  22. for (ResolveInfo resolveInfo : resInfoList) {
  23. String packageName = resolveInfo.activityInfo.packageName;
  24. if (TextUtils.isEmpty(packageName)) {
  25. continue;
  26. }
  27. context.grantUriPermission(packageName, saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  28. }
  29. //2、授权(安装apk时需要)
  30. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  31. }

里面有两部分授权方式,经过多轮测试,发现需要同时使用比较好。

第一种方式,for循环,是因为有时候你并不确定需要分享的应用的包名是哪一个,所以找到所有有可能的第三方应用,全部授权了。

后来测试发现,安装apk的时候,只有上面的授权是不行的,还得加上intent.addFlag的方式再次授权才行。

最后,需要注意的是:

1、注意7.0的版本判断。

2、切记,7.0的权限有可能需要对sd卡读写,需要6.0的读写sd卡权限。当你测试7.0权限不成功时,考虑下6.0的权限是否到位了。

其他的没的说了。就这些。

时间: 2024-08-26 10:45:23

Android 7.0动态权限大总结的相关文章

Android 6.0 - 动态权限管理的解决方案(转)

转自:http://www.cnblogs.com/dubo-/p/6018262.html Android 6.0 - 动态权限管理的解决方案 转载请标注 Android 6.0版本(Api 23)推出了很多新的特性, 大幅提升了用户体验, 同时也为程序员带来新的负担. 动态权限管理就是这样, 一方面让用户更加容易的控制自己的隐私, 一方面需要重新适配应用权限. 时代总是不断发展, 程序总是以人为本, 让我们为应用添加动态权限管理吧! 这里提供了一个非常不错的解决方案, 提供源码, 项目可以直

Android 6.0 动态权限申请注意事项

<span style="font-size:24px;color:#ffff00;background-color: rgb(0, 0, 153);"><strong>Android 6.0 权限区分</strong></span> Android 6.0 为了保护用户隐私,将一些权限的申请放在了应用运行的时候去申请, 比如以往的开发中,开发人员只需要将需要的权限在清单文件中配置即可,安装后用户可以在设置中的应用信息中看到:XX应用以获

android 6.0+ 动态权限获取

android 6.0+ 的权限 需要动态申请 这里的权限针对的是 敏感权限: SMS(短信) SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS STORAGE(存储卡) READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE CONTACTS(联系人) READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS PHONE(手机) READ_PHONE_STATE CA

Android 6.0: 动态权限管理的解决方案

欢迎Follow我的GitHub, 关注我的CSDN. Android 6.0版本(Api 23)推出了很多新的特性, 大幅提升了用户体验, 同时也为程序员带来新的负担. 动态权限管理就是这样, 一方面让用户更加容易的控制自己的隐私, 一方面需要重新适配应用权限. 时代总是不断发展, 程序总是以人为本, 让我们为应用添加动态权限管理吧! 这里提供了一个非常不错的解决方案. Android系统包含默认的授权提示框, 但是我们仍需要设置自己的页面. 原因是系统提供的授权框, 会有不再提示的选项. 如

android 6.0动态权限的申请

一下9组权限需要动态申请//通讯录(读.写.查询)group:android.permission-group.CONTACTS permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS permission:android.permission.READ_CONTACTS //android 9.0之后引入CALL_LOG 权限组并将 READ_CALL_LOG.WRITE_C

Android6.0动态权限管理库RuntimePermission

在Android开发中,申请个权限本来很简单的事情,可就因为Android6.0出个动态权限,整的那个复杂度是几何倍增.本着我们开发向简的宗旨,今天我在这推荐一个Android6.0动态权限管理库RuntimePermission,下面我就来介绍它的大用处--化繁为简! 首先,看一下效果演示图: 接下来,我们介绍一下库的功能及其使用方法: 1.功能 Android6.0动态权限管理库,回调模式管理动态权限,无需关注申请权限各种状态,只用关心回调是否有权限,然后做不同逻辑处理! 2.Android

Android开发之深入理解Android 7.0系统权限更改相关文档

摘要: Android 6.0之后的版本增加了运行时权限,应用程序在执行每个需要系统权限的功能时,需要添加权限请求代码(默认权限禁止),否则应用程序无法响应:Android 7.0在Android 6.0的基础上,对系统权限进一步更改,这次的权限更改包括三个方面: APP应用程序的私有文件不再向使用者放宽 Intent组件传递file://URI的方式可能给接收器留下无法访问的路径,触发FileUriExposedException异常,推荐使用FileProvider DownloadMana

Android 6.0的权限问题

Android 6.0的权限获取不同于别的版本,具体的实例如下: if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { //申请READ_CONTACTS权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_

Android6.0动态权限申请

goggle在Android6.0要求部分权限需要动态申请,直接下载AndroidManifest.xml中无效 6.0权限的基本知识,以下是需要单独申请的权限,共分为9组, 每组只要有一个权限申请成功了,就默认整组权限都可以使用了. group:android.permission-group.CONTACTS permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS perm