首先读取外置TF卡,需要获取到外置TF卡的权限,因为Android 在4.4版本之后 引入了外部存储访问的框架(SAF)。SAF其中的部分功能就是通过其获取对外置TF卡的读写权限,从而操作外置TF卡。
目录
在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; //单位KB
return (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; //单位KB
return (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) {
// 初始化基础目录,确保录音所在目录存在
// 获取到根目录的uri
if (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;
@Override
protected 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();
}
}
}
}
@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;
}
}
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