首先读取外置TF卡,需要获取到外置TF卡的权限,因为Android 在4.4版本之后 引入了外部存储访问的框架(SAF)。SAF其中的部分功能就是通过其获取对外置TF卡的读写权限,从而操作外置TF卡。

目录

1、TF卡读写操作

1.1获取TF卡权限

1.2、DoucumentFile简介

1.2.1创建文件

1.2.2编辑文件

1.2.3查找文件

1.2.4删除文件

1.2.5获得文件名

1.2.6获得文件Uri

1.2.7获得文件类型(MIME类型)

1.2.8创建文件夹

1.2.9获得文件的父文件

1.2.10查看是否是目录/文件

1.2.11获得目录下所有文件

1.2.12重命名文件

2、U盘的读取

3、TF卡路径和U盘根路径获取

附件

MIME_TYPE

StorageUtils.java

DocumentsUtils.java

MainActivity.java


在Android7.0时对外部存储进行了简化

1、TF卡读写操作

1.1获取TF卡权限

private void showOpenDocumentTree(String rootPath) {Intent intent = null;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {StorageManager sm = getSystemService(StorageManager.class);StorageVolume volume = sm.getStorageVolume(new File(rootPath));if (volume != null) {intent = volume.createAccessIntent(null);}}if (intent == null) {intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);}Log.d(TAG,"startActivityForResult...");startActivityForResult(intent, DocumentsUtils.OPEN_DOCUMENT_TREE_CODE);
}

当我们应用在打开的时候就会提示如下的弹框

点击确定,在对权限回调进行处理,将SD卡路径和URI通过Sp存储起来方便下次使用。SD卡路径的获取会在后文中提到。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case DocumentsUtils.OPEN_DOCUMENT_TREE_CODE:if (data != null && data.getData() != null) {Uri uri = data.getData();String rootPath = StorageUtils.getSDCardDir(this);DocumentsUtils.saveTreeUri(this, rootPath, uri);final int takeFlags = data.getFlags()& (Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);Log.d(TAG, "save tree uri = " + uri);// 相当于将url存储下来,下次开启apk不在进行弹框权限请求getContentResolver().takePersistableUriPermission(uri, takeFlags);}break;}}

1.2、DoucumentFile简介

在使用之前,我们要知道这是一个模拟File的程序类,它提供了文档树的模式,所以它会有很大的开销,为了节省我们的空间,我们要确定是不是要访问整个文档树,最小特权原则规定只应要求访问真正需要的文档。如果只需要用户选择单个文件,使用ACTION_OPEN_DOCUMENT或 ACTION_GET_CONTENT。如果想让用户选择多个文件,添加EXTRA_ALLOW_MULTIPLE。如果只需要用户保存单个文件,使用ACTION_CREATE_DOCUMENT。如果使用这些API,可以通过产生的getData()成 fromSingleUri(Context, Uri)与文档工作。

如果确实需要完全访问整个文档子树,首先启动ACTION_OPEN_DOCUMENT_TREE以允许用户选择目录。然后,通过所产生的getData()进入 fromTreeUri(Context, Uri)开始与用户选择的树工作。

在导航DocumentFile实例树时,始终可以使用 getUri()获取表示该对象的基础文档的Uri,以便与之一起使openInputStream(Uri)等。

1.2.1创建文件

DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri);

DocumentFile newFile = documentFile.createFile("text/plain", "hello.txt");

通过 createFile(String mimeType, String displayName)来创建一个hello.txt的文件.

对应这里的 “mimeType” 我会单独在文末给贴出来。

注:因为我获得uri权限是sd卡完整权限,所以创建文件的位置是卡的跟目录.能使用uri就用uri,不要documentFile一层套一层,很浪费资源的。

1.2.2编辑文件

向hello.txt写入string

OutputStream out = null;
try {out = context.getContentResolver().openOutputStream(fileText.getUri());out.write("A long time ago...".getBytes());out.close();
} catch (Exception e) {e.printStackTrace();
}

写入一个图片文件

OutputStream out = null;
try {out = context.getContentResolver().openOutputStream(fileImage.getUri());ByteArrayOutputStream baos = new ByteArrayOutputStream();BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher).compress(Bitmap.CompressFormat.JPEG, 100, baos);InputStream inputimage = new ByteArrayInputStream(baos.toByteArray());byte[] bytes = new byte[1024 * 10];int len = 0;while ((len = inputimage.read(bytes)) != -1) {out.write(bytes, 0, len);}out.close();
} catch (Exception e) {e.printStackTrace();
}

1.2.3查找文件

DocumentFile file = documentFile.findFile("abc.txt");

1.2.4删除文件

DocumentFile有delete()方法来删除文件

newFile.delete();

1.2.5获得文件名

通过getName()能够得到当前documentFile的文件名

Log.d(TAG, "newFile.getName():"+newFile.getName());

1.2.6获得文件Uri

Log.d(TAG, "newFile.getUri():"+newFile.getUri());

1.2.7获得文件类型(MIME类型)

Log.d(TAG, "newFile.getType():"+newFile.getType());

1.2.8创建文件夹

createDirectory(String displayName)能够在当前documentFile树下创建名称为displayName的文件夹

DocumentFile testDir = documentFile.createDirectory("helloDir");

1.2.9获得文件的父文件

getParentFile()能够获得当前文件的父文件

Log.d(TAG, "newFile.getParentFile():"+newFile.getParentFile());

1.2.10查看是否是目录/文件

boolean directory = documentFile.isDirectory();

boolean file = documentFile.isFile();

1.2.11获得目录下所有文件

DocumentFile[] documentFiles = documentFile.listFiles();

1.2.12重命名文件

boolean renameFile = newFile.renameTo("renameFile");

代码中提到了DoucumentsFile类,这个类比较好找,网上一大堆,文末会附上代码

之前查看了网上其他博客,发现用他们的方法不生效或者崩溃.对我来说 getTreeUri 和 setTreeUri是比较有用的,同时也加了一部分自己的方法方便使用 (doCopyFile)

/*** @param context      context* @param uri          uri 默认传null ,从sp中读取* @param orgFilePath  被复制的文件路径* @param destFileName 复制后的文件名* @desc 将orgFilePath文件复制到指定SD卡指定路径/storage/xx-xx/hello/*/
public static boolean doCopyFile(Context context, Uri uri, String orgFilePath, String destFileName)

在配合MainActivity这样操作就可以把东西写在外置TF卡里了

public void onClick1(View v) {String rootPath = StorageUtils.getSDCardDir(this);String urlStr = DocumentsUtils.getTreeUri(this, rootPath);Uri uri = Uri.parse(urlStr);writeFile(this, uri);DocumentsUtils.doCopyFile(this,null,"/storage/emulated/0/helloWorld.apk","copyApk.apk");
}

有两种方式

  • 是直接穿件文件往文件里写,writeFile
  • 是对已有的文件copy到TF卡里,doCopyFile,因为有时候文件不好直接往TF卡中操作的时候,可以现在本地创建好,之后再进行复制就可以了

操作前

操作后

复制的APK 亲测可安装,这样完美操作TF卡。

2、U盘的读取

U盘文件的读取相对简单一些,主要是需要获取到U盘的根路径,然后通过File的操作来操作U盘,不用必须通过广播的方式来读取U盘。

public void onClick(View v) {String path = StorageUtils.getUsbDir(this);if (!TextUtils.isEmpty(path)) {File[] files = new File(path).listFiles();if (files != null) {for (File file : files) {if (file.getName().endsWith(".zip")) {Log.d(TAG, "file path=" + file.getAbsolutePath());}}}} else {Log.d(TAG, "没有插入U盘");}}

3、TF卡路径和U盘根路径获取

TF卡路径获取方法

public static String getSDCardDir(Context context) {String sdcardDir = null;StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);Class<?> volumeInfoClazz = null;Class<?> diskInfoClazz = null;try {diskInfoClazz = Class.forName("android.os.storage.DiskInfo");Method isSd = diskInfoClazz.getMethod("isSd");volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");Method getType = volumeInfoClazz.getMethod("getType");Method getDisk = volumeInfoClazz.getMethod("getDisk");Field path = volumeInfoClazz.getDeclaredField("path");Method getVolumes = storageManager.getClass().getMethod("getVolumes");List<Class<?>> result = (List<Class<?>>) getVolumes.invoke(storageManager);for (int i = 0; i < result.size(); i++) {Object volumeInfo = result.get(i);if ((int) getType.invoke(volumeInfo) == 0) {Object disk = getDisk.invoke(volumeInfo);if (disk != null) {if ((boolean) isSd.invoke(disk)) {sdcardDir = (String) path.get(volumeInfo);break;}}}}if (sdcardDir == null) {Log.w(TAG, "sdcardDir null");return null;} else {Log.i(TAG, "sdcardDir " + sdcardDir + File.separator);return sdcardDir + File.separator;}} catch (Exception e) {Log.i(TAG, "sdcardDir e " + e.getMessage());e.printStackTrace();}Log.w(TAG, "sdcardDir null");return null;}

U盘根路径获取

public static String getUsbDir(Context context) {String sdcardDir = null;StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);Class<?> volumeInfoClazz = null;Class<?> diskInfoClazz = null;try {diskInfoClazz = Class.forName("android.os.storage.DiskInfo");Method isUsb = diskInfoClazz.getMethod("isUsb");volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");Method getType = volumeInfoClazz.getMethod("getType");Method getDisk = volumeInfoClazz.getMethod("getDisk");Field path = volumeInfoClazz.getDeclaredField("path");Method getVolumes = storageManager.getClass().getMethod("getVolumes");List<Class<?>> result = (List<Class<?>>) getVolumes.invoke(storageManager);for (int i = 0; i < result.size(); i++) {Object volumeInfo = result.get(i);Log.w(TAG, "disk path " + path.get(volumeInfo));if ((int) getType.invoke(volumeInfo) == 0) {Object disk = getDisk.invoke(volumeInfo);Log.w(TAG, "is usb " + isUsb.invoke(disk));if (disk != null) {if ((boolean) isUsb.invoke(disk)) {sdcardDir = (String) path.get(volumeInfo);break;}}}}if (sdcardDir == null) {Log.w(TAG, "usbpath null");return null;} else {Log.i(TAG, "usbpath " + sdcardDir + File.separator);return sdcardDir + File.separator;}} catch (Exception e) {Log.i(TAG, "usbpath e " + e.getMessage());e.printStackTrace();}Log.w(TAG, "usbpath null");return null;}

两者代码大同小异,小异在什么地方,看这里

Method isUsb = diskInfoClazz.getMethod("isUsb");

这里的反射调用为什么这么用,我们可以参考Android源码中的 “DiskInfo.java” 和 “VolumeInfo.java”,系统通过isUsb和isSd变量来区别是否是U盘设备和外接SD卡设备。

最后不能忘记最基础的读写权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

附件

MIME_TYPE

{".3gp",    "video/3gpp"},
{".apk",    "application/vnd.android.package-archive"},
{".asf",    "video/x-ms-asf"},
{".avi",    "video/x-msvideo"},
{".bin",    "application/octet-stream"},
{".bmp",    "image/bmp"},
{".c",  "text/plain"},
{".class",  "application/octet-stream"},
{".conf",   "text/plain"},
{".cpp",    "text/plain"},
{".doc",    "application/msword"},
{".docx",   "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".xls",    "application/vnd.ms-excel"},
{".xlsx",   "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".exe",    "application/octet-stream"},
{".gif",    "image/gif"},
{".gtar",   "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h",  "text/plain"},
{".htm",    "text/html"},
{".html",   "text/html"},
{".jar",    "application/java-archive"},
{".java",   "text/plain"},
{".jpeg",   "image/jpeg"},
{".jpg",    "image/jpeg"},
{".js", "application/x-javascript"},
{".log",    "text/plain"},
{".m3u",    "audio/x-mpegurl"},
{".m4a",    "audio/mp4a-latm"},
{".m4b",    "audio/mp4a-latm"},
{".m4p",    "audio/mp4a-latm"},
{".m4u",    "video/vnd.mpegurl"},
{".m4v",    "video/x-m4v"},
{".mov",    "video/quicktime"},
{".mp2",    "audio/x-mpeg"},
{".mp3",    "audio/x-mpeg"},
{".mp4",    "video/mp4"},
{".mpc",    "application/vnd.mpohun.certificate"},
{".mpe",    "video/mpeg"},
{".mpeg",   "video/mpeg"},
{".mpg",    "video/mpeg"},
{".mpg4",   "video/mp4"},
{".mpga",   "audio/mpeg"},
{".msg",    "application/vnd.ms-outlook"},
{".ogg",    "audio/ogg"},
{".pdf",    "application/pdf"},
{".png",    "image/png"},
{".pps",    "application/vnd.ms-powerpoint"},
{".ppt",    "application/vnd.ms-powerpoint"},
{".pptx",   "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prop",   "text/plain"},
{".rc", "text/plain"},
{".rmvb",   "audio/x-pn-realaudio"},
{".rtf",    "application/rtf"},
{".sh", "text/plain"},
{".tar",    "application/x-tar"},
{".tgz",    "application/x-compressed"},
{".txt",    "text/plain"},
{".wav",    "audio/x-wav"},
{".wma",    "audio/x-ms-wma"},
{".wmv",    "audio/x-ms-wmv"},
{".wps",    "application/vnd.ms-works"},
{".xml",    "text/plain"},
{".z",  "application/x-compress"},
{".zip",    "application/x-zip-compressed"},
{"",        "*/*"}

StorageUtils.java

import android.content.Context;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.util.Log;import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;public class StorageUtils {private static final String TAG = "StorageUtils";//定义GB的计算常量private static final int GB = 1024 * 1024 * 1024;//定义MB的计算常量private static final int MB = 1024 * 1024;//定义KB的计算常量private static final int KB = 1024;public static String bytes2kb(long bytes) {//格式化小数DecimalFormat format = new DecimalFormat("###.0");if (bytes / GB >= 1) {return format.format(bytes * 1.0f / GB) + "GB";} else if (bytes / MB >= 1) {return format.format(bytes * 1.0f / MB) + "MB";} else if (bytes / KB >= 1) {return format.format(bytes * 1.0f / KB) + "KB";} else {return bytes + "B";}}/*获取全部存储设备信息封装对象*/public static ArrayList<Volume> getVolume(Context context) {ArrayList<Volume> list_storagevolume = new ArrayList<Volume>();StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);try {Method method_volumeList = StorageManager.class.getMethod("getVolumeList");method_volumeList.setAccessible(true);Object[] volumeList = (Object[]) method_volumeList.invoke(storageManager);if (volumeList != null) {Volume volume;for (int i = 0; i < volumeList.length; i++) {try {volume = new Volume();volume.setPath((String) volumeList[i].getClass().getMethod("getPath").invoke(volumeList[i]));volume.setRemovable((boolean) volumeList[i].getClass().getMethod("isRemovable").invoke(volumeList[i]));volume.setState((String) volumeList[i].getClass().getMethod("getState").invoke(volumeList[i]));list_storagevolume.add(volume);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}}} else {Log.e("null", "-null-");}} catch (Exception e1) {e1.printStackTrace();}return list_storagevolume;}/*** SD卡剩余空间*/public static long getSDFreeSize(String path) {try {// 取得SD卡文件路径StatFs sf = new StatFs(path);// 获取单个数据块的大小(Byte)long blockSize = sf.getBlockSizeLong();// 空闲的数据块的数量long freeBlocks = sf.getAvailableBlocksLong();// 返回SD卡空闲大小// return freeBlocks * blockSize; //单位Byte// return (freeBlocks * blockSize)/1024; //单位KBreturn (freeBlocks * blockSize);  // 单位GB} catch (IllegalArgumentException e) {Log.d(TAG, "hello world");}return 0L;}/*** SD卡总容量*/public static long getSDAllSize(String path) {try { // 取得SD卡文件路径StatFs sf = new StatFs(path);// 获取单个数据块的大小(Byte)long blockSize = sf.getBlockSizeLong();// 获取所有数据块数long allBlocks = sf.getBlockCountLong();// 返回SD卡大小// return allBlocks * blockSize; //单位Byte// return (allBlocks * blockSize)/1024; //单位KBreturn (allBlocks * blockSize); // 单位GB} catch (IllegalArgumentException e) {Log.d(TAG, "hello world");}return 0L;}public static String getSDCardDir(Context context) {String sdcardDir = null;StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);Class<?> volumeInfoClazz = null;Class<?> diskInfoClazz = null;try {diskInfoClazz = Class.forName("android.os.storage.DiskInfo");Method isSd = diskInfoClazz.getMethod("isSd");volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");Method getType = volumeInfoClazz.getMethod("getType");Method getDisk = volumeInfoClazz.getMethod("getDisk");Field path = volumeInfoClazz.getDeclaredField("path");Method getVolumes = storageManager.getClass().getMethod("getVolumes");List<Class<?>> result = (List<Class<?>>) getVolumes.invoke(storageManager);for (int i = 0; i < result.size(); i++) {Object volumeInfo = result.get(i);if ((int) getType.invoke(volumeInfo) == 0) {Object disk = getDisk.invoke(volumeInfo);if (disk != null) {if ((boolean) isSd.invoke(disk)) {sdcardDir = (String) path.get(volumeInfo);break;}}}}if (sdcardDir == null) {Log.w(TAG, "sdcardDir null");return null;} else {Log.i(TAG, "sdcardDir " + sdcardDir + File.separator);return sdcardDir + File.separator;}} catch (Exception e) {Log.i(TAG, "sdcardDir e " + e.getMessage());e.printStackTrace();}Log.w(TAG, "sdcardDir null");return null;}public static String getUsbDir(Context context) {String sdcardDir = null;StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);Class<?> volumeInfoClazz = null;Class<?> diskInfoClazz = null;try {diskInfoClazz = Class.forName("android.os.storage.DiskInfo");Method isUsb = diskInfoClazz.getMethod("isUsb");volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");Method getType = volumeInfoClazz.getMethod("getType");Method getDisk = volumeInfoClazz.getMethod("getDisk");Field path = volumeInfoClazz.getDeclaredField("path");Method getVolumes = storageManager.getClass().getMethod("getVolumes");List<Class<?>> result = (List<Class<?>>) getVolumes.invoke(storageManager);for (int i = 0; i < result.size(); i++) {Object volumeInfo = result.get(i);Log.w(TAG, "disk path " + path.get(volumeInfo));if ((int) getType.invoke(volumeInfo) == 0) {Object disk = getDisk.invoke(volumeInfo);Log.w(TAG, "is usb " + isUsb.invoke(disk));if (disk != null) {if ((boolean) isUsb.invoke(disk)) {sdcardDir = (String) path.get(volumeInfo);break;}}}}if (sdcardDir == null) {Log.w(TAG, "usbpath null");return null;} else {Log.i(TAG, "usbpath " + sdcardDir + File.separator);return sdcardDir + File.separator;}} catch (Exception e) {Log.i(TAG, "usbpath e " + e.getMessage());e.printStackTrace();}Log.w(TAG, "usbpath null");return null;}/*存储设备信息封装类*/public static class Volume {protected String path;protected boolean removable;protected String state;public String getPath() {return path;}public void setPath(String path) {this.path = path;}public boolean isRemovable() {return removable;}public void setRemovable(boolean removable) {this.removable = removable;}public String getState() {return state;}public void setState(String state) {this.state = state;}}
}

DocumentsUtils.java

import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.DocumentsContract;
import android.support.v4.provider.DocumentFile;
import android.text.TextUtils;
import android.util.Log;import java.io.File;
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.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);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;}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 {if (src.getParent().equals(dest.getParent())) {res = srcDoc.renameTo(dest.getName());} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {res = DocumentsContract.moveDocument(context.getContentResolver(),srcDoc.getUri(),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;}public static boolean saveTreeUri(Context context, String rootPath, Uri uri) {DocumentFile file = DocumentFile.fromTreeUri(context, uri);Log.d(TAG, "saveTreeUri= " + uri);if (file != null && file.canWrite()) {SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);perf.edit().putString(rootPath, uri.toString()).apply();return true;} else {Log.e(TAG, "no write permission: " + rootPath);}Log.d(TAG, "save_tree_uri=false");return false;}public static String getTreeUri(Context context, String rootPath) {SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);String treeUri = perf.getString(rootPath, "");Log.d(TAG, "treeUri=" + treeUri);return treeUri;}public static boolean checkWritableRootPath(Context context, String rootPath) {File root = new File(rootPath);if (!root.canWrite()) {if (DocumentsUtils.isOnExtSdCard(root, context)) {DocumentFile documentFile = DocumentsUtils.getDocumentFile(root, true, context);return documentFile == null || !documentFile.canWrite();} else {SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);String documentUri = perf.getString(rootPath, "");if (documentUri == null || documentUri.isEmpty()) {return true;} else {DocumentFile file = DocumentFile.fromTreeUri(context, Uri.parse(documentUri));return !(file != null && file.canWrite());}}}return false;}public static boolean isExist(DocumentFile documentFile, String fileName) {boolean exist = false;if (documentFile != null) {DocumentFile file = documentFile.findFile(fileName);if (file != null) {exist = file.exists();}} else {Log.w(TAG, "documentFile null");}return exist;}/*** 在上一级目录下创建目录** @param parentDir 父节点目录 docFile* @param dirName   child 目录名*/public static DocumentFile mkdir(DocumentFile parentDir, String dirName) {if (parentDir != null && !TextUtils.isEmpty(dirName)) {if (!DocumentsUtils.isExist(parentDir, dirName)) {return parentDir.createDirectory(dirName);} else {return parentDir.findFile(dirName);}}Log.d(TAG, "dirName = " + dirName);return null;}/*** @param context      context* @param uri          uri 默认传null ,从sp中读取* @param orgFilePath  被复制的文件路径* @param destFileName 复制后的文件名* @desc 将orgFilePath文件复制到指定SD卡指定路径/storage/xx-xx/hello/*/public static boolean doCopyFile(Context context, Uri uri, String orgFilePath, String destFileName) {// 初始化基础目录,确保录音所在目录存在// 获取到根目录的uriif (uri == null) {String sdPath = StorageUtils.getSDCardDir(context);if (TextUtils.isEmpty(sdPath)) {return false;}String uriStr = DocumentsUtils.getTreeUri(context, sdPath);if (TextUtils.isEmpty(uriStr)) {return false;}uri = Uri.parse(uriStr);}DocumentFile destFile = null;File inFile = new File(orgFilePath);DocumentFile documentFile = DocumentFile.fromTreeUri(context, uri);if (documentFile != null) {DocumentFile[] documentFiles = documentFile.listFiles();for (DocumentFile file : documentFiles) {Log.i(TAG, "file.getName() = " + file.getName());if (file.isDirectory() && file.getName() != null && file.getName().equals("hello")) {destFile = file;}}// 进行文件复制if (destFile != null) {DocumentFile newFile = destFile.createFile("*/*", destFileName);if (newFile != null) {return copyFile(context, inFile, newFile);} else {Log.w(TAG, "newFile is null");}}}return false;}/*** ref: https://www.jianshu.com/p/2f5d80688ca6*/private static boolean copyFile(Context context, File inFile, DocumentFile documentFile) {OutputStream out = null;try {InputStream in = new FileInputStream(inFile);out = context.getContentResolver().openOutputStream(documentFile.getUri());byte[] buf = new byte[1024 * 10];int len;while ((len = in.read(buf)) > 0) {out.write(buf, 0, len);}in.close();return true;} catch (IOException e) {Log.e(TAG, "error = " + e.getMessage());e.printStackTrace();return false;} finally {if (out != null) {try {out.close();} catch (IOException e) {e.printStackTrace();}}}}
}

MainActivity.java

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.support.annotation.Nullable;
import android.support.v4.provider.DocumentFile;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private static final int REQUEST_CODE = 1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);verifyStoragePermissions(this,REQUEST_CODE);String sdPath = StorageUtils.getSDCardDir(this);if (sdPath != null) {String str = DocumentsUtils.getTreeUri(this,sdPath);if (TextUtils.isEmpty(str)) {showOpenDocumentTree(sdPath);}}}private void showOpenDocumentTree(String rootPath) {Intent intent = null;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {StorageManager sm = getSystemService(StorageManager.class);StorageVolume volume = sm.getStorageVolume(new File(rootPath));if (volume != null) {intent = volume.createAccessIntent(null);}}if (intent == null) {intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);}Log.d(TAG,"startActivityForResult...");startActivityForResult(intent, DocumentsUtils.OPEN_DOCUMENT_TREE_CODE);}public void onClick(View v) {String path = StorageUtils.getUsbDir(this);if (!TextUtils.isEmpty(path)) {File[] files = new File(path).listFiles();if (files != null) {for (File file : files) {if (file.getName().endsWith(".zip")) {Log.d(TAG, "file path=" + file.getAbsolutePath());}}}} else {Log.d(TAG, "没有插入U盘");}}public void onClick1(View v) {String rootPath = StorageUtils.getSDCardDir(this);String urlStr = DocumentsUtils.getTreeUri(this, rootPath);Uri uri = Uri.parse(urlStr);writeFile(this, uri);DocumentsUtils.doCopyFile(this,null,"/storage/emulated/0/helloWorld.apk","copyApk.apk");}/*** 在外置TF卡根目录下创建目录*/public void writeFile(Context context, Uri uri) {if (uri == null) {String sdPath = StorageUtils.getSDCardDir(context);if (TextUtils.isEmpty(sdPath)) {Log.w(TAG, "init folder is null path");return;}String uriStr = DocumentsUtils.getTreeUri(context, sdPath);if (TextUtils.isEmpty(uriStr)) {return;}uri = Uri.parse(uriStr);}DocumentFile documentFile = DocumentFile.fromTreeUri(context, uri);// 创建文件,并写入字符串if (!DocumentsUtils.isExist(documentFile, "hello")) {if (documentFile != null) {try {DocumentFile fileHello = documentFile.createDirectory("hello");DocumentFile fileText = fileHello.createFile("text/plain", "test.txt");OutputStream out = null;try {out = context.getContentResolver().openOutputStream(fileText.getUri());out.write("A long time ago...".getBytes());out.close();} catch (Exception e) {e.printStackTrace();}DocumentFile fileImage = fileHello.createFile("image/png", "luncher.png");try {out = context.getContentResolver().openOutputStream(fileImage.getUri());ByteArrayOutputStream baos = new ByteArrayOutputStream();BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher).compress(Bitmap.CompressFormat.JPEG, 100, baos);InputStream inputimage = new ByteArrayInputStream(baos.toByteArray());byte[] bytes = new byte[1024 * 10];int len = 0;while ((len = inputimage.read(bytes)) != -1) {out.write(bytes, 0, len);}out.close();} catch (Exception e) {e.printStackTrace();}} catch (Exception e) {e.printStackTrace();}}}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case DocumentsUtils.OPEN_DOCUMENT_TREE_CODE:if (data != null && data.getData() != null) {Uri uri = data.getData();String rootPath = StorageUtils.getSDCardDir(this);DocumentsUtils.saveTreeUri(this, rootPath, uri);final int takeFlags = data.getFlags()& (Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);Log.d(TAG, "save tree uri = " + uri);// 相当于将url存储下来,下次开启apk不在进行弹框权限请求getContentResolver().takePersistableUriPermission(uri, takeFlags);}break;}}private boolean verifyStoragePermissions(Activity activity, int requestCode) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {int write = activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);int read = activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);//检测是否有权限,如果没有权限,就需要申请if (write != PackageManager.PERMISSION_GRANTED ||read != PackageManager.PERMISSION_GRANTED) {//申请权限activity.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);return false;}}return true;}
}

参考:

https://blog.csdn.net/qq_27531813/article/details/90369993

https://blog.csdn.net/weixin_33734979/article/details/84582017

Android 对于外部TF(SD)卡和U盘的读写操作相关推荐

  1. 存储当时android,Android之外部存储(SD卡)

    *手机的外部存储空间,这个我们可以理解成电脑的外接移动硬盘,U盘也行.所有的Android设备都有两个文件存储区域:"内部"和"外部"存储器.这两个名称来自早期 ...

  2. [sg] Android 6.0 判断SD卡是否挂载,获取SD卡路径,和挂载的U盘进行区分

    这三个问题其实可以归为一个问题,Android 6.0的SD卡和U盘都会挂载在 /mnt/media_rw/路径下,其实问题就是如何获取挂载信息 1. 存储服务StorageManagerServic ...

  3. android 自动下一首,Android播播放完SD卡指定文件夹音乐之后,自动播放下一首

    最近做一个项目,需要连续播放音乐,播放完一首歌之后,自动播放完下一首歌.不要重复播放. 代码如下: package com.example.asyncplayer_ex; import java.io ...

  4. Android文件系统管理——版本内外存所指差异,获得外接SD/U盘路径,在SD卡与U盘间传送文件,两天辛酸泪,收藏不迷路

    在开发一个文件管理系统的路上,总有坑在等着你. 前情提示:因为该系统的使用方需要严格保密文件,导致它失去了无线传输功能,只能通过外设传输文件. 在没接触这个功能之前,我想大家应该都觉得手机自带的存储空 ...

  5. Android模拟器环境下SD卡内容的管理[转]

    Android模拟器环境下SD卡内容的管理 2010-11-30 22:03 by ·风信子·, 2305 阅读, 0 评论, 收藏, 编辑 本文旨在介绍一些Android模拟器下如何对SD卡内容进行 ...

  6. 在Google Android模拟器中使用SD卡(命令行和eclipse环境)

    Android模拟器能够让我们使用fat32格式的磁盘镜像作为SD卡的模拟: 以下所有操作均在windows环境 首先,运行cmd,进入命令行界面(需要预先将你放置android sdk所在目录下的t ...

  7. android 无法显示SD卡目录,Android studio无法在SD卡上创建新目录?

    我使用名为scanlibrary的库来扫描照片,然后将它传递给tess-two来执行OCR过程.问题是,在目录"ScanDemoExample"不被创建因此tessdata文件不会 ...

  8. sd卡有多个android文件夹,android - 如何adb拉出SD卡中存在的文件夹的所有文件

    android - 如何adb拉出SD卡中存在的文件夹的所有文件 我的SD卡中有一个文件夹:/mnt/sdcard/Folder1/Folder2/Folder3/*.jpg Folder1和Fold ...

  9. android u盘自动挂载点,Android2.3实现SD卡与U盘自动挂载的方法

    本文实例讲述了Android2.3实现SD卡与U盘自动挂载的方法.分享给大家供大家参考,具体如下: 在 s3c6410平台上移植android2.3 过程中SD卡总是不能自动挂载. 查阅相关资料,知道 ...

  10. Android模拟器中添加SD卡(转)

    Android模拟器能够让我们使用fat32格式的磁盘镜像作为SD卡的模拟: 以下所有操作均在windows环境 首先,运行cmd,进入命令行界面(需要预先将你放置android sdk所在目录下的t ...

最新文章

  1. php fopen 中文,php fopen用法是什么
  2. Plan with Global Optimization
  3. java日志处理汇总
  4. 在线运行 Linux,强的离谱!
  5. 12种方法返回2个文件路径之间的公共基路径ExtractBasePath
  6. Java但中获取时间将时间转换成字符串格式(年月日格式)
  7. U86650-群鸡乱舞【矩阵乘法】
  8. UVA - 213 Message Decoding
  9. 在5个数中找最大的数,并把他放入MAX单元
  10. 小米公司宣布启动网络恶意营销账号专项整治行动
  11. duilib之源码分析
  12. 数值计算之 插值法(6)样条插值
  13. P8-图标字体-font-awesome-伪类-阿里图标字体icnfont-字体-行高-文本样式
  14. 用机器学习来提升你的用户增长:第八步,Uplift模型
  15. 【不读唐诗,不足以知盛世】杜甫《饮中八仙歌》
  16. Lucene--千锋修改+踩坑版本
  17. IPv6技术详解:基本概念、应用现状、技术实践(上篇)
  18. python代码中 from . import ××× 是什么意思?
  19. VMware虚拟机安装win10系统教程(巨细)
  20. I.MX6ULL ARM驱动开发---platfrom设备驱动

热门文章

  1. OnTime pro for mac(多功能时钟工具)
  2. (附源码)Node.js图书管理小程序 毕业设计 250858
  3. rtspplayer播放器实现
  4. 思科CISCO交换机端口升级方案
  5. 6-3 在一个数组中实现两个堆栈 (22分)
  6. python发邮件被认定为垃圾邮件_Python:脚本发送的邮件被Gmail标记为垃圾邮件
  7. python自动玩2048
  8. java做2048_java版实现2048游戏功能
  9. 代码还是要亲自动手写才行啊
  10. SpCL阅读笔记:Self-paced Contrastive Learning with Hybrid Memory for Domain Adaptive Object Re-ID