android9.0保存文件到外置Sd卡
1. 获取外置sd卡根目录
sd卡根目录是类似/storage/0000-0000 这样的路径,
而用runtime.exec("mount"), 或者是用StorageManager.getStorageVolumes()再用反射path获取的路径是/mnt/media_rw/0000-0000 不可用,需要用下面的方式获取:
public String getExternalDir(Context context) {
String root = null;
File[] dirs = context.getExternalFilesDirs(null);
for (File f : dirs) {
String path = f.getAbsolutePath();
if (!path.contains(Environment.getExternalStorageDirectory().getAbsolutePath())) {
int index = path.indexOf("/Android/data");
if (index != -1) {
root = path.substring(0, index);
break;
}
}
}
return root;
}
2. 外置sd卡写入权限检查
外置sd卡用File.canWrite()检查返回均为false,因此无法用来检查是否可写。
看之前有人写的判断方式:
if (!file.isDirectory()) {
writable = file.createNewFile() && file.delete();
} else {
writable = file.mkdirs() && file.delete();
}
测试是否可写(测过不好用,建议用下面的); 或者把申请权限回调的Uri保存,判断是否存在及对应的DocumentFile是否可写
boolean hasPermission = false;
String uriStr = EdoPreference.getExternalUri();//fetch value in sharedpreferences
if (uriStr != null) {
DocumentFile documentFile = DocumentFile.fromTreeUri(context, Uri.parse(uriStr));
if (documentFile != null) {
hasPermission = documentFile.canWrite();
}
}
3. 申请外置sd卡写入权限
如果>=7.0, 需要用StorageVolume.createAccessIntent()来创建intent,否则intent需设置action: Intent.ACTION_OPEN_DOCUMENT_TREE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StorageManager storageManager = EmailApplication.getContext().getSystemService(StorageManager.class);
StorageVolume volume = storageManager.getStorageVolume(new File(externalDir));
if (volume != null) {
Intent accessIntent = volume.createAccessIntent(null);
if (accessIntent != null) {
extFragment.startActivityForResult(accessIntent, ExtSdFragment.REQ_CODE_EXT);
}
}
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
extFragment.startActivityForResult(intent, ExtSdFragment.REQ_CODE_EXT);
}
其中externalDir是sd卡的任意路径, 在onActivityResult回调中保存Uri的string,以备后用
4. 写入外置sd卡
直接用FileOutputStream写入外置sd卡会失败,原因是对应file的canWrite()返回false, 用户授权后仍然是false(这里太坑了!!!),需要用DocumentFile获取Uri,然后用Uri打开OutputStream:
DocumentFile file = getExtDocumentFile(to.substring(externalDir.length() + 1));
if (file == null) {
return false;
}
try (FileInputStream is = new FileInputStream(from); OutputStream os = EmailApplication.getContext().getContentResolver().openOutputStream(file.getUri())) {
byte[] b = new byte[is.available()];
is.read(b);
os.write(b);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
//根据文件路径获取DocumentFile,需要一层层去找
private DocumentFile getExtDocumentFile(String path,Context context) {
String extSDUri = EdoPreference.getExternalUri();//获取保存的Uri string
if (extSDUri == null) {
return null;
}
DocumentFile file = DocumentFile.fromTreeUri(context, Uri.parse(extSDUri));
if (file == null) {
return null;
}
if (path.contains("/")) {
String[] split = path.split("/");
for (int i = 0; i < split.length; i++) {
DocumentFile df = file.findFile(split[i]);
if (df == null) {
if (i == split.length - 1) {
df = file.createFile("*/*", split[i]);
} else {
df = file.createDirectory(split[i]);
}
}
file = df;
}
} else {
file = file.createFile("*/*", path);
}
return file;
}