Android使用DocumentFile读写外置存储的问题
作者:雪飘碧鸳 发布时间:2023-10-23 01:58:54
标签:Android,DocumentFile,外置存储
最近在维护项目,app遇到安装在高版本的Android时,以往直接授权和new File(path)的形式不再支持,日志也是说Permission denied。。。。。好吧,换为DocumentFile。
经过一番操作,也终于实再对存储目录的读和写了,下面记录一下:
首先建一个DocumentFile的Utils类:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.provider.DocumentsContract;
import android.util.Log;
import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
public class DocumentsUtils {
private static final String TAG = DocumentsUtils.class.getSimpleName();
public static final int OPEN_DOCUMENT_TREE_CODE = 8000;
private static List<String> sExtSdCardPaths = new ArrayList<>();
private DocumentsUtils() {
}
public static void cleanCache() {
sExtSdCardPaths.clear();
}
/**
* Get a list of external SD card paths. (Kitkat or higher.)
*
* @return A list of external SD card paths.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static String[] getExtSdCardPaths(Context context) {
if (sExtSdCardPaths.size() > 0) {
return sExtSdCardPaths.toArray(new String[0]);
}
for (File file : context.getExternalFilesDirs("external")) {
if (file != null && !file.equals(context.getExternalFilesDir("external"))) {
int index = file.getAbsolutePath().lastIndexOf("/Android/data");
if (index < 0) {
Log.w(TAG, "Unexpected external file dir: " + file.getAbsolutePath());
} else {
String path = file.getAbsolutePath().substring(0, index);
try {
path = new File(path).getCanonicalPath();
} catch (IOException e) {
// Keep non-canonical path.
}
sExtSdCardPaths.add(path);
}
}
}
if (sExtSdCardPaths.isEmpty()) sExtSdCardPaths.add("/storage/sdcard1");
return sExtSdCardPaths.toArray(new String[0]);
}
/**
* Determine the main folder of the external SD card containing the given file.
*
* @param file the file.
* @return The main folder of the external SD card containing this file, if the file is on an SD
* card. Otherwise,
* null is returned.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static String getExtSdCardFolder(final File file, Context context) {
String[] extSdPaths = getExtSdCardPaths(context);
try {
for (int i = 0; i < extSdPaths.length; i++) {
if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
return extSdPaths[i];
}
}
} catch (IOException e) {
return null;
}
return null;
}
/**
* Determine if a file is on external sd card. (Kitkat or higher.)
*
* @param file The file.
* @return true if on external sd card.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean isOnExtSdCard(final File file, Context c) {
return getExtSdCardFolder(file, c) != null;
}
/**
* Get a DocumentFile corresponding to the given file (for writing on ExtSdCard on Android 5).
* If the file is not
* existing, it is created.
*
* @param file The file.
* @param isDirectory flag indicating if the file should be a directory.
* @return The DocumentFile
*/
public static DocumentFile getDocumentFile(final File file, final boolean isDirectory,
Context context) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return DocumentFile.fromFile(file);
}
String baseFolder = getExtSdCardFolder(file, context);
// Log.i(TAG,"lum_ baseFolder " + baseFolder);
boolean originalDirectory = false;
if (baseFolder == null) {
return null;
}
String relativePath = null;
try {
String fullPath = file.getCanonicalPath();
if (!baseFolder.equals(fullPath)) {
relativePath = fullPath.substring(baseFolder.length() + 1);
} else {
originalDirectory = true;
}
} catch (IOException e) {
return null;
} catch (Exception f) {
originalDirectory = true;
//continue
}
String as = PreferenceManager.getDefaultSharedPreferences(context).getString(baseFolder,
null);
Uri treeUri = null;
if (as != null) treeUri = Uri.parse(as);
if (treeUri == null) {
return null;
}
// start with root of SD card and then parse through document tree.
DocumentFile document = DocumentFile.fromTreeUri(context, treeUri);
if (originalDirectory) return document;
String[] parts = relativePath.split("/");
for (int i = 0; i < parts.length; i++) {
DocumentFile nextDocument = document.findFile(parts[i]);
if (nextDocument == null) {
if ((i < parts.length - 1) || isDirectory) {
nextDocument = document.createDirectory(parts[i]);
} else {
nextDocument = document.createFile("image", parts[i]);
}
}
document = nextDocument;
}
return document;
}
public static boolean mkdirs(Context context, File dir) {
boolean res = dir.mkdirs();
if (!res) {
if (DocumentsUtils.isOnExtSdCard(dir, context)) {
DocumentFile documentFile = DocumentsUtils.getDocumentFile(dir, true, context);
res = documentFile != null && documentFile.canWrite();
}
}
return res;
}
public static boolean delete(Context context, File file) {
boolean ret = file.delete();
if (!ret && DocumentsUtils.isOnExtSdCard(file, context)) {
DocumentFile f = DocumentsUtils.getDocumentFile(file, false, context);
if (f != null) {
ret = f.delete();
}
}
return ret;
}
public static boolean canWrite(File file) {
boolean res = file.exists() && file.canWrite();
if (!res && !file.exists()) {
try {
if (!file.isDirectory()) {
res = file.createNewFile() && file.delete();
} else {
res = file.mkdirs() && file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return res;
}
public static boolean canWrite(Context context, File file) {
boolean res = canWrite(file);
if (!res && DocumentsUtils.isOnExtSdCard(file, context)) {
DocumentFile documentFile = DocumentsUtils.getDocumentFile(file, true, context);
res = documentFile != null && documentFile.canWrite();
}
return res;
}
/**
* 重命名
* @param context
* @param src
* @param dest
* @return
*/
public static boolean renameTo(Context context, File src, File dest) {
boolean res = src.renameTo(dest);
if (!res && isOnExtSdCard(dest, context)) {
DocumentFile srcDoc;
if (isOnExtSdCard(src, context)) {
srcDoc = getDocumentFile(src, false, context);
} else {
srcDoc = DocumentFile.fromFile(src);
}
DocumentFile destDoc = getDocumentFile(dest.getParentFile(), true, context);
if (srcDoc != null && destDoc != null) {
try {
Log.i("renameTo", "src.getParent():" + src.getParent() + ",dest.getParent():" + dest.getParent());
if (src.getParent().equals(dest.getParent())) {//同一目录
res = srcDoc.renameTo(dest.getName());
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//不同一目录
Uri renameSrcUri = DocumentsContract.renameDocument(context.getContentResolver(),//先重命名
srcDoc.getUri(), dest.getName());
res = DocumentsContract.moveDocument(context.getContentResolver(),//再移动
renameSrcUri,
srcDoc.getParentFile().getUri(),
destDoc.getUri()) != null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return res;
}
public static InputStream getInputStream(Context context, File destFile) {
InputStream in = null;
try {
if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
if (file != null && file.canWrite()) {
in = context.getContentResolver().openInputStream(file.getUri());
}
} else {
in = new FileInputStream(destFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return in;
}
public static OutputStream getOutputStream(Context context, File destFile) {
OutputStream out = null;
try {
if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
if (file != null && file.canWrite()) {
out = context.getContentResolver().openOutputStream(file.getUri());
}
} else {
out = new FileOutputStream(destFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return out;
}
/**
* 获取文件流
* @param context
* @param destFile 目标文件
* @param mode May be "w", "wa", "rw", or "rwt".
* @return
*/
public static OutputStream getOutputStream(Context context, File destFile, String mode) {
OutputStream out = null;
try {
if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
if (file != null && file.canWrite()) {
out = context.getContentResolver().openOutputStream(file.getUri(), mode);
}
} else {
out = new FileOutputStream(destFile, mode.equals("rw") || mode.equals("wa"));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return out;
}
public static FileDescriptor getFileDescriptor(Context context, File destFile) {
FileDescriptor fd = null;
try {
if (/*!canWrite(destFile) && */isOnExtSdCard(destFile, context)) {
DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
if (file != null && file.canWrite()) {
ParcelFileDescriptor out = context.getContentResolver().openFileDescriptor(file.getUri(), "rw");
fd = out.getFileDescriptor();
}
} else {
RandomAccessFile file = null;
try {
file = new RandomAccessFile(destFile, "rws");
file.setLength(0);
fd = file.getFD();
} catch (Exception e){
e.printStackTrace();
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return fd;
}
public static boolean saveTreeUri(Context context, String rootPath, Uri uri) {
DocumentFile file = DocumentFile.fromTreeUri(context, uri);
if (file != null && file.canWrite()) {
SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);
perf.edit().putString(rootPath, uri.toString()).apply();
Log.e(TAG, "save uri" + rootPath);
return true;
} else {
Log.e(TAG, "no write permission: " + rootPath);
}
return false;
}
/**
* 返回true表示没有权限
* @param context
* @param rootPath
* @return
*/
public static boolean checkWritableRootPath(Context context, String rootPath) {
File root = new File(rootPath);
if (!root.canWrite()) {
Log.e(TAG,"sd card can not write:" + rootPath + ",is on extsdcard:" + DocumentsUtils.isOnExtSdCard(root, context));
if (DocumentsUtils.isOnExtSdCard(root, context)) {
DocumentFile documentFile = DocumentsUtils.getDocumentFile(root, true, context);
if (documentFile != null) {
Log.i(TAG, "get document file:" + documentFile.canWrite());
}
return documentFile == null || !documentFile.canWrite();
} else {
SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);
String documentUri = perf.getString(rootPath, "");
Log.i(TAG,"lum_2 get perf documentUri:" + documentUri);
if (documentUri == null || documentUri.isEmpty()) {
return true;
} else {
DocumentFile file = DocumentFile.fromTreeUri(context, Uri.parse(documentUri));
if (file != null)
Log.i(TAG,"lum get perf documentUri:" + file.canWrite());
return !(file != null && file.canWrite());
}
}
}else{
Log.e(TAG,"sd card can write...");
}
return false;
}
}
然后在app启动的地方检查下是否需要授权才能操作:
if (DocumentsUtils.checkWritableRootPath(this, StringUtils.STORAGE_PATH)) { //检查sd卡路径是否有 权限 没有显示dialog
showOpenDocumentTree();
}
private void showOpenDocumentTree() {
Log.e("showOpenDocumentTree", "start check sd card...");
Intent intent = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
StorageManager sm = getSystemService(StorageManager.class);
StorageVolume volume = sm.getStorageVolume(new File(StringUtils.STORAGE_PATH));
if (volume != null) {
intent = volume.createAccessIntent(null);
}
}
Log.e("showOpenDocumentTree", "intent=" + intent);
if (intent == null) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
}
startActivityForResult(intent, DocumentsUtils.OPEN_DOCUMENT_TREE_CODE);
}
//................................
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case DocumentsUtils.OPEN_DOCUMENT_TREE_CODE:
if (data != null && data.getData() != null) {
Uri uri = data.getData();
DocumentsUtils.saveTreeUri(this, StringUtils.STORAGE_PATH, uri);
Log.i(TAG,"DocumentsUtils.OPEN_DOCUMENT_TREE_CODE : " + uri);
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
按以上代码,是可以实现对外置存储卡进行读和写操作了,只要机器没关机,多关打开关闭app,都不会弹下面这个授权框:
但是-----如果关机再重新开机,就要重新授权,这样很麻烦,用户体验也极其不好,所以又查询了很多资料,最后在stackoverflow上找到办法,关键是下面这句:
grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
是在ActivityResult回调里,添加上面这两行代码,就不用每次都弹授权,影响体验了
来源:https://blog.csdn.net/toyauko/article/details/122175793


猜你喜欢
- 为什么需要多线程?模型的简化,如某些程序是由多个相对独立任务的运行:图形界面的出现,输入、输出的阻塞多核CPU的更好利用异步行为的需要Jav
- 网上的教程大都是手动通过protoc编译, 比较难用给当前工程添加"Google.Protobuf"和"Grp
- 前言似乎委托对于C#而言是一种高级属性,但是我依旧希望你就算第一次看我的文章,也能有很大的收获。所以本博客的语言描述尽量简单易懂,知识点也是
- 在JVM中,每个对象都关联一个监视器,这里的对象包含Object实例和Class实例。监视器是一个同步工具,相当于一个许可证,拿到许可证的线
- 本文实例讲述了C#中累加器函数Aggregate用法。分享给大家供大家参考。具体如下:var shouldExclude = false;v
- 在这里就分享两条开发中曾经忽略的问题:1、Union(联合体)的字节对齐先看代码:#pragma pack(4)struct com{&nb
- 介绍: 本文章主要针对web项目中的两个问题进行详细解析介绍:1- 页面跳转404,即controller转发无法跳转页面问题;2- 静态资
- 今天写一个小程序中使用到了全局快捷键,找到了我之前写的文章在c#中使用全局快捷键翻了一下,发现它是WinForm版本的,而我现在大部分写WP
- /** * @param h *
- 1、Aop 全程是Aspect Oriented Programming 即面向切面编程,通过预编译方式和运行期 * 实现程序功能的同一维
- 还记得警匪片上,匪徒们是怎么配合实施犯罪的吗?一个团伙在进行盗窃的时候,总有一两个人在门口把风——如果有什么风吹草动,则会立即通知里面的同伙
- 前言: 最近一直在看Launcher模块,经过差不多两个月学习,终于摸透了Launcher的一些主要功能实现,目前继续还处于摸索状态。未看L
- 本文实例为大家分享了android自定义环形统计图动画的具体代码,供大家参考,具体内容如下一、测试截图二、实现原理 package
- 每天上下楼都是乘坐电梯的,就想电梯的工作原理是什么呢?于是自己写了个控制台程序来模拟一下电梯的工作原理!采用面向对象的编程思想!将电梯拆解为
- 目录1、Integer a = 1;2、对于同一类中的两个方法 , 在判断它们是不是重载方法时 , 肯定不考虑( )3、对于Java中异常的
- 前言现在的项目一般是拆分成一个个独立的模块,当在其他项目中想要使用独立出来的这些模块,只需要在其pom.xml使用<dependenc
- 1、final修饰类被final修饰的类不能被继承,因此final类的成员方法也不能被覆写,被final关键字修饰的类没有子类,因此类的实现
- 关于注入数据说明1.不通过配置文件注入数据通过@Value将外部的值动态注入到Bean中,使用的情况有:注入普通字符串注入操作系统属性注入表
- 加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可
- 前言:本篇文章将介绍Java多线程中的几个典型案例之单例模式,所谓单例模式,就是一个类只有一个实例对象,本文将着重介绍在多线程的背景下,单例