Android存储权限兼容性:API 21到34的适配方案

Android存储权限兼容性:API 21到34的适配方案

【免费下载链接】Seal 🦭 Video/Audio Downloader for Android, based on yt-dlp, designed with Material You 【免费下载链接】Seal 项目地址: https://gitcode.com/gh_mirrors/se/Seal

Android存储权限机制在API 21到34期间经历了重大变革,从传统的读写权限到分区存储(Scoped Storage)的强制实施,再到MANAGE_EXTERNAL_STORAGE特殊权限的引入。本文基于Seal项目的实现,详细解析各版本权限适配方案,帮助开发者解决跨版本存储访问问题。

权限机制演进概述

Android存储权限的演变可分为三个阶段:

  • 传统权限阶段(API 21-28):通过WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE控制所有外部存储访问
  • 分区存储过渡(API 29-32):引入分区存储,限制应用只能访问自身沙盒和公共媒体目录
  • 精细化权限(API 33+):拆分读写权限,增加媒体类型细分权限

权限演变

权限声明对比

API级别声明权限特殊配置
21-28WRITE_EXTERNAL_STORAGE无需额外配置
29-32WRITE_EXTERNAL_STORAGE + android:requestLegacyExternalStorage="true"临时兼容传统存储
33+READ_MEDIA_IMAGES READ_MEDIA_VIDEO READ_MEDIA_AUDIO按媒体类型申请权限

Seal项目在AndroidManifest.xml中声明了兼容配置:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<application android:requestLegacyExternalStorage="true">

运行时权限请求实现

Seal采用Jetpack Compose的rememberPermissionState API处理运行时权限请求,在DownloadPage.kt中实现:

val storagePermission = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE) { granted ->
    if (granted) {
        checkNetworkOrDownload()
    } else {
        ToastUtil.makeToast(R.string.permission_denied)
    }
}

// 权限检查逻辑
val checkPermissionOrDownload = {
    if (Build.VERSION.SDK_INT > 29 || storagePermission.status == PermissionStatus.Granted) {
        checkNetworkOrDownload()
    } else {
        storagePermission.launchPermissionRequest()
    }
}

对于API 33+设备,项目在DownloadDirectoryPreferences.kt中单独处理媒体权限:

val storagePermission = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
val showDirectoryAlert = Build.VERSION.SDK_INT >= 30 && 
    !Environment.isExternalStorageManager() &&
    (!audioDirectoryText.isValidDirectory() || !videoDirectoryText.isValidDirectory())

分区存储适配方案

文件访问路径处理

Seal通过FileUtil.kt封装了不同API级别的路径处理逻辑:

  • API < 29:直接使用Environment.getExternalStorageDirectory()
  • API 29+:使用Context.getExternalFilesDir()或媒体集合API
// 公共下载目录获取逻辑
fun getPublicDownloadDir(context: Context): File {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.path ?: "")
    } else {
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    }
}

存储访问框架(SAF)集成

对于需要访问非媒体文件或自定义目录的场景,Seal集成了存储访问框架,在DownloadDirectoryPreferences.kt中实现目录选择:

val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
    uri?.let {
        App.updateDownloadDir(uri, editingDirectory)
        val path = FileUtil.getRealPath(uri)
        when (editingDirectory) {
            Directory.AUDIO -> audioDirectoryText = path
            Directory.VIDEO -> videoDirectoryText = path
        }
    }
}

MANAGE_EXTERNAL_STORAGE特殊权限

对于需要完全访问外部存储的场景(如自定义下载目录),Seal支持申请MANAGE_EXTERNAL_STORAGE权限,在DownloadDirectoryPreferences.kt中实现:

if (Build.VERSION.SDK_INT >= 30 && !Environment.isExternalStorageManager()) {
    Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK
        data = Uri.parse("package:" + context.packageName)
        context.startActivity(this)
    }
}

权限设置界面

最佳实践总结

  1. 权限声明优化

    • 按API级别拆分权限声明,避免不必要权限
    • 使用maxSdkVersion限制旧权限作用范围
  2. 运行时权限策略

    • 采用Compose权限API实现响应式权限请求
    • 权限请求与用户操作绑定,避免启动时集中请求
  3. 存储路径管理

    • 使用FileProvider分享文件,避免直接路径访问
    • 适配代码示例:provider_paths.xml
  4. 用户体验优化

    • 权限申请前提供明确说明
    • 权限被拒后引导用户到设置页面

Seal项目通过以上适配策略,实现了从API 21到34的存储权限兼容,相关实现可参考:

应用截图

通过合理的权限管理和存储策略,Seal在保证功能完整的同时,遵循了Android系统的安全规范,为用户提供了流畅的音视频下载体验。

【免费下载链接】Seal 🦭 Video/Audio Downloader for Android, based on yt-dlp, designed with Material You 【免费下载链接】Seal 项目地址: https://gitcode.com/gh_mirrors/se/Seal

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值