Android使用DocumentFile读写外置存储的问题

作者:雪飘碧鸳 时间:2023-10-23 01:58:54 

最近在维护项目,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,都不会弹下面这个授权框:

Android使用DocumentFile读写外置存储的问题

但是-----如果关机再重新开机,就要重新授权,这样很麻烦,用户体验也极其不好,所以又查询了很多资料,最后在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回调里,添加上面这两行代码,就不用每次都弹授权,影响体验了

Android使用DocumentFile读写外置存储的问题

来源:https://blog.csdn.net/toyauko/article/details/122175793

标签:Android,DocumentFile,外置存储
0
投稿

猜你喜欢

  • java新人基础入门之递归调用

    2023-11-10 01:15:16
  • 老生常谈Scanner的基本用法

    2021-08-27 00:34:18
  • Java I/O 操作及优化详细介绍

    2022-07-30 14:46:42
  • Android实现自动点击无障碍服务功能的实例代码

    2022-08-31 12:14:26
  • 解决SpringMVC Controller 接收页面传递的中文参数出现乱码的问题

    2021-08-06 20:52:42
  • Android View 事件分发机制详解

    2023-11-26 05:18:13
  • 一篇文章带你搞定JAVA注解

    2023-03-15 05:22:23
  • Java遗传算法之冲出迷宫

    2022-01-12 21:34:58
  • C#遍历系统进程的方法

    2021-09-24 15:35:29
  • 解决fastjson泛型转换报错的解决方法

    2023-04-02 03:10:44
  • SpringBoot整合WebService服务的实现代码

    2021-11-11 14:59:11
  • 解读@RequestBody与post请求的关系

    2022-10-07 02:02:51
  • Java异常ClassCastException的解决

    2022-10-21 02:37:13
  • Android百度地图定位后获取周边位置的实现代码

    2021-11-30 08:21:07
  • 教你使用Java获取当前时间戳的详细代码

    2021-09-19 04:41:02
  • Android实现下拉菜单Spinner效果

    2022-03-21 07:27:07
  • 使用Java实现Redis限流的方法

    2023-09-27 01:43:47
  • MyBatis Mapper接受参数的四种方式代码解析

    2021-09-05 19:28:27
  • java selenium元素定位大全

    2023-10-23 01:07:42
  • Android仿抖音列表效果

    2022-03-01 16:36:50
  • asp之家 软件编程 m.aspxhome.com