IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 11 保存文件到外部存储,并分享文件 -> 正文阅读

[移动开发]Android 11 保存文件到外部存储,并分享文件

众所周知,Android 11 使用了专属目录,并且强制启用。关于专属目录的介绍,这里不详细多说,因为官方文档已经很明确了,这里主要介绍我保存在外部存储根目录遇到的一些坑。
专属目录,就是Android11为应用开辟的专属空间,APP将文件保存到专属目录,不再需要请求存储权限,直接就可以保存。并且其它应用无法访问专属目录里的文件,保证了用户的隐私安全。

而我这里的需求不是将文件保存在专属目录里,也不是保存在媒体目录里,而是外部存储的根目录,新建一个文件夹,保存我的csv文件,并将csv分享出去。

其实之前针对Android 10,我就已经采用了 FileProvider 的做法,但是一年后,同样的代码出现了错误,并且Android10上没问题,Android11出现了问题。
我主要遇到了两个问题,

  1. Android 10 分享时提示 csv文件不存在: Failed to find configured root ....
  2. Android 11 保存为csv时,提示 : EPERM (Operation not permitted)

下面从头到尾介绍我的解决方法:

1. Manifest.xml

① 在 application 下添加:android:requestLegacyExternalStorage="true"

② 定义 FileProvider: 向您的应用清单添加一个元素。
name 固定使用 androidx.core.content.FileProvider ,
authorities 一般是 包名+fileprovider
exportedfalse
grantUriPermissionstrue,授予对文件的临时访问权限,用来分享。

<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;
        }
        String csvName = csvPath.substring(csvPath.lastIndexOf("/") + 1);
        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的临时读写权限。

如此编写实现在外部存储目录的需求。

在Android11上,因为我保存的文件名是当前时间,使用时间格式yyyy-MM-dd HH:mm:ss 生成的,但是一直遇到 EPERM (Operation not permitted) 问题。我以为是Android11上的文件目录权限问题造成的,导致没有读取权限。搜了一下才知道,原来是文件名保存时的问题… 时间格式里 : 要改成 . 就不会有这个问题了。吐血…

参考:
Creating a CSV file in Android 11 Return Error “java.io.FileNotFoundException: EPERM (Operation not permitted)”
开发者文档 FileProvider

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-29 10:24:09  更:2021-09-29 10:26:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:42:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码