Android Nougat 中通过 Intents 共享文件,你准备好了吗?

简介: 本文讲的是Android Nougat 中通过 Intents 共享文件,你准备好了吗?,确实,目前来说这个问题并不会影响很大范围的 Android 设备,但是这不仅仅是你不采用新特性的问题 —— 如果不解决,在 Nougat 设备上会崩溃,并且在以前的版本上是不安全的。
本文讲的是Android Nougat 中通过 Intents 共享文件,你准备好了吗?,

从 Android 7.0 Nougat 开始,你将不能使用 Intent 传递 file:// URI 的方式访问你主包之外的文件,但是无需苦恼:下面将介绍如何解决这个问题。

Android 7.0 Nougat 为了提高安全性引入了一些 文件系统权限变更。如果你已经将 app 的 targetSdkVersion 升级为 24 (或者更高),并且你通过 Intent 传递 file://URI 来访问你的主包之外的文件,那么你将会遇到 FileUriExposedException 的异常。

为什么会这样呢?

根据官方文档介绍:

为提高私有文件的安全性,在 Android 7.0 及以上的应用中的私有目录有着更严格的访问权限 (0700)。这个设定可以防止私有文件元数据的泄漏(比如文件的大小或者是否存在)。

当你通过 file:// URI方式共享一个文件时,你同时修改了它的文件系统权限,使得它对所有应用都是可访问的(直到你再次修改它)。毋庸置疑这种方法是不安全的。

Ok, 但是这个问题只会影响 Nougat, 那我现在还需要修复吗?

长话短说,当然需要。

确实,目前来说这个问题并不会影响很大范围的 Android 设备,但是这不仅仅是你不采用新特性的问题 —— 如果不解决,在 Nougat 设备上会崩溃,并且在以前的版本上是不安全的。而且修复这个问题并不困难,所以在你的应用发生奔溃以及你的用户开始抱怨之前,修复这个问题确实是值得的。

是时候亮代码了

最典型的例子(我也是通过它发现的这种问题),是当拍照时你给相机传递了一个文件 URI 来获取拍照后的照片。如果你想具体看看,在本文的结尾你可以找到一个 GitHub 代码库。

我们创建了一个文件,并把文件的 URI 传给了 Intent 来从相机应用接收文件(我们应用主包之外的路径)。这段代码在 Marshmallow 或更低版本上是正常的,在 Nougat、 SDK 24 版本或更高的版本,你会遇到类似下面的堆栈信息:


02-06 17:30:00.476 22265-22265/com.quiro.fileproviderexample E/AndroidRuntime: FATAL EXCEPTION: main

Process: com.quiro.fileproviderexample, PID: 22265
android.os.FileUriExposedException: file:///storage/emulated/0/Pictures/pics/JPEG_20170206_173000966174899.jpg exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.ClipData.prepareToLeaveProcess(ClipData.java:845)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8941)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8926)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4225)
at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:50)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:79)
at android.app.Activity.startActivityForResult(Activity.java:4183)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:859)
at com.quiro.fileproviderexample.MainActivity.takePicture(MainActivity.java:70)
at com.quiro.fileproviderexample.MainActivity$1.onClick(MainActivity.java:42)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)                                                                                  

解决方案 —— FileProvider

FileProvider 是 ContentProvider 的子类,FileProvider 允许我们使用 content:// URI 的方式取代 file:// 实现文件的安全共享。为什么这种方法更好?因为你为文件赋予了临时的访问权限 —— 仅仅允许接收者 activity 和 service 运行时才能访问。

首先,我们在 AndroidManifest.xml 中添加 FileProvider

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="@string/file_provider_authority"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_provider_paths" />
        </provider>
        ...
    </application>
</manifest>

我们将 android:exported 设置为禁止,因为我们不需要在其他应用使用;将 android:grantUriPermissions 设置为允许,因为这样才能给予文件临时访问权限;以及通过 android:authorities 设置管理的域。如果你的域为com.quiro.fileproviderexample,你可以使用类似 com.quiro.fileproviderexample.provider 的内容来访问。提供者的授权标识应该是唯一的,所以我们往往会使用应用的包名加上类似 .fileprovider: 的内容。

<string name="file_provider_authority" 
translatable="false">com.quiro.fileproviderexample.fileprovider</string>

接下来我们需要在 res/xml 目录下创建 file_provider_path。这个文件用来定义允许安全共享的文件目录。在我们的例子中,我们只需要访问外部存储目录:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_files" path="." />
</paths>

最后,修改我们的代码

用 FileProvider.getUriForFile(context, string, file) 的方式取代 Uri.fromFile(file) 来创建我们的 URI,FileProvider.getUriForFile(context, string, file) 会生成一个有权限访问我们所指向文件的 content://* URI。

接收者应用通过调用 ContentResolver.openFileDescriptor 来访问文件。在我们代码中 Intent 是供相机应用使用的,所以我们无需添加其他代码。






原文发布时间为:2017年2月19日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
3月前
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
|
5月前
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
409 1
|
7月前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
3月前
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
204 0
|
5月前
|
开发工具 git 索引
repo sync 更新源码 android-12.0.0_r34, fatal: 不能重置索引文件至版本 ‘v2.27^0‘。
本文描述了在更新AOSP 12源码时遇到的repo同步错误,并提供了通过手动git pull更新repo工具来解决这一问题的方法。
205 1
|
5月前
|
Android开发
android基础:Intents 和 intent-filter 的匹配规则
这篇文章详细解释了Android开发中Intent和<intent-filter>的匹配规则,包括Action、Category和Data的解析规则以及如何通过这些规则匹配隐式Intent。
167 1
|
5月前
|
存储 监控 数据库
Android经典实战之OkDownload的文件分段下载及合成原理
本文介绍了 OkDownload,一个高效的 Android 下载引擎,支持多线程下载、断点续传等功能。文章详细描述了文件分段下载及合成原理,包括任务创建、断点续传、并行下载等步骤,并展示了如何通过多种机制保证下载的稳定性和完整性。
173 0
|
7月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
7月前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
8月前
|
Android开发
【苹果安卓通用】xlsx 和 vCard 文件转换器,txt转vCard文件格式,CSV转 vCard格式,如何批量号码导入手机通讯录,一篇文章说全
本文介绍了如何快速将批量号码导入手机通讯录,适用于企业客户管理、营销团队、活动组织、团队协作和新员工入职等场景。步骤包括:1) 下载软件,提供腾讯云盘和百度网盘链接;2) 打开软件,复制粘贴号码并进行加载预览和制作文件;3) 将制作好的文件通过QQ或微信发送至手机,然后按苹果、安卓或鸿蒙系统的指示导入。整个过程简便快捷,可在1分钟内完成。
178 6

热门文章

最新文章