google文档中的拍照demo,在android7.0上的奔溃

2017-03-20  本文已影响238人  夏广成

谷歌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解决。完美打开照相机

您能读到这里,对我已是莫大鼓励,点个喜欢,请勿打赏####

上一篇 下一篇

猜你喜欢

热点阅读