android 7.0 FileProvider

android 7.0及以上版本,应用私有目录被限制访问。此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。此权限更改有多重副作用:比如 ,file:///URI可能给接收器留下无法访问的路径,推荐使用FileProvider。

FileProvider介绍

FileProvider是ContentProvider的一个特殊子类,通过content://代替file:///,有助于安全地共享与应用程序相关联的文件。

Content URI授予读写访问权限用来允许您临时访问文件。当您创建包含内容URI的Intent时,为了将内容URI发送到客户端应用程序,还可以调用Intent.setFlags() 来添加权限。只要在堆栈中活跃的activity,这些权限就可用于客户端app。对于服务上的Intent,只要服务一直在运行,权限就处于可用状态。

相比之下,为了控制对文件的访问:feil:///URI,您必须修改底层文件的文件系统权限。您提供的权限对任何应用程序都可用,并在您更改它们之前保持有效。这种访问级别根本上是不安全的。

ContentURI 是提高android文件系统安全性的关键部分。

FileProvider程序包括以下内容:

  1. 定义FileProvider
  2. 指定可用文件
  3. 检索文件的Content URI
  4. 授予URI临时访问权限
  5. 将ContentURI提供给其他应用程序

定义FileProvider

由于FileProvider默认功能里包含了ContentURI的生成,所以不需要我们去定义。相反,您可以在XML中指定可用文件。要指定FileProvider组件本身,请将<provider>元素添加到您的应用清单。将android:name属性设置为android.support.v4.content.FileProvider。根据您控制的域将android:authority属性设置为URI权限;例如,如果您控制域mydomain.com,则应使用授权com.mydomain.fileprovider。将android:exported属性设置为false; FileProvider不需要公开。 将android:grantUriPermissions属性设置为true,以允许您授予对文件的临时访问权限。

 例如:

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.mydomain.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>

如果要覆盖FileProvider方法的任何默认行为,请扩展FileProvider类并在<provider>元素的android:name属性中使用完全限定的类名称。

指定可用文件

FileProvider只能为您事先指定的目录中的文件生成内容URI。 要指定目录,请使用<paths>元素的子元素指定其存储区域和XML路径。 例如,以下路径元素告诉FileProvider您打算请求私有文件区域的images /子目录的内容URI。

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>

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

<files-path name="name" path="path" />

代表应用程序内部存储区的files /子目录中的文件。 该子目录与Context.getFilesDir()返回的值相同。路径:eg:”/data/data/包名/files”。

<cache-path name="name" path="path" />

代表应用内部存储区的缓存子目录中的文件。 此子目录的根路径与getCacheDir()返回的值相同。路径:eg:“/data/data/包名/cache”。

<external-path name="name" path="path" />

代表外部存储区域根目录中的文件。 此子目录的根路径与Environment.getExternalStorageDirectory()返回的值相同。路径:eg:”/storage/emulated/0”。

<external-files-path name="name" path="path" />

代表应用外部存储区域根目录中的文件。 此子目录的根路径与Context#getExternalFilesDir(String)Context.getExternalFilesDir(null)返回的值相同。路径。eg:”/storage/emulated/0/Android/data/包名/files”。

<external-cache-path name="name" path="path" />

代表应用外部缓存区域根目录中的文件。 此子目录的根路径与Context.getExternalCacheDir()返回的值相同。路径。eg:”/storage/emulated/0/Android/data/包名/cache”。

<external-media-path name="name" path="path" />

代表应用外部媒体区域根目录中的文件。 此子目录的根路径与Context.getExternalMediaDirs()的第一个结果返回的值相同。

注意:此目录仅适用于API 21+设备。

这些子元素都使用相同的属性:

name="name"

一个URI路径段。 为了强制执行安全性,此值将隐藏您要共享的子目录的名称。 该值的子目录名称包含在路径属性中。

path="path"

你正在分享的子目录。 虽然name属性是一个URI路径段,但路径值是一个实际的子目录名称。 请注意,该值是指一个子目录,而不是单个文件或文件。 您无法通过文件名共享单个文件,也无法使用通配符指定文件的子集。

您必须为每个包含您需要内容URI的文件的目录指定<路径>的子元素。 

例如,这些XML元素指定两个目录:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    <files-path name="my_docs" path="docs/"/>
</paths>

将<paths>元素及其子元素放入项目中的XML文件中。 例如,您可以将它们添加到名为res / xml / file_paths.xml的新文件。 要将此文件链接到FileProvider,请将<meta-data>元素添加为定义FileProvider的<provider>元素的子元素。 将<meta-data>元素的“android:name”属性设置为android.support.FILE_PROVIDER_PATHS。 将元素的“android:resource”属性设置为@ xml / file_paths(请注意,您不指定.xml扩展名)。

例如,

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

为文件生成ContentURI

要使用内容URI与另一个应用程序共享文件,您的应用程序必须生成内容URI。 要生成内容URI,请为该文件创建一个新文件,然后将该文件传递给getUriForFile()。 您可以将getUriForFile()返回的内容URI发送到意图中的另一个应用程序。 接收内容URI的客户端应用程序可以通过调用ContentResolver.openFileDescriptor来获取ParcelFileDescriptor来打开文件并访问其内容。

例如,假设您的应用使用具有com.mydomain.fileprovider权限的FileProvider将文件提供给其他应用。 要在内部存储器的images /子目录中获取文件default_image.jpg的内容URI,请添加以下代码:

File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

由于前面的代码片段,getUriForFile()返回内容URI的内容://com.mydomain.fileprovider/my_images/default_image.jpg

授予URI临时权限

要为从getUriForFile()返回的内容URI授予访问权限,请执行以下操作:

  • 调用方法Context.grantUriPermission(package,Uri,mode_flags)作为content:// Uri,使用所需的模式标志。 这将根据可以设置为FLAG_GRANT_READ_URI_PERMISSION,FLAG_GRANT_WRITE_URI_PERMISSION或两者的mode_flags参数的值将内容URI的临时访问权限授予指定的包。 直到您通过调用revokeUriPermission()或直到设备重新启动为止,权限才会生效。
  • 通过调用setData()将ContentURI放入Intent中
  • 接下来,使用FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION或两者调用方法Intent.setFlags()。
  • 最后,将Intent发送到另一个应用程序。 通常,通过调用setResult()来做到这一点。

将ContentURI提供给其他app

有多种方式可以将文件的内容URI提供给客户端应用程序。 一种常见的方式是让客户端应用程序通过调用startActivityResult()来启动您的应用程序,该应用程序将Intent发送到您的应用程序以在您的应用程序中启动一个Activity。 作为响应,您的应用程序可以立即将内容URI返回给客户端应用程序,或者呈现允许用户选择文件的用户界面。 在后一种情况下,一旦用户选择文件,您的应用可以返回其内容URI。 在这两种情况下,您的应用都会通过setResult()发送Intent中的内容URI。

您也可以将内容URI放入ClipData对象中,然后将该对象添加到您发送给客户端应用程序的Intent中。 为此,请调用Intent.setClipData()。 当您使用这种方法时,您可以将多个ClipData对象添加到Intent,每个对象都有自己的内容URI。 当您调用Intent上的Intent.setFlags()以设置临时访问权限时,相同的权限将应用于所有内容URI。

注意:Intent.setClipData()方法仅在平台版本16(Android 4.1)及更高版本中可用。 如果您希望保持与以前版本的兼容性,您应该一次在意图中发送一个内容URI。 将操作设置为ACTION_SEND,并通过调用setData()将URI放入数据中。

更多

要了解有关FileProvider的更多信息,请参阅使用URI安全的共享文件

参考文档

https://developer.android.com/reference/android/support/v4/content/FileProvider


猜你喜欢

转载自blog.csdn.net/yx1166/article/details/80448884