因为项目需要,APP要把数据导出到U盘中,下文将介绍下Android5.0以上怎么操作U盘。重点就是写入文件之后,必须调用内核文件同步函数,否则可能存在写入不完全的问题.这里不详叙DocumentFile相关的操作,网上已经有很多了.
private List<StorageVolume> getVolume() {
StorageManager manager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
if (manager != null) {
// 过滤掉不符合需求的设备,USB设备肯定是可移动的,这里注意存储卡也会出来 最好是用对话框来提示用户要操作那个存储设备
return ListUtils.filiter(Utils.getVolume(manager, mActivity), it -> Utils.compatStorageRemoveable(it));
}
return null;
}
private void requestUri(String uuid) {
//记住授权的ID,授权之前建议用界面引导用户如何授权,授权界面是Android自带的,程序不可控!
currentUUID = uuid;
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, DOCUMENT_TREE_REQUEST);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == DOCUMENT_TREE_REQUEST) {
Uri treeUri;
//获取授权之后的Uri
treeUri = data.getData();
if (treeUri != null) {
String value = treeUri.toString();
String uuid = value.substring(value.lastIndexOf("/") + 1).replace("%3A", "");
//判断用户选的设备是否是我们想要的设备!
if (uuid.equals(currentUUID)) {
//将URL存起来 后续访问时可以直接用
PreferencesUtils.put(ACTION_OPEN_DOCUMENT_TREE_URL + uuid, value);
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//固化授权信息,下次该设备插入次APP就无需授权
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
} else {
appendMessage(new Message("您选择的不是移动存储设备的根目录或不是该移动存储设备!", CallBack.ERROR));
}
}
}
}
}
DocumentFile file = Utils.findPath(DocumentFile.fromTreeUri(mActivity, Uri.parse(uri)), "xj_data/" + fileName);
//拿到流了就不需要我多说什么了吧
InputStream inputStream = getContext().getContentResolver().openInputStream(file.getUri());
//与读取文件差不多,先通过DocumentFile的相关方法找到文件,然后打开输出流,把流写进去就行
OutputStream outputStream =context.getContentResolver().openOutputStream(dataFile.getUri());
...
//流写完了必须调用内核方法强制sync到设备上,不然拔快了,文件还没有同步到外部存储设备上
((ParcelFileDescriptor.AutoCloseOutputStream) outputStream).getFD().sync();
package com.cnksi.upan.utils;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.support.v4.provider.DocumentFile;
import android.text.TextUtils;
import com.cnksi.common.utils.ExceptionLog;
import com.cnksi.core.utils.FileUtils;
import com.cnksi.upan.bean.Message;
import java.io.BufferedReader;
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.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Utils {
public static DocumentFile findPath(DocumentFile root, String path) {
String[] split = path.split(File.separator);
if (!root.exists()) {
return null;
} else {
for (String s : split) {
if (TextUtils.isEmpty(s)) {
continue;
}
DocumentFile file = root.findFile(s);
if (file == null || !file.exists()) {
return null;
} else {
root = file;
}
}
}
return root;
}
/**
* 获取存储设备的UUID
*/
public static String compatStorageUUID(StorageVolume volume) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return volume.getUuid();
} else {
try {
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method uuid = storageVolumeClazz.getDeclaredMethod("getUuid");
uuid.setAccessible(true);
return (String) uuid.invoke(volume);
} catch (Exception e) {
}
}
return null;
}
/**
* 获取存储设备是否可移动
*/
public static boolean compatStorageRemoveable(StorageVolume volume) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return volume.isRemovable();
} else {
try {
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method uuid = storageVolumeClazz.getDeclaredMethod("isRemovable");
uuid.setAccessible(true);
return (boolean) uuid.invoke(volume);
} catch (Exception e) {
}
}
return false;
}
/**
* 获取存储设备的描述
*/
public static String compatStorageDesc(StorageVolume volume, Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return volume.getDescription(activity);
} else {
try {
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method desc = storageVolumeClazz.getMethod("getDescription", Context.class);
desc.setAccessible(true);
return (String) desc.invoke(volume, activity);
} catch (Exception ignored) {
}
}
return null;
}
/**
* 获取所有存储设备
*/
public static List<StorageVolume> getVolume(StorageManager manager, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return manager.getStorageVolumes();
} else {
try {
Method getVolume = StorageManager.class.getDeclaredMethod("getVolumeList", int.class, int.class);
StorageVolume[] invoke = (StorageVolume[]) getVolume.invoke(manager, getUserId(context), 0);
return Arrays.asList(invoke);
} catch (Exception ignored) {
}
return null;
}
}
private static int getUserId(Context context) {
try {
Method userId = Context.class.getDeclaredMethod("getUserId");
return (int) userId.invoke(context);
} catch (Exception ignored) {
}
return android.os.Process.myUid() / 100000;
}
}