当前位置: 首页 > news >正文

Android Q(Android 10 API 29)适配指南——Scoped Storage in Android 10

从Android Q(Android 10 API 29)开始,即便应用请求了WRITE_EXTERNAL_STORAGE权限,其对全局外部存储的访问也受到限制,鼓励开发者采用Scoped Storage的新规范来保护用户隐私和数据安全如果应用需要更广泛的访问权限,需要请求MANAGE_EXTERNAL_STORAGE权限来访问用户选择的文件和目录,由于此权限的强大访问能力,Google Play会对申请使用该权限的应用进行严格审查,确保应用的用途正当且必要。

默认情况下targetSdk大于等于29的应用仅可:

  • 通过Context.getExternalFilesDir("type")Context.getExternalCacheDir()访问应用专属存储目录;
  • 通过申请READ_EXTERNAL_STORAGE权限,使用MediaStoreAPI,访问共享媒体目录(如Photos、Screenshots等目录)。
  • 通过API Storage Access Framework读取手机的Downloads文件夹,不需要任何权限

本文通过以下几个方面,对Scoped Storage in Android10进行介绍:

  1. Scoped Storage in Android10 (Android10存储变更介绍)
  2. 无需迁移到Scoped Storage模型的特例情况(所有文件的访问权限的获取)
  3. 专属目录使用方式举例 SdCardUtil
  4. 官方文档参考

一、Scoped Storage in Android 10


为了解决Android文件混乱的问题,从Android Q(Android 10 API 29)开始,Android对外部存储进行一定的限制。

  • 通过Context.getExternalFilesDir("type")Context.getExternalCacheDir()访问应用专属存储目录;
  • 通过申请READ_EXTERNAL_STORAGE权限,使用MediaStoreAPI,访问共享媒体目录(如Photos、Screenshots等目录)。
  • 通过API Storage Access Framework读取手机的Downloads文件夹,不需要任何权限

1.1 应用专属路径

通过Context.getExternalFilesDir("type")Context.getExternalCacheDir()访问应用专属存储目录,无需任何权限

  • 内部存储路径/data/data/<包名>/
  • 外部存储路径/storage/Android/data/<包名>/

1.2 手机共享路径

通过申请READ_EXTERNAL_STORAGE权限,使用MediaStoreAPI,访问共享媒体目录(如Photos、Screenshots等目录):

  • 图片:Photos、Screenshots 使用MediaStore.Images API访问
  • 视频:使用MediaStore.Video API访问
  • 音频:使用 MediaStore.Audio API访问

1.3 Downloads文件夹

读取手机的Downloads文件夹,不需要任何权限,需要使用API Storage Access Framework

二、所有文件的访问权限

从Android10版本升级开始,发现我自己使用的Android手机,很多应用仍然可以访问非应用专属目录文件,因此总结了几种特例情况,来解答自己的心里疑问。
一般来说有以下几种情况的应用,仍然可以继续访问非应用专属目录文件:

  • 低Api版本的兼容模式:如果应用的targetSdk低于29在Android 10上运行,那么该应用可以暂时继续使用旧的存储访问方式,但建议对应的开发者尽快迁移到Scoped Storage模型。
  • 过渡性解决方案 requestLegacyExternalStorage="true":对于适配难度较大的应用,Android提供了一种方式可以暂时过渡性的解决方案,可以让应用在Android 10(API级别29)及更高版本上继续保持对传统外部存储访问方式的支持
  • 特殊权限MANAGE_EXTERNAL_STORAGE:从Android R(Android 11 API 30)开始,对于需要访问外部存储上非自身创建文件的应用,可以申请MANAGE_EXTERNAL_STORAGE权限,但该权限需要用户的明确授权,并且由于此权限的强大访问能力,Google Play会对申请使用该权限的应用进行严格审查,确保应用的用途正当且必。

2.1 targetSdk低于29的低版本兼容模式

如果应用的targetSdk低于29在Android 10上运行,那么该应用可以暂时继续使用旧的存储访问方式,但建议对应的开发者尽快迁移到Scoped Storage模型。

2.2 过渡性解决方案 requestLegacyExternalStorage=“true”

对于适配难度较大的应用,Android提供了一种方式可以暂时过渡性的解决方案
在androidmanifest配置文件中将requestLegacyExternalStorage设置为true,可以让应用在Android 10(API级别29)及更高版本上继续保持对传统外部存储访问方式的支持
requestLegacyExternalStorage设置为true,可以迅速适配Android 10,但Google明确表示这是一个过渡性的解决方案,并且在未来的Android版本中可能会移除或不再支持。因此,开发者应计划逐步迁移应用至遵循Scoped Storage模型。

requestLegacyExternalStorage="true"的使用方式如下:

<manifest...><applicationandroid:requestLegacyExternalStorage="true"></application></manifest>

2.3 特殊权限 MANAGE_EXTERNAL_STORAGE

Android R(Android 11 API 30)开始,即使应用动态请求了MANAGE_EXTERNAL_STORAGE权限,用户还需要手动在系统的设置中开启这一权限,因为它涉及到对用户数据的广泛访问。此外,由于此权限的敏感性,Google Play会对申请该权限的应用进行严格审查,确保应用确实有合理需求并正确处理用户数据。

MANAGE_EXTERNAL_STORAGE权限的使用方式如下:

// 权限配置<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>// 版本为Android 11if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){// 先判断有没有权限if(Environment.isExternalStorageManager()){// 拥有存储权限}else{// 跳转页面,请求权限Intentintent=newIntent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);intent.setData(Uri.parse("package:"+getPackageName()));startActivityForResult(intent,REQUEST_STORAGE_CODE);}}

三、 专属目录使用方式

Android应用专属存储目录的读写,根据官方描述,无需申请Manifest.permission.WRITE_EXTERNAL_STORAGEManifest.permission.READ_EXTERNAL_STORAGE等相关权限,可直接读写,相关描述如下:

https://developer.android.com/reference/android/content/Context.html#getExternalCacheDir()

其使用方式如下所示:

importandroid.content.Context;importandroid.os.Environment;importandroid.text.TextUtils;importjava.io.File;/** * 内部存储 * /data/data/包名/files * context.getFilesDir().getPath() * /data/data/包名/cache * context.getCacheDir().getPath() * <p> * 外部存储 * /sdcard/Android/data/包名/file/dir * context.getExternalFilesDir("dir").getPath() * /sdcard/Android/data/包名/cache * context.getExternalCacheDir().getPath() */publicclassSdCardUtil{/** * 获取应用私有file目录 * <p> * /sdcard/Android/data/包名/file/dirorfile * * @param dir The type of files directory to return. May be {@code null} * for the root of the files directory or one of the following * constants for a subdirectory: * {@link android.os.Environment#DIRECTORY_MUSIC}, * {@link android.os.Environment#DIRECTORY_PODCASTS}, * {@link android.os.Environment#DIRECTORY_RINGTONES}, * {@link android.os.Environment#DIRECTORY_ALARMS}, * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS}, * {@link android.os.Environment#DIRECTORY_PICTURES}, or * {@link android.os.Environment#DIRECTORY_MOVIES}. */publicstaticStringgetPrivateFilePath(Contextcontext,Stringdir){returngetPrivateFilePath(context,dir,"");}publicstaticStringgetPrivateFilePath(Contextcontext,Stringdir,StringfileName){FileexternalDir=context.getExternalFilesDir(dir);StringfilePath;// 先判断外部存储是否可用if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())&&externalDir!=null){filePath=externalDir.getAbsolutePath();}// 外部不可用则使用内部存储else{filePath=context.getFilesDir()+(TextUtils.isEmpty(dir)?"":File.separator+dir);}// 添加文件名if(!TextUtils.isEmpty(fileName)){filePath=filePath+File.separator+fileName;}// 确保目录存在Filetarget=newFile(filePath);if(TextUtils.isEmpty(fileName)){target.mkdirs();}else{target.getParentFile().mkdirs();}returnfilePath;}/** * 获取应用私有cache目录 * <p> * /sdcard/Android/data/包名/cache */publicstaticStringgetPrivateCachePath(Contextcontext){Filefile=context.getExternalCacheDir();//先判断外部存储是否可用if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())&&file!=null){returnfile.getAbsolutePath();}else{returncontext.getCacheDir().getAbsolutePath();}}}

四、官方参考:

外部存储访问权限范围限定为应用文件和媒体:
https://developer.android.com/about/versions/10/privacy/changes#scoped-storage

Manage scoped external storage access:
https://developer.android.com/training/data-storage/files/external-scoped

requestLegacyExternalStorage="true"官方描述:
https://developer.android.google.cn/training/data-storage/use-cases

MANAGE_EXTERNAL_STORAGE权限官方描述:
https://developer.android.google.cn/training/data-storage/manage-all-files

http://www.jsqmd.com/news/813471/

相关文章:

  • 基于Wasserstein距离的仿真到现实迁移优化技术解析
  • 用HFSS Floquet Port仿真无限大阵列:从单元设计到S参数提取全流程解析
  • Java内存血缘追踪工具memlineage:定位内存泄漏的利器
  • 【Pixel专属Gemini Edge推理引擎】:本地运行LLM不联网、零延迟、功耗降低47%——实测数据首次公开
  • AI开发代码菜谱:从数据预处理到模型部署的实战指南
  • S32K3 FlexCAN实战:从MCAL配置到DMA接收,手把手教你避开那些手册里没写的坑
  • 从零掌握生成式AI:开源学习路径与实战项目全解析
  • 一人独立交付 UI + 前端:AI 驱动 UI 设计工具的五大功能模块深度评测
  • 第4章:C++ 对象生命周期
  • P1238 走迷宫【洛谷算法习题】
  • 别再搞混了!用Python和NumPy手把手教你从旋转矩阵解算Yaw/Pitch/Roll(附避坑指南)
  • TangleClaw v3:基于tmux的本地AI编码会话持久化与编排平台
  • 移动端应用集成AI能力时如何通过Taotoken实现成本可控与稳定调用
  • Linux 7.6 环境下 InterSystems Caché 数据库的部署与核心配置实战
  • 基于RAG与n8n工作流构建PDF智能问答AI聊天应用全栈实践
  • 一次断电引发的血案:深度复盘CentOS 7 LVM分区下fstab丢失的排查与修复全记录
  • ARM PL192 VIC中断控制器架构与驱动开发详解
  • 别再只用Umeyama了!手把手教你用Horn四元数搞定点云对齐(附Python代码)
  • python系列【仅供参考】:Pycharm 给 python 程序打包EXE的配置和方法
  • Dev Containers实战:容器化开发环境配置与团队协作指南
  • 如何快速掌握AMD锐龙性能调优:SMUDebugTool完全指南
  • FinBERT vs 通用BERT:在金融新闻分类任务上,到底能提升多少?
  • 3步搞定Windows安装安卓应用:APK Installer免费工具终极指南
  • Unity 2D横版闯关游戏:从零到一构建像素风丛林冒险
  • 【模板】最近公共祖先(LCA)【牛客tracker 每日一题】
  • Kotlin Multiplatform (KMP) 跨端改造实战:聚焦性能与功耗优化的深度解析
  • Windows系统下PyTorch三维处理利器Kaolin的安装与配置全攻略
  • 深度优化之道:Android应用性能与功耗优化实战指南
  • TimeGen3.2实战指南:从零绘制专业硬件时序图
  • 自托管AI工作空间Llama Workspace:企业级部署与核心架构解析