Android FileProvider实战:从配置到Intent分享,打通应用间文件共享

Android FileProvider实战:从配置到Intent分享,打通应用间文件共享
1. 为什么需要FileProvider在Android开发中应用间文件共享是个常见需求。比如你想让用户把应用内的图片分享到社交媒体或者用其他应用打开你生成的PDF文件。但在Android 7.0API 24之后直接使用file://URI分享文件会抛出FileUriExposedException异常。这是因为Android引入了更严格的文件权限管理。以前我们习惯用Uri.fromFile()生成文件URI现在这条路行不通了。FileProvider就是Google提供的解决方案它通过content://URI机制实现安全的文件共享。我去年就踩过这个坑。当时客户反馈分享功能在Android 10上崩溃查了半天才发现是没适配FileProvider。后来花了两天时间重构代码才把这个问题彻底解决。2. 配置FileProvider基础环境2.1 修改AndroidManifest.xml首先要在manifest中声明FileProvider。注意这个provider标签必须放在application标签内部application ... provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider /application这里有几个关键参数需要注意authorities建议使用应用包名加后缀如.fileprovider确保全局唯一exported设为false表示不对外公开grantUriPermissions必须为true才能授权临时访问2.2 创建路径配置文件在res/xml目录下新建file_paths.xml没有xml目录就手动创建?xml version1.0 encodingutf-8? paths xmlns:androidhttp://schemas.android.com/apk/res/android !-- 对应Context.getFilesDir() -- files-path nameinternal_files path. / !-- 对应Context.getCacheDir() -- cache-path nameinternal_cache path. / !-- 对应Environment.getExternalStorageDirectory() -- external-path nameexternal_storage path. / !-- 对应Context.getExternalFilesDir(null) -- external-files-path nameexternal_app_files path. / !-- 对应Context.getExternalCacheDir() -- external-cache-path nameexternal_app_cache path. / /paths这个配置文件定义了哪些目录可以被共享。每个标签的path属性支持子目录设置比如pathdocuments/表示只共享该子目录。3. 实战文件分享流程3.1 生成Content URI假设我们要分享SD卡根目录下的test.pdf文件File file new File(Environment.getExternalStorageDirectory(), test.pdf); Uri contentUri FileProvider.getUriForFile( context, context.getPackageName() .fileprovider, file );生成的URI格式会是content://com.example.app.fileprovider/external_storage/test.pdf这里有个常见坑点getUriForFile()的第二个参数必须和manifest中定义的authorities完全一致。我建议直接用context.getPackageName().fileprovider来避免拼写错误。3.2 构建分享IntentIntent shareIntent new Intent(Intent.ACTION_SEND); shareIntent.setType(application/pdf); // 关键添加临时读写权限 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); startActivity(Intent.createChooser(shareIntent, 分享文件));特别注意FLAG_GRANT_READ_URI_PERMISSION这个flag没有它接收方会报权限错误。如果需要写权限可以加上FLAG_GRANT_WRITE_URI_PERMISSION。4. 高级使用技巧4.1 处理接收文件请求如果你的应用需要接收其他应用分享的文件需要在manifest中配置activity android:name.ReceiveFileActivity intent-filter action android:nameandroid.intent.action.SEND / category android:nameandroid.intent.category.DEFAULT / data android:mimeType*/* / /intent-filter /activity在Activity中获取文件内容Uri receivedUri getIntent().getParcelableExtra(Intent.EXTRA_STREAM); try (InputStream inputStream getContentResolver().openInputStream(receivedUri)) { // 处理文件流 } catch (IOException e) { e.printStackTrace(); }4.2 多文件目录管理对于需要共享多个目录的情况可以在file_paths.xml中配置多个路径paths files-path namereports pathreports/ / external-files-path namedownloads pathDownloads/ / cache-path nametemp_files pathtemp/ / /paths使用时通过不同的name前缀区分// 分享内部存储的reports目录文件 Uri reportUri FileProvider.getUriForFile( context, authorities, new File(context.getFilesDir(), reports/report1.pdf) ); // 分享下载目录文件 Uri downloadUri FileProvider.getUriForFile( context, authorities, new File(context.getExternalFilesDir(Downloads), file.zip) );5. 常见问题排查问题1FileNotFoundException可能原因文件路径不在file_paths.xml配置的目录中文件实际不存在没有正确配置meta-data标签问题2SecurityException检查点是否漏加了FLAG_GRANT_READ_URI_PERMISSIONauthorities字符串是否完全匹配exported是否设置为false问题3ContentResolver查询失败如果接收方需要查询文件信息可以这样获取ContentResolver resolver context.getContentResolver(); String displayName null; try (Cursor cursor resolver.query(uri, null, null, null, null)) { if (cursor ! null cursor.moveToFirst()) { displayName cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) ); } }最后分享一个实用技巧在开发阶段可以通过adb shell content命令查看FileProvider生成的URI对应的真实路径这对调试非常有帮助。