目录
背景描述:
9.0之后,Google合入一笔patch,去掉了WRITE_MEDIA_STORAGE权限中的sdcard_rw。导致之前的文件读写方式无法对sdcard生效
解决方案:
DocumentFile
//申请目录权限
private void getSdCardPermission(){
StorageManager mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
boolean canFindPath=false;
//获取所有挂载的设备(内部sd卡、外部sd卡、挂载的U盘)
List<StorageVolume> volumes = mStorageManager.getStorageVolumes();
try {
Class<?> storageVolumeClazz = Class
.forName("android.os.storage.StorageVolume");
//通过反射调用系统hide的方法
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
for (int i = 0; i < volumes.size(); i++) {
StorageVolume storageVolume = volumes.get(i);//获取每个挂载的StorageVolume
//通过反射调用getPath、isRemovable
storagePath = (String) getPath.invoke(storageVolume); //获取路径
boolean isRemovableResult = (boolean) isRemovable.invoke(storageVolume);//是否可移除
String description = storageVolume.getDescription(this);
android.util.Log.d(TAG, " i=" + i + " ,storagePath=" + storagePath
+ " ,isRemovableResult=" + isRemovableResult +" ,description="+description);
// i=0 ,storagePath=/storage/emulated/0 ,isRemovableResult=false ,description=内部共享存储空间
// i=1 ,storagePath=/storage/B8EA-15F1 ,isRemovableResult=true ,description=SD卡
if (filePath.contains(storagePath)){
this.storageVolume=storageVolume;
canFindPath=true;
break;
}
}
} catch (Exception e) {
android.util.Log.d("jason", " e:" + e);
}
if (!canFindPath){
this.storageVolume=null;
Toast.makeText(this,"filePath not find",Toast.LENGTH_SHORT).show();
return;
}
if (storageVolume!=null&&canFindPath){
Intent intent = storageVolume.createAccessIntent(getDirectoryName("DCIM"));//
permissionPath=storagePath + File.separator+"DCIM";
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
}
}
//请求成功之后,权限持久化
getContentResolver().takePersistableUriPermission(uriForPermissionPath,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//持久化之后的权限可以通过下面方法获取所有已经获得授权的uriPermission
List<UriPermission> persistedUriPermissions = context.getContentResolver().getPersistedUriPermissions();
//获取授权的uri
Uri uri = persistedUriPermissions.get(0).getUri();
//根据uri获取DocumentFile对象
DocumentFile documentFile= DocumentFile.fromTreeUri(context, parentUri);
//创建目录或者文件
//获取权限路径之后的未知目录结构 /100MEDIA/VIDEO0001_01.mp4.hyperlapse
String replace = outPath.replace(permissionPath, "");
//循环取得各层级目录名,判断是否存在并创建
String[] split = replace.split(File.separator);
for (int i = 0; i <split.length ; i++) {
if (TextUtils.isEmpty(split[i])){
continue;
}
DocumentFile documentFileTmp = documentFile.findFile(split[i]);
if (documentFileTmp!=null&&documentFileTmp.exists()){
documentFile=documentFileTmp;
if (i==split.length -1){
return documentFile;
}
continue;
}
if (split[i].contains(".")&&i==split.length -1){//文件
documentFile = documentFile.createFile(null, split[i]);//创建文件
if (documentFile!=null){
Uri writeUri = documentFile.getUri();
return documentFile;
}
}else{//目录
DocumentFile directory = documentFile.createDirectory(split[i]);
if (directory!=null){
documentFile=directory;
}
}
}
//文件读写
Uri uri = documentFile.getUri();//获取文件的uri
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw");//通过uri获取ParcelFileDescriptor对象
FileDescriptor fileDescriptor = pfd.getFileDescriptor();//得到文件描述
FileInputStream fis = new FileInputStream(fileDescriptor);//文件输入流
FileOutputStream fos = new FileOutputStream(fileDescriptor);//文件输出流
//之后就可以进行正常的文件读写
处理过程中遇到的问题(代码中需要替换的点)
1、权限获取方式
2、文件/目录的创建、删除、重命名方式
3、其他文件相关的类的初始化或者方法调用,不能直接传入path,需要改为FileDescriptor。
3.1、MediaMuxer(@NonNull FileDescriptor fd, @Format int format)//初始化时使用FileDescriptor
3.2、RandomAccessFile raf = new RandomAccessFile(ori, "rws");//不支持,无法初始化RandomAccessFile
//*****Open file channel to read byte
FileChannel fc = raf.getChannel();
3.3、MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(FileDescriptor fd);//setDataSource方法需要传入FileDescriptor
3.4、MediaMetadataRetriever retriever = new MediaMetadataRetriever();
if (!inExtStorage){
retriever.setDataSource(pfd.getFileDescriptor());//setDataSource方法需要传入FileDescriptor
}else{
retriever.setDataSource(mContext, Uri.parse(path));
}
3.5、MovieCreator.build(FileChannel in);//入参的获取方式需要通过DocumentFile,不能直接通过路径
in = new FileInputStream(filePath).getChannel();
DocumentFile documentFileByPath = FileUtils.getDocumentFileByPath(filePath);
pfd = context.getContentResolver().openFileDescriptor(documentFileByPath.getUri(), "rw");
in = new FileInputStream(pfd.getFileDescriptor()).getChannel();
3.6、WritableByteChannel fc = null;//获取方式不能通过path,类似与3.5
3.5和3.6:都属于FileInputStream、FileOutputStream、FileDescriptor、FileChannel的获取方式
结尾:
以上是目前的进展,简单的做一个总结.