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

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

作者: 夏广成 | 来源:发表于2017-03-20 16:43 被阅读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解决。完美打开照相机

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/rifinttx.html