当我碰到这个问题之后,知道是由于7.0私有目录的配置导致的问题,经过网上是分析并不能找到解决方案,再加上用的是第三方的开源框架,所以这个时候需要自己去查看源码去解决.
经过分析我找到了这里,然后断点查看异常的原因.
发现了一个细节
重点在于
HashMap mRoots = new HashMap();
这个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 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 mostSpecific = null;
for (Map.Entry 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概述包括以下主题:
以上就是适配7.0私有目录的步骤,具体的定义查看官网去适配和理解,有时间我会继续更新翻译给大家看