Android 7.0之FileProvider——通过FileProvider来获取content uri

前言

随着Android 7.0的到来,为了进一步提高私有文件的安全性,Android不再由开发者放宽私有文件的访问权限,之前我们一直使用"file:///"绝对路径来传递文件地址的方式,在接收方访问时很容易触发SecurityException的异常。

因此,为了更好的适配Android 7.0,例如相机拍照这类涉及到文件地址传递的地方就用上了FileProvider,FileProvider也更好地进入了大家的视野。

其实FileProvider是ContentProvider的一个特殊子类,本质上还是基于ContentProvider的实现,FileProvider会把"file:///"的路径转换为特定的"content://"形式的content uri,接收方通过这个uri再使用ContentResolver去媒体库查询解析。

 

使用示例

1.在AndroidManifest.xml里声明Provider

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.myapp"> 
      <application        
          ...>  
          <provider     
              android:name="androidx.core.content.FileProvider" //指向系统里的FileProvider类
              android:authorities="com.example.myapp.fileprovider" //对应你的content uri的基础域名,生成的uri将以content://com.example.myapp.fileprovider作为开头
              android:grantUriPermissions="true" //设置允许获取访问uri的临时权限
              android:exported="false"//设置不允许导出,我们的FileProvider应该是私有的
          >            
          <meta-data
              android:name="android.support.FILE_PROVIDER_PATHS" 
              android:resource="@xml/filepaths" //用于设置FileProvider的文件访问路径
           />        
          </provider>        
          ...    
      </application>
</manifest>

name直接使用系统的androidx.core.content.FileProvider,如果需要自己继承FileProvider,则在这里写自己的FileProvider,一定要写全名,即:包名+类名。

2.配置FileProvider文件共享的路径

接下来在我们的res目录下创建一个xml目录,在xml目录下新建一个filepaths.xml,这个xml的名字可以根据项目的具体情况来取,对应第一步中mainifest配置里的FileProvider路径的配置中指定的文件。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path 
        name="my_images" 
        path="images/"/> 
</paths>

paths元素必须包含以下一个或者多个子元素:

  •     files-path 对应目录Context.getFilesDir()
  •     cache-path 对应目录Context.getCacheDir()
  •     external-path 对应目录Environment.getExternalStorageDirectory()
  •     external-files-path 对应目录Context,getExternalFilesDir(String) 或者Context.getExternalFilesDir(null)
  •     external-cache-path 对应目录Context.getExternalCacheDir()。

这一点要谨记,在后面创建文件时会用到。
name属性:name 是分享的文件路径的一部分,它会覆盖要分享的真实的路径,即path指定的路径。 后续生成 content:// URI 时,会使用这个别名代替真实目录名。这样做的目的,很显然是为了提高安全性。这里的name为my_images,所以对应的content uri为

content://com.example.myapp.fileprovider/my_images

path属性:<files-path>标签对应的路径地址为Context.getFilesDir()]()返回的路径地址,而path属性的值则是该路径的子路径,这里的path值为"images/",那组合起来的路径如下所示:

Content.getFilesDir() + "/images/"

name属性跟path属性一一对应,根据上面的配置,当访问到文件"content://com.example.myapp.fileprovider/my_images/xxx.jpg",就会找到这里配置的path路径"Content.getFilesDir() + "/images/xxx.jpg"

扫描二维码关注公众号,回复: 10659082 查看本文章

示例

<?xml version="1.0" encoding="utf-8"?>
<paths>
 //代表的目录即为:Environment.getExternalStorageDirectory()/Android/data/包名/
    <external-path
        name="files_root"
        path="Android/data/包名/" />

 //代表的目录即为:Environment.getExternalStorageDirectory()
    <external-path
        name="external_storage_root"
        path="." />

 //代表的目录即为:Environment.getExternalStorageDirectory()/pics
    <external-path
        name="external"
        path="pics" />
 //意味着Context.getExternalCacheDir()路径下的全部文件
    <external-cache-path name="name" path="." />

</paths>

3.使用FileProvider

File imagePath = new File(Context.getFilesDir(), "images");
File photoFile= new File(imagePath, "default_image.jpg");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    Uri uri = FileProvider.getUriForFile(getContext(), "com.example.myapp.fileprovider", photoFile);
} else {
    Uri uri = Uri.fromFile(photoFile);
}

getUriForFile方法中的第二个参数要与第一步中在manifest文件里面创建的provider里面的android:authorities名称一样
在file-path中使用name为my_images;
正常路径:/data/data/com.example.myapp/files/default_image.jpg
uri路径:content://com.kgh.test.fileprovider/my_images/default_image.jpg
显然路径被name覆盖了增强了安全性。

4.授予一个uri的临时权限,并将值传给接收方app

我们假设接收方app使用startActivityForResult来请求app的图片资源,则请求方获取请求后根据上面的代码获取

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

用来授予接收方对于文件操作的临时权限,可以设置为 Intent.FLAG_GRANT_READ_URI_PERMISSION或者Intent.FLAG_GRANT_WRITE_URI_PERMISSION或者两者都允许

FLAG_GRANT_READ_URI_PERMISSION——Intent的接受者将被准许执行read操作。

FLAG_GRANT_WRITE_URI_PERMISSION——Intent的接受者将被准许执行write操作。

 

5.示例

    /**
     * 从相机获取图片
     */
    private void getPicFromCamera() {
        //用于保存调用相机拍照后所生成的文件
        File mTempFile = new File(this.getExternalCacheDir().getPath() + "/picture/", System.currentTimeMillis() + ".png");
        if (!mTempFile.getParentFile().exists()) {
            mTempFile.getParentFile().mkdirs();
        }
        //跳转到调用系统相机
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //判断版本
        //如果在Android7.0以上,使用FileProvider获取Uri
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".FileProvider", mTempFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
        } else {    //否则使用Uri.fromFile(file)方法获取Uri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTempFile));
        }

        startActivityForResult(intent, 0);
    }
发布了63 篇原创文章 · 获赞 1 · 访问量 2101

猜你喜欢

转载自blog.csdn.net/weixin_42046829/article/details/104569057
今日推荐