简介:Android 11引入了存储访问框架(SAF)和分区存储机制,显著改变了文件共享的实现方式。本文详细解析了Android 11后文件共享的核心机制、权限配置及三种主流实现方案,帮助开发者高效适配新系统要求。
Android 11(API 30)引入了存储访问框架(Storage Access Framework, SAF)和分区存储(Scoped Storage)机制,彻底改变了传统文件共享的实现方式。开发者需重新设计文件访问逻辑,以适配系统对隐私保护的强化要求。本文将从权限配置、技术方案、代码实现三个维度,系统解析Android 11后文件共享的核心实现路径。
Android 11默认启用分区存储,应用仅能直接访问自身应用专属目录(/Android/data/<package_name>)和特定类型的共享文件(如照片、视频、下载文件)。访问其他应用的文件或外部存储的非媒体文件需通过存储访问框架(SAF)或MediaStore API。
影响分析:
Environment.getExternalStorageDirectory()方法失效READ_EXTERNAL_STORAGE仅允许访问媒体文件| 权限类型 | 适用场景 | 保护级别 |
|---|---|---|
MANAGE_EXTERNAL_STORAGE |
全盘文件管理(如文件管理器) | 特殊权限(需用户手动授权) |
READ_EXTERNAL_STORAGE |
访问媒体文件(照片、视频) | 危险权限 |
WRITE_EXTERNAL_STORAGE |
Android 11起废弃,改用MediaStore | 已废弃 |
关键点:申请MANAGE_EXTERNAL_STORAGE需在Play Store提交权限使用声明,且需通过ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION引导用户授权。
适用场景:共享图片、视频、音频等媒体文件。
// 插入图片到MediaStoreval resolver = context.contentResolverval contentValues = ContentValues().apply {put(MediaStore.Images.Media.DISPLAY_NAME, "shared_image.jpg")put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)}val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)uri?.let {resolver.openOutputStream(it)?.use { outputStream ->// 写入文件数据Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888).compress(Bitmap.CompressFormat.JPEG, 100, outputStream)}}
// 通过Intent共享媒体文件val intent = Intent(Intent.ACTION_SEND).apply {type = "image/*"putExtra(Intent.EXTRA_STREAM, uri) // uri为MediaStore返回的URI}startActivity(Intent.createChooser(intent, "Share via"))
优势:
限制:
适用场景:共享任意类型文件,需用户交互选择。
// 启动文件选择器val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {addCategory(Intent.CATEGORY_OPENABLE)type = "application/pdf" // 指定文件类型putExtra(Intent.EXTRA_TITLE, "shared_document.pdf")}startActivityForResult(intent, REQUEST_CODE_CREATE_FILE)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_CODE_CREATE_FILE && resultCode == RESULT_OK) {data?.data?.let { uri ->// 获取持久化权限contentResolver.takePersistableUriPermission(uri,Intent.FLAG_GRANT_READ_URI_PERMISSION orIntent.FLAG_GRANT_WRITE_URI_PERMISSION)// 写入文件内容contentResolver.openOutputStream(uri)?.use { outputStream ->outputStream.write("Hello SAF".toByteArray())}}}}
优势:
限制:
适用场景:应用间共享私有文件,无需用户交互。
// 获取外部存储应用专属目录val externalDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)val sharedFile = File(externalDir, "shared_data.txt").apply {writeText("This is shared content")}// 通过FileProvider共享val contentUri = FileProvider.getUriForFile(context,"${context.packageName}.fileprovider",sharedFile)
AndroidManifest.xml:
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider>
res/xml/file_paths.xml:
<paths><external-files-path name="documents" path="Documents/" /></paths>
val shareIntent = Intent(Intent.ACTION_SEND).apply {type = "text/plain"putExtra(Intent.EXTRA_STREAM, contentUri)addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)}startActivity(Intent.createChooser(shareIntent, "Share file"))
优势:
限制:
// 检查并申请MANAGE_EXTERNAL_STORAGE权限fun checkStoragePermission(context: Context): Boolean {return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {Environment.isExternalStorageManager()} else {context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) ==PackageManager.PERMISSION_GRANTED}}// 引导用户授权全盘访问fun requestFullStorageAccess(activity: Activity) {val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)activity.startActivity(intent)}
Intent.FLAG_GRANT_READ_URI_PERMISSION/WRITE_URI_PERMISSION临时授权takePersistableUriPermission()持久化SAF返回的URI权限onDestroy()中释放不再需要的URI权限
// 兼容不同Android版本的文件访问fun getSharedFileUri(context: Context, fileName: String): Uri? {return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {// Android 10+使用MediaStore或SAFval resolver = context.contentResolverval contentValues = ContentValues().apply {put(MediaStore.Downloads.DISPLAY_NAME, fileName)put(MediaStore.Downloads.MIME_TYPE, "application/octet-stream")}resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)} else {// Android 9及以下使用FileProviderval file = File(context.getExternalFilesDir(null), fileName)FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)}}
MediaStore的批量插入API减少IO次数BufferedInputStream/BufferedOutputStreamIntentService或WorkManager中执行Android 11的文件共享机制通过分区存储和SAF框架,在保障用户隐私的同时提供了灵活的文件访问方式。开发者应根据具体场景选择合适的技术方案:
未来Android版本可能进一步强化隐私保护,建议持续关注Android存储更新文档,及时调整实现策略。通过合理设计文件共享架构,既能满足功能需求,也能符合平台的安全规范。