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回调里,添加上面这两行代码,就不用每次都弹授权,影响体验了

到此这篇关于Android使用DocumentFile读写外置存储的问题的文章就介绍到这了,更多相关Android DocumentFile读写外置存储内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android获取内外置存储卡的方法

    以前的Android(4.1之前的版本)中,SDcard跟路径通过"/sdcard"或者"/mnt/sdcard"来表示存储卡,而在Jelly Bean系统中修改为了"/storage/sdcard0",以后可能还会有多个SDcard的情况. 目前为了保持和之前代码的兼容,sdcard路径做了link映射. 为了使您的代码更加健壮并且能够兼容以后的Android版本和新的设备,请通过Environment.getExternalStorageDi

  • Android 8.0 读取内部和外部存储以及外置SDcard的方法

    最近碰到询问我这个读取SDcard的问题, 很久没有看这部分了,所以大致看了一下, 顺便记录一下.在Android 8.0上做了测试. 一般的Android App能读取的存储空间主要有三种: app自己的私有目录,也就是/data/data/<app 目录>. 读写这个目录不需要单独的权限.每个app只能读写自己的目录,而不能读写其他app的目录. Android通过Seandroid对权限进行了管理. /sdcard. 这个其实是Android手机的internal storage. 也就

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

    最近在维护项目,app遇到安装在高版本的Android时,以往直接授权和new File(path)的形式不再支持,日志也是说Permission denied.....好吧,换为DocumentFile. 经过一番操作,也终于实再对存储目录的读和写了,下面记录一下: 首先建一个DocumentFile的Utils类: import android.annotation.TargetApi; import android.content.Context; import android.conte

  • 解决Android 5.1限制外置SD卡写入权限的问题

    在Android开发者网站的 "外部存储技术信息"文档中描述道 : WRITE_EXTERNAL_STORAGE只为设备上的主要外部存储授予写权限,应用程序无法将数据写入二级外部存储设备,除非综合权限指定了应用程序的包目录.这目前只影响双存储设备,如果你的设备有内部存储空间,即通常所说的机身存储,那么你的SD卡就是一个二级外部存储设备. 在Android 4.4中,如果你同时使用了机身存储和SD卡, 那么应用程序将无法在SD卡中创建.修改.删除数据.比如,你无法使用酷我音乐盒下载保存M

  • 简介Android应用中sharedPreferences类存储数据的用法

    在Android开发中,经常会遇到这样一种情况,即需要将用户偏好设置(如用户偏好的app色彩主题).与特定登录用户相关的设置(如不同登陆用户的偏好).整个app的生命周期内的设置(如首次登陆显示介绍页)存储在用户本地,那么首先考虑使用sharedPreferences.这是Android中专门用来存储轻量级键值对数据的类,会在本地以xml文件形式存储. 一. 运行机制 接口:SharedPreferencesImpl在该接口中,定义了一个Editor接口.一个edit方法.多个get方法.一个监

  • Android平台中实现数据存储的5种方式

    本文介绍Android中的5种数据存储方式,具体内容如下 数据存储在开发中是使用最频繁的,在这里主要介绍Android平台中实现数据存储的5种方式,分别是: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面将为大家一一详细介绍.  第一种:使用SharedPreferences存储数据 SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一

  • Android使用文件进行数据存储的方法

    本文实例讲述了Android使用文件进行数据存储的方法.分享给大家供大家参考.具体如下: 很多时候我们开发的软件需要对处理后的数据进行存储,以供再次访问.Android为数据存储提供了如下几种方式: 文件 SharedPreferences(参数) SQLite数据库 内容提供者(Content provider) 网络 首先给大家介绍使用文件如何对数据进行存储 Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的实现过程与在J2SE环境中保存数据到文件中

  • Android之内置和外置sdcard路径显示并且写入数据的方法

    1.效果图片 2.部分代码 package com.example.sdcardcheck; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Array; import java.lang.refle

  • Android 7.0开发获取存储设备信息的方法

    本文实例讲述了 Android 7.0开发获取存储设备信息的方法.分享给大家供大家参考,具体如下: Android 7.0开发相较之前有不少改进,具体可参考前面的文章Android7.0版本影响开发的改进分析,这里简单总结一下Android 7.0针对存储设备的简单操作方法. MountPoint 我们通过MountPoint来描述android设备信息 private static class MountPoint { String mDescription; String mPath; bo

  • android中NFC读写功能的实现方法

    本文实例为大家分享了android中NFC读写功能的具体代码,供大家参考,具体内容如下 首先检查一下设备是否支持NFC功能 private void checkNFCFunction() { // TODO Auto-generated method stub mNfcAdapter = NfcAdapter.getDefaultAdapter(this); // check the NFC adapter first if (mNfcAdapter == null) { // mTextVie

  • Android入门之读写本地文件的实现

    目录 简介 课程目标 UI端 后端代码 运行效果 简介 为了这个系列,我的代码已经准备到了第150天了.接下来的内容会越来越精彩,我们也越来越开始进入Android的一些高级功能上的编程了.今天我们就要讲Android中对本地文件进行读写的全过程. 课程目标 输入文件名.输入文件内容后按[保存到SD卡],可以把文件保存到SD卡根目录: 输入文件名,按[读取SD卡中的文件],可以根据输入的文件名把文件内容显示成Toast: 搞清Android中对于SD卡读写时所需要的静态权限申请.动态权限申请:

  • Android持久化技术之SharedPreferences存储实例详解

    本文实例讲述了Android持久化技术之SharedPreferences存储.分享给大家供大家参考,具体如下: 1.SharedPreferences存储 在前面一篇文章<Android持久化技术之文件的读取与写入实例详解>中,我们介绍了Android持久化技术的文件的读取与写入.在本文中,继续介绍Android持久化技术另外一个SharedPreferences存储. (1)SharedPreferences存储方式是基于key-value的,通过key可以找到对应的value. (2)支

随机推荐