Android Q(Android 10 API 29)适配指南——Scoped Storage in Android 10
从Android QAndroid 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进行介绍Scoped Storage in Android10 (Android10存储变更介绍)无需迁移到Scoped Storage模型的特例情况所有文件的访问权限的获取专属目录使用方式举例 SdCardUtil官方文档参考一、Scoped Storage in Android 10为了解决Android文件混乱的问题从Android QAndroid 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模型。过渡性解决方案 requestLegacyExternalStoragetrue对于适配难度较大的应用Android提供了一种方式可以暂时过渡性的解决方案可以让应用在Android 10API级别29及更高版本上继续保持对传统外部存储访问方式的支持。特殊权限MANAGE_EXTERNAL_STORAGE从Android RAndroid 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 10API级别29及更高版本上继续保持对传统外部存储访问方式的支持。将requestLegacyExternalStorage设置为true可以迅速适配Android 10但Google明确表示这是一个过渡性的解决方案并且在未来的Android版本中可能会移除或不再支持。因此开发者应计划逐步迁移应用至遵循Scoped Storage模型。requestLegacyExternalStoragetrue的使用方式如下manifest...applicationandroid:requestLegacyExternalStoragetrue/application/manifest2.3 特殊权限 MANAGE_EXTERNAL_STORAGE从Android RAndroid 11 API 30开始即使应用动态请求了MANAGE_EXTERNAL_STORAGE权限用户还需要手动在系统的设置中开启这一权限因为它涉及到对用户数据的广泛访问。此外由于此权限的敏感性Google Play会对申请该权限的应用进行严格审查确保应用确实有合理需求并正确处理用户数据。MANAGE_EXTERNAL_STORAGE权限的使用方式如下// 权限配置uses-permission android:nameandroid.permission.MANAGE_EXTERNAL_STORAGE/// 版本为Android 11if(Build.VERSION.SDK_INTBuild.VERSION_CODES.R){// 先判断有没有权限if(Environment.isExternalStorageManager()){// 拥有存储权限}else{// 跳转页面请求权限IntentintentnewIntent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);intent.setData(Uri.parse(package:getPackageName()));startActivityForResult(intent,REQUEST_STORAGE_CODE);}}三、 专属目录使用方式Android应用专属存储目录的读写根据官方描述无需申请Manifest.permission.WRITE_EXTERNAL_STORAGE、Manifest.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){FileexternalDircontext.getExternalFilesDir(dir);StringfilePath;// 先判断外部存储是否可用if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())externalDir!null){filePathexternalDir.getAbsolutePath();}// 外部不可用则使用内部存储else{filePathcontext.getFilesDir()(TextUtils.isEmpty(dir)?:File.separatordir);}// 添加文件名if(!TextUtils.isEmpty(fileName)){filePathfilePathFile.separatorfileName;}// 确保目录存在FiletargetnewFile(filePath);if(TextUtils.isEmpty(fileName)){target.mkdirs();}else{target.getParentFile().mkdirs();}returnfilePath;}/** * 获取应用私有cache目录 * p * /sdcard/Android/data/包名/cache */publicstaticStringgetPrivateCachePath(Contextcontext){Filefilecontext.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-storageManage scoped external storage accesshttps://developer.android.com/training/data-storage/files/external-scopedrequestLegacyExternalStoragetrue官方描述https://developer.android.google.cn/training/data-storage/use-casesMANAGE_EXTERNAL_STORAGE权限官方描述https://developer.android.google.cn/training/data-storage/manage-all-files