Android 11 保存文件到外部存储,并分享文件
众所周知,Android 11 使用了专属目录,并且强制启用。关于专属目录的介绍,这里不详细多说,因为官方文档已经很明确了,之后或许会写一下。这里主要介绍我保存在外部存储根目录
遇到的一些坑。
专属目录,就是Android11为应用开辟的专属空间,APP将文件保存到专属目录,不再需要请求存储权限,直接就可以保存。并且其它应用无法访问专属目录里的文件,保证了用户的隐私安全。
而我这里的需求不是将文件保存在专属目录里,也不是保存在媒体目录里,而是外部存储的根目录,新建一个文件夹,保存我的csv文件,并将csv分享出去。
其实之前针对Android 10,我就已经采用了 FileProvider
的做法,但是一年后,同样的代码出现了错误,并且Android10上没问题,Android11出现了问题。 我主要遇到了两个问题,
- Android 10 分享时提示 csv文件不存在:
Failed to find configured root ....
- Android 11 保存为csv时,提示 :
EPERM (Operation not permitted)
下面从头到尾介绍我的解决方法:
1. Manifest.xml
① 在 application
下添加:android:requestLegacyExternalStorage="true"
② 定义 FileProvider: 向您的应用清单添加一个元素。
name
固定使用 androidx.core.content.FileProvider ,
authorities
一般是 包名 fileprovider,
exported
为 false,
grantUriPermissions
为 true,授予对文件的临时访问权限,用来分享。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.adsale.registersite.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<!-- 元数据 -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
③ 在 res
目录下 新建 xml
文件夹,创建文件名为file_paths
,在里面添加如下代码。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<external-path name="com.adsale.ConcurrentEvent" path="." />
</paths>
</resources>
path
里面的属性介绍一下:
external-path
相当于代码 Environment.getExternalStorageDirectory()
,所以如果你代码里使用的是这个路径,就用 external-path
.
name
是你在 ExternalStorageDirectory() 下建立的文件夹的名称,例如我这里的文件名是 :com.adsale.ConcurrentEvent
path
使用 .
,或者 /
。
其它path对应代码:
<files-path/>
—— Context.getFilesDir()
<cache-path/>
—— Context.getCacheDir()
<external-path/>
—— Environment.getExternalStorageDirectory()
<external-files-path/>
—— Context.getExternalFilesDir(String)
<external-cache-path/>
—— Context.getExternalCacheDir()
<external-media-path/>
—— Context.getExternalMediaDirs()
2. 代码创建外部存储目录
首先要请求外部读写权限,这个不用说。 如果文件夹不存在,则创建
public String setRootDir(Activity activity) {
String rootPath = "";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//有SD卡
rootPath = Environment.getExternalStorageDirectory() "/";
} else {
rootPath = Environment.getDataDirectory() "/";
}
rootPath = rootPath "com.adsale.ConcurrentEvent/";
if (RequestPermissionUtil.requestPermissionWriteStorage(activity)) { // 有存储权限,则创建文件夹,没有权限则请求
createRootDir(rootPath);
}
return rootPath; // 返回文件夹绝对路径
// /storage/emulated/0/com.adsale.ConcurrentEvent/
}
/**
* 创建文件夹
**/
public boolean createRootDir(String rootPath) {
File dirRoot = new File(rootPath);
if (!dirRoot.exists() || !dirRoot.isDirectory()) {
boolean isCreateRoot = dirRoot.mkdirs();
return isCreateRoot;
}
App.rootDir = rootPath ;
return true;
}
得到绝对路径,我们就可以往这个目录里存文件啦。 文件写出过程省略。
3. 分享文件
private void sendCSVByEmail() {
// App.rootDir是前一步得到的绝对路径:/storage/emulated/0/com.adsale.ConcurrentEvent/
// csvName 是文件名称:签到查询-2021-09-28 15.42.26.csv
String csvPath = App.rootDir csvName;
File file = new File(csvPath);
if (!file.exists()) {
Toast.makeText(getApplicationContext(), "CSV不存在", Toast.LENGTH_SHORT).show();
return;
}
try {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra("subject", csvName);
intent.putExtra("body", " "); // 正文
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.adsale.registersite.fileprovider", file);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, uri); // 添加附件,附件为file对象
if (csvName.endsWith(".csv")) {
intent.setType("application/octet-stream"); // 其他的均使用流当做二进制数据来发送
}
startActivity(intent); // 调用系统的mail客户端进行发送
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), "系统没有邮件客户端!", Toast.LENGTH_SHORT).show();
}
}
重点代码:
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.adsale.registersite.fileprovider", file);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.adsale.registersite.fileprovider", file);
com.adsale.registersite.fileprovider
是xml中定义的authorities
,file 是 csv文件的File,通过 FileProvider 获取内容Uri。 通过 setFlags 授予对返回的内容URI的临时读写权限。
以上实现了在外部存储目录的需求。
QAQ
在Android11上,因为我保存的文件名是当前时间,使用时间格式yyyy-MM-dd HH:mm:ss
生成的,但是一直遇到 EPERM (Operation not permitted)
问题。我以为是Android11上的文件目录权限问题造成的,导致没有读取权限。搜了一下才知道,原来是文件名保存时的问题...... 时间格式里 :
要改成 .
就不会有这个问题了。
本篇文章来至:学新通
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通
- 本文地址: https://www.swvq.com/boutique/detail/tanefka
- 联系方式: luke.wu#vfv.cc
-
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel下拉菜单选择后怎么自动出现相应内容
PHP中文网 06-24 -
wphotoshop工具右键快捷工具不见了怎么办
PHP中文网 06-19 -
微信小程序没声音怎么办
PHP中文网 06-15 -
手机怎样打开html文件
PHP中文网 05-20 -
pr编辑工具栏面板不见了怎么办
PHP中文网 05-09 -
把文字添加蓝色阴影边框
PHP中文网 06-28 -
win7系统开机后总是弹出个性化设置怎么办
PHP中文网 06-24