Android7.0适配 私有目录访问异常-Failed to find configured root that contains " + path

当我碰到这个问题之后,知道是由于7.0私有目录的配置导致的问题,经过网上是分析并不能找到解决方案,再加上用的是第三方的开源框架,所以这个时候需要自己去查看源码去解决.

经过分析我找到了这里,然后断点查看异常的原因.

发现了一个细节

重点在于

HashMap<String, File> mRoots = new HashMap<String, File>();

这个mRoots集合的遍历,一般这个根目录会包含两个,如果你自己设置的路径不在这两个根目录下面就会导致mostSpecific这个集合的数据为null,然后执行到下面就会跑出异常

解决思路:

我们可以断点查看它包含哪些根目录,然后查看我们的路径是否符合根目录

当我点击拍照的时候,就会导致异常,我断点进去看了之后发现

如上所示,拍照的进入相册的时候path路径的根目录和rootPath是对应不上去的,所以导致了异常.

那么,我是在哪里设置的路径呢?

此处是拍照的源码:

/**
 * 拍照的方法
 */
public void takePicture(Activity activity, int requestCode) {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    takePictureIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
        if (Utils.existSDCard()) {
            takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");//异常的目录
            takeImageFile = new File(Environment.getExternalStorageDirectory(), "/Download/"); //经过修正后的目录
        } else {
            takeImageFile = Environment.getDataDirectory();
        }
        takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");
        if (takeImageFile != null) {
            // 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            // 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片,
            // 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据,
            // 如果没有指定uri,则data就返回有数据!

            Uri uri;
            if (VERSION.SDK_INT <= VERSION_CODES.M) {
                uri = Uri.fromFile(takeImageFile);
            } else {
                /**
                 * 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider
                 * 并且这样可以解决MIUI系统上拍照返回size为0的情况
                 */
                uri = FileProvider.getUriForFile(activity, ProviderUtil.getFileProviderName(activity), takeImageFile);
                //加入uri权限 要不三星手机不能拍照
                List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities
                        (takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    activity.grantUriPermission(packageName, uri, Intent
                            .FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
            }

            Log.e("nanchen", ProviderUtil.getFileProviderName(activity));
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        }
    }
    activity.startActivityForResult(takePictureIntent, requestCode);
}

看到我标注红色的代码没,那里就是罪魁祸首. 由于这个开源库年久失修,所以需要自己去维护.

这个是作者的地址Github地址:https://github.com/jeasonlzy0216  如果你也在用开源使用我的方案.

报异常的地方是此处

/**
 * 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider
 * 并且这样可以解决MIUI系统上拍照返回size为0的情况
 */
uri = FileProvider.getUriForFile(activity, ProviderUtil.getFileProviderName(activity), takeImageFile);

跳转进源码之后

public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
        @NonNull File file) {
    final PathStrategy strategy = getPathStrategy(context, authority);
    return strategy.getUriForFile(file);
}

只看返回值即可.查看getUriForFile(file)方法,断点查看即进入

SimplePathStrategy的getUriForFile方法

此处根据不同的类型,抛出不同的异常,我们需要实事求是的去翻看源码然后解决

@Override
    public Uri getUriForFile(File file) {
        String path;
        try {
            path = file.getCanonicalPath();
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
        }

        // Find the most-specific root path
        Map.Entry<String, File> mostSpecific = null;
        for (Map.Entry<String, File> root : mRoots.entrySet()) {
            final String rootPath = root.getValue().getPath();
            if (path.startsWith(rootPath) && (mostSpecific == null
                    || rootPath.length() > mostSpecific.getValue().getPath().length())) {
                mostSpecific = root;
            }
        }

        if (mostSpecific == null) {
            throw new IllegalArgumentException(
                    "Failed to find configured root that contains " + path);
        }

        // Start at first char of path under root
        final String rootPath = mostSpecific.getValue().getPath();
        if (rootPath.endsWith("/")) {
            path = path.substring(rootPath.length());
        } else {
            path = path.substring(rootPath.length() + 1);
        }

        // Encode the tag and path separately
        path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
        return new Uri.Builder().scheme("content")
                .authority(mAuthority).encodedPath(path).build();
    }

    @Override
    public File getFileForUri(Uri uri) {
        String path = uri.getEncodedPath();

        final int splitIndex = path.indexOf('/', 1);
        final String tag = Uri.decode(path.substring(1, splitIndex));
        path = Uri.decode(path.substring(splitIndex + 1));

        final File root = mRoots.get(tag);
        if (root == null) {
            throw new IllegalArgumentException("Unable to find configured root for " + uri);
        }

        File file = new File(root, path);
        try {
            file = file.getCanonicalFile();
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
        }

        if (!file.getPath().startsWith(root.getPath())) {
            throw new SecurityException("Resolved path jumped beyond configured root");
        }

        return file;
    }
}

此处是7.0适配的官方方案和解释,抽空可以看下

https://developer.android.google.cn/reference/android/support/v4/content/FileProvider

此处大致介绍下:

FileProvider是一个特殊的子类,ContentProvider它通过创建content:// Uri文件而不是文件来促进与应用程序关联的文件的安全共享file:/// Uri

内容URI允许您使用临时访问权限授予读写访问权限。当您创建Intent包含内容URI时,为了将内容URI发送到客户端应用程序,您还可以调用Intent.setFlags()添加权限。只要接收的堆栈Activity处于活动状态,客户端应用程序就可以使用这些权限。对于Intent转到a Service,只要Service正在运行,权限就可用 。

相比之下,要控制对a的访问,file:/// Uri必须修改底层文件的文件系统权限。您提供的权限可供 任何应用程序使用,并在您更改之前保持有效。这种访问水平从根本上说是不安全的。

内容URI提供的文件访问安全性提高使FileProvider成为Android安全基础架构的关键部分。

FileProvider概述包括以下主题:

  1. 定义FileProvider
  2. 指定可用文件
  3. 检索文件的内容URI
  4. 授予URI临时权限
  5. 向另一个应用程序提供内容URI

以上就是适配7.0私有目录的步骤,具体的定义查看官网去适配和理解,有时间我会继续更新翻译给大家看

猜你喜欢

转载自blog.csdn.net/yang1349day/article/details/81476580