Android 10-11适配外部存储方案
Android Api 29 对文件和文件夹进行了重大更改。不允许使用外部存储,如下方法:
Environment.getExternalStorageDirectory() = /mnt/sdcard
Environment.getExternalStoragePublicDirectory(“test”) = /mnt/sdcard/test

只能使用内部存储
getExternalFilesDir(“test”) = /mnt/sdcard/Android/data/com.my.app/files/test
getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files

但谷歌官方给了一个后门,在AndroidManifest.xml文件中application节点中加上android:requestLegacyExternalStorage="true"属性才可以访问沙盒路径下的数据

原来的项目就要重新适配

Android开发:filePath放在哪个文件夹
Environment.getDataDirectory() = /data
Environment.getDownloadCacheDirectory() = /cache
外部存储
Environment.getExternalStorageDirectory() = /mnt/sdcard
Environment.getExternalStoragePublicDirectory(“test”) = /mnt/sdcard/test
Environment.getRootDirectory() = /system
getPackageCodePath() = /data/app/com.my.app-1.apk
getPackageResourcePath() = /data/app/com.my.app-1.apk
getCacheDir() = /data/data/com.my.app/cache
getDatabasePath(“test”) = /data/data/com.my.app/databases/test
getDir(“test”, Context.MODE_PRIVATE) = /data/data/com.my.app/app_test
getExternalCacheDir() = /mnt/sdcard/Android/data/com.my.app/cache
应用内部存储
getExternalFilesDir(“test”) = /mnt/sdcard/Android/data/com.my.app/files/test
getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files
getFilesDir() = /data/data/com.my.app/files
参考文章:
安卓Enviroment类的详解-CSDN博客
权限申请
11以上添加如下权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
代码动态申请
public final class PermissionActivity extends AppCompatActivity {private static final int REQUEST_CODE = 1024;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestPermission();}private void requestPermission() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {// 先判断有没有权限if (Environment.isExternalStorageManager()) {writeFile();} else {Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);intent.setData(Uri.parse("package:" + context.getPackageName()));startActivityForResult(intent, REQUEST_CODE);}} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 先判断有没有权限if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {writeFile();} else {ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);}} else {writeFile();}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_CODE) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {writeFile();} else {ToastUtils.show("存储权限获取失败");}}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (Environment.isExternalStorageManager()) {writeFile();} else {ToastUtils.show("存储权限获取失败");}}}/*** 模拟文件写入*/private void writeFile() {ToastUtils.show("写入文件成功");}
}
动态申请代码三方框架XXPermissions
XXPermissions.with(this)// 不适配 Android 11 可以这样写//.permission(Permission.Group.STORAGE)// 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE.permission(Permission.MANAGE_EXTERNAL_STORAGE).request(new OnPermissionCallback() {@Overridepublic void onGranted(List<String> permissions, boolean all) {if (all) {toast("获取存储权限成功");}}@Overridepublic void onDenied(List<String> permissions, boolean never) {if (never) {toast("被永久拒绝授权,请手动授予存储权限");// 如果是被永久拒绝就跳转到应用权限系统设置页面XXPermissions.startPermissionActivity(MainActivity.this, permissions);} else {toast("获取存储权限失败");}}});
项目地址:GitHub - getActivity/XXPermissions: Android 权限请求框架,已适配 Android 14

// JitPack 远程仓库:https://jitpack.iomaven { url 'https://jitpack.io' }
// 权限请求框架:https://github.com/getActivity/XXPermissionsimplementation 'com.github.getActivity:XXPermissions:18.5

# 表示将第三方库迁移到 AndroidX
android.enableJetifier = true
<manifest>
<application>
<!-- 告知 XXPermissions 当前项目已经适配了分区存储特性 -->
<meta-data
android:name="ScopedStorage"
android:value="true" />
</application>
</manifest>
框架扩展
XXPermissions.with(this)// 申请单个权限.permission(Permission.RECORD_AUDIO)// 申请多个权限.permission(Permission.Group.CALENDAR)// 设置权限请求拦截器(局部设置)//.interceptor(new PermissionInterceptor())// 设置不触发错误检测机制(局部设置)//.unchecked().request(new OnPermissionCallback() {@Overridepublic void onGranted(@NonNull List<String> permissions, boolean allGranted) {if (!allGranted) {toast("获取部分权限成功,但部分权限未正常授予");return;}toast("获取录音和日历权限成功");}@Overridepublic void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {if (doNotAskAgain) {toast("被永久拒绝授权,请手动授予录音和日历权限");// 如果是被永久拒绝就跳转到应用权限系统设置页面XXPermissions.startPermissionActivity(context, permissions);} else {toast("获取录音和日历权限失败");}}});
其他API介绍
// 判断一个或多个权限是否全部授予了
XXPermissions.isGranted(Context context, String... permissions);// 获取没有授予的权限
XXPermissions.getDenied(Context context, String... permissions);// 判断某个权限是否为特殊权限
XXPermissions.isSpecial(String permission);// 判断一个或多个权限是否被勾选了《不再询问》的选项(一定要在权限申请的回调方法中调用才有效果)
XXPermissions.isDoNotAskAgainPermissions(Activity activity, String... permissions);// 跳转到应用权限设置页
XXPermissions.startPermissionActivity(Context context, String... permissions);
XXPermissions.startPermissionActivity(Activity activity, String... permissions);
XXPermissions.startPermissionActivity(Activity activity, String... permission, OnPermissionPageCallback callback);
XXPermissions.startPermissionActivity(Fragment fragment, String... permissions);
XXPermissions.startPermissionActivity(Fragment fragment, String... permissions, OnPermissionPageCallback callback);// 设置不触发错误检测机制(全局设置)
XXPermissions.setCheckMode(false);
// 设置权限申请拦截器(全局设置)
XXPermissions.setInterceptor(new IPermissionInterceptor() {});
相关文章:
Android 10-11适配外部存储方案
Android Api 29 对文件和文件夹进行了重大更改。不允许使用外部存储,如下方法: Environment.getExternalStorageDirectory() /mnt/sdcard Environment.getExternalStoragePublicDirectory(“test”) /mnt/sdcard/test 只能使用内部存储 getExterna…...
软件测试/测试开发丨Python:易学、强大、多用途的编程语言
点此获取更多相关资料 Python 发展历史 Python 是一门高级编程语言,由 Guido van Rossum(龟叔) 在 1989 年发明,设计 Python 语言的初衷是为了创造一种介于 C 和 shell 之间,简洁方便,易学易用࿰…...
一、VPN基础
VPN基础 1、定义及特征2、VPN优势3、VPN分类4、VPN体系结构5、VPN实现的模式 —————————————————————————————————————————————————— 1、定义及特征 虚拟专用网VPN是依靠Internet服务提供商ISP和网络服务提供商NSP在公共网…...
淘宝协议最新版
我可以为您提供一些示例代码,以演示一些与电商平台相关的功能。请注意,以下代码仅为示例,具体实现还需要根据您的应用程序的架构、技术栈和需求进行调整和扩展。 1. 用户注册功能: - 后端实现:在后端,您可…...
AI“走深向实”,蚂蚁蚁盾在云栖大会发布实体产业「知识交互建模引擎」
数字化起步晚、数据分散稀疏、专业壁垒高、行业知识依赖「老师傅」,是很多传统产业智能化发展面临的难题。2023年云栖大会上,蚂蚁集团安全科技品牌蚁盾发布“知识交互建模引擎”,将实体产业知识与AI模型有机结合,助力企业最快10分…...
如何估计池塘里鱼的数目,周边有多少车辆?
如何估计池塘里鱼的数目? 老李想估计一下自己池塘里鱼的数量,第一天他捕捞了50条鱼做好标记,然后全放回池塘。过了几天带标记的鱼完全混合于鱼群中,他又去捕捞了168条,发现做标记的鱼有8条。帮老李估算一下池塘里的鱼…...
docker中安装rabbitMq并配置启动
目录 1. 拉取镜像并安装(此处实例安装的是最新版)2.查看docker中已安装的镜像和版本3.启动RabbitMq4.配置管理端5.安装完成 1. 拉取镜像并安装(此处实例安装的是最新版) docker pull rabbitmq2.查看docker中已安装的镜像和版本 …...
viewfs://为Hadoop 中的一个特殊文件系统
解释 viewfs:// 是 Hadoop 中的一个特殊文件系统 URI,用于访问 Hadoop 的视图文件系统(ViewFS)。 ViewFS 是 Hadoop 提供的一种虚拟文件系统,它可以将来自多个底层文件系统的文件统一管理和访问。 通过 ViewFS,你可…...
UniPro自定义个人专属工作台 大幅提升工作效率
很多研发团队在开完每日站会后,工程师的工作习惯便是打开研发管理系统,先看看自己的待办事项,或是查看同事的需求、评论,亦或是查看今日份工作的高优先级项等等。 如何方便工程师能够快速查看和了解一天的工作究竟从哪开始呢&…...
python调用飞书机器人发送文件
当前飞书webhook机器人还不支持发送文件类型的群消息,可以申请创建一个机器人应用来实现群发送文件消息。 创建机器人后,需要开通一系列权限,然后发布。由管理员审核通过后,才可使用。 包括如下的权限,可以获取群的c…...
【产品应用】一体化伺服电机在焊接设备中的应用
随着制造业的不断发展,焊接设备在许多领域都得到了广泛应用,如汽车制造、机械加工、钢结构等领域。为了提高焊接设备的性能和效率,许多厂家开始采用一体化伺服电机作为焊接设备的主要驱动部件。本文将介绍一体化伺服电机在焊接设备中的应用背…...
uni+vue3+firstUI——组件弹框使用 v-model绑定参数
说明 将框架弹框组件 封装成子组件,在页面中引用该子组件,传参并控制弹框显示与隐藏。 子组件 <template><view><wh-modal :show"showPopup" :descr"descr" maskClosable click"onClick" :buttons"…...
【电路笔记】-正弦波形
正弦波 文章目录 正弦波1、概述2、波形产生3、总结 在 19 世纪末的 10 年间,许多技术成就使得交流电的使用得以扩展,并克服了直流电向公众供电的局限性。 1882 年,法国发明了变压器,它简化了交流电的分配,正如我们将在…...
Failed to launch task: 文件”Setup”不存在 Mac安装Adobe软件报错解决方案
在安装 Adobe 软件时,软件提示 Failed to launch task: 文件”Setup”不存在 ,这个时候怎么处理呢? 解决方法如下: 1、安装 AnitCC 或 或 Creative Cloud 环境,保证软件所需要的环境 2、如果安装后也不起作用&#x…...
什么是 DevOps
DevOps是一套融合软件开发(Dev)和 IT 运营(Ops)的实践,旨在缩短应用程序开发周期并确保以高软件质量持续交付,通过采用 DevOps 实践,您可以帮助组织更可靠、更快速、更高效地交付软件。 什么是…...
java修仙基石篇->instanceof子父类检查
instanceof检查子父类(或者是否能被强转) 作用1:检查某对象是否是某类的子类 如:儿子类继承了父亲类。 检查儿子类对象是否属于父亲类 作用2:检查两个对象是否可以强转 语法: 子类对象 instanceof 父…...
[笔记] ‘\0‘的含义:任何字符串后面都会有的结束符。
参考博客:C语言中 \0 代表什么 摘录: ‘\0’ 是字符串的结束符,任何字符串之后都会自动加上’\0’。如果字符串末尾少了‘\0’字符,则其在输出时可能会出现乱码问题。 字符串总是以“\0”作为串的结束符;因此当把一个…...
雪花算法:分布式ID生成的优雅解决方案(建议收藏)
在分布式系统中,唯一ID的生成和管理是一项重要而棘手的任务。雪花算法,由Twitter开源的一种分布式ID生成算法,为这个问题提供了一种优雅的解决方案。本文将详细介绍雪花算法的原理、设计和实现,并通过示例代码和图片帮助读者更好地…...
全国产EtherCAT运动控制边缘控制器(六):RtBasic文件下载与连续轨迹加工的Python+Qt开发
今天,正运动小助手给大家分享一下全国产EtherCAT运动控制边缘控制器ZMC432H如何使用PythonQT实现连续轨迹加工。 01 功能简介 全国产EtherCAT运动控制边缘控制器ZMC432H是正运动的一款软硬件全国产自主可控,运动控制接口兼容EtherCAT总线和脉冲型的独立…...
git代码行统计
本文介绍统计项目代码行的方式,包括使用git log统计、git ls-files统计和使用linux命令行方式统计。 一、使用git log统计 1.统计所有代码行数 当前代码都存放在git仓库下,当需进行代码行数统计时,让开发人员在代码路径下运行如下指令&…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
