google文档中的拍照demo,在android7.0上的奔溃
谷歌PhotoIntentActivity.zip下载地址。这个demo在android7.0上,会因为版本升级涉及到文件分享机制的更改,而导致出现崩溃。
一:bug再现
google案例图android.os.FileUriExposedException: file:///data/user/0/com.xiaguangcheng.bluetoothc2s/cache/IMG_20170320_160221_1256793946.jpg exposed beyond app through ClipData.Item.getUri()
在android7.0以上机器中,当我们点击第一个按钮的时候,将会引发崩溃。
二:代码分析(在android7.0以上需要手动请求权限哦)
1:当我们点击第一个按钮时,将会调用下面这行代码
//添加intent的action,即打开相机
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
switch(actionCode) {
//当我们点击第一个按钮的时候,会进入switch中的来
case ACTION_TAKE_PHOTO_B:
File f = null;
try {
f = setUpPhotoFile();
mCurrentPhotoPath = f.getAbsolutePath();
//通过Uri.fromFile()方法获取到创建的临时文件uri,传入到相机中
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
} catch (IOException e) {
e.printStackTrace();
f = null;
mCurrentPhotoPath = null;
}
break;
default:
break;
} // switch
startActivityForResult(takePictureIntent, actionCode);
**To securely offer a file from your app to another app, you need to configure your app to offer a secure handle to the file, in the form of a content URI.
事实上,在谷歌在android N中已经强调了,在应用之间的文件分享,需要使用content uri,而非file uri。即形式应该如:content://com.example.myapp.fileprovider/myimages/default_image.jpg
而非如:file:///data/user/0/com.xiaguangcheng.bluetoothc2s/cache/IMG_20170320_160221_1256793946.jpg
**
三:解决bug
按照谷歌文档提示,我们应该在AndroidManifest.xml中配置一个<provider>的标签,利用他来完成内容的分享,在配置的过程中,需要引用一个xml文件,这些基本的配置操作可以详见谷歌文档。在配置的过程当中笔者踩了两个坑,希望读者能够避免:
1:在配置的xml文件根标签<paths>中别忘了添加
xmlns:android="http://schemas.android.com/apk/res/android"
2:关于子标签<files-path><external-path>的区别,以及name,path属性的意义。
<external-path name="my_images" path="" />
上面这行配置,就表示分享的文件是在外部存储空间,如果文件前面还有文件夹,需要在path中配置。name表示在content:// uri中的位置
当我们配置好了之后,就可以在代码中修改:
try {
f = setUpPhotoFile();
mCurrentPhotoPath = f.getAbsolutePath();
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
}else{
//获取uri的方式修改
Uri uriForFile = FileProvider.getUriForFile(this,"com.xiaguangcheng.bluetoothc2s.fileprovider", f);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uriForFile);
//添加uri的临时读取权限
takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} catch (IOException e) {
e.printStackTrace();
f = null;
mCurrentPhotoPath = null;
}
这里需要留意一些关于异常的捕获。笔者在这里就多走了一些弯路,出现了下面这个bug
AndroidRuntime: FATAL EXCEPTION: main
Process: com.xiaguangcheng.bluetoothc2s, PID: 25758
java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.xiaguangcheng.bluetoothc2s/cache/IMG_20170320_151151_1240399134.jpg
at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:711)
at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:400)
at com.xiaguangcheng.bluetoothc2s.camera.PhotoIntentActivity.dispatchTakePictureIntent(PhotoIntentActivity.java:161)
at com.xiaguangcheng.bluetoothc2s.camera.PhotoIntentActivity.access$000(PhotoIntentActivity.java:33)
at com.xiaguangcheng.bluetoothc2s.camera.PhotoIntentActivity$1.onClick(PhotoIntentActivity.java:215)
at android.view.View.performClick(View.java:5646)
at android.view.View$PerformClick.run(View.java:22450)
at android.os.Handler.handleCallback(Handler.java:755)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6524)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:941)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:831)
之所以出现上面这个bug,是因为在异常捕获的时候,本君并没有注意到这行代码也是需要捕获异常的。
FileProvider.getUriForFile(this,"com.xiaguangcheng.bluetoothc2s.fileprovider", f);
FileProvider源码分析
其实在FileProvider的源码中,我们可以发现在创建的过程中,会调用
private static PathStrategy parsePathStrategy(Context context, String authority){
final SimplePathStrategy strat = new SimplePathStrategy(authority);
...
...
if (target != null) {
strat.addRoot(name, buildPath(target, path));
}
}
其中SimplePathStrategy就是我们在xml中配置的一些信息。它定义了哪些文件夹是可以分享的,同时content uri中的路径具体怎么写。在调用了addRoot()方法,将xml文件中的配置信息加载之后。
@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();
}
在for循环便利map的过程中,就将文件的路径和存储在root中的路径进行了对比,如果要分享的文件满足条件,就复制。否则就抛异常。
因此在上面的代码中我们应该再添加上一条异常捕获。
四:bug解决。完美打开照相机
您能读到这里,对我已是莫大鼓励,点个喜欢,请勿打赏####