Android 5.0 DocumentFile из дерева URI
Хорошо, я искал и искал, и никто не получил мой точный ответ, или я пропустил его. Мои пользователи выбирают каталог по:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, READ_REQUEST_CODE);
В своей деятельности я хочу уловить реальный путь, который кажется невозможным.
protected void onActivityResult(int requestCode, int resultCode, Intent intent){
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
//Marshmallow
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
//Set directory as default in preferences
Uri treeUri = intent.getData();
//grant write permissions
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//File myFile = new File(uri.getPath());
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
Папка, которую я выбрал, находится по адресу:
Device storage/test/
Я попробовал все следующие способы получить точное имя пути, но безрезультатно.
File myFile = new File (uri.getPath());
//returns: /tree/1AF6-3708:test
treeUri.getPath();
//returns: /tree/1AF6-3708:test/
pickedDir.getName()
//returns: test
pickedDir.getParentFile()
//returns: null
В основном мне нужно включить /tree/1AF6-3708:
в /storage/emulated/0/
или как там каждое устройство называет это место хранения. Все остальные доступные варианты возврата /tree/1AF6-37u08:
также.
Есть две причины, по которым я хочу сделать это таким образом.
1) В моем приложении я сохраняю расположение файла в качестве общего предпочтения, поскольку оно зависит от пользователя. У меня есть довольно много данных, которые будут загружены и сохранены, и я хочу, чтобы пользователь мог разместить их там, где они хотят, особенно если у них есть дополнительное место для хранения. Я устанавливаю значение по умолчанию, но мне нужна универсальность, а не выделенное расположение:
Device storage/Android/data/com.app.name/
2) В 5.0 я хочу разрешить пользователю получать права на чтение / запись для этой папки, и это, кажется, единственный способ сделать это. Если я могу получить разрешения на чтение / запись из строки, которая исправит эту проблему.
Все решения, которые мне удалось найти, относятся к Mediastore, что мне точно не помогает. Я должен что-то упустить, или я должен был застеклить это. Любая помощь будет оценена. Благодарю.
6 ответов
Это даст вам фактический путь к выбранной папке (при условии, что выбранная папка находится во внешнем хранилище или на внешней SD-карте)
Uri treeUri = data.getData();
String path = FileUtil.getFullPathFromTreeUri(treeUri,this);
где FileUtil - следующий класс
public final class FileUtil {
static String TAG="TAG";
private static final String PRIMARY_VOLUME_NAME = "primary";
@Nullable
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) return null;
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
if (volumePath == null) return File.separator;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator))
return volumePath + documentPath;
else
return volumePath + File.separator + documentPath;
}
else return volumePath;
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
}
Это дополнение к ответу @Anonymous для Android 11.
@TargetApi(Build.VERSION_CODES.R)
private static String getVolumePath_SDK30(final String volumeId, Context context) {
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
if (mStorageManager == null) return null;
List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
for (StorageVolume storageVolume : storageVolumes) {
String uuid = storageVolume.getUuid();
Boolean primary = storageVolume.isPrimary();
if (primary == null) return null;
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return storageVolume.getDirectory().getPath();
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return storageVolume.getDirectory().getPath();
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
В своей деятельности я хочу уловить реальный путь, который кажется невозможным.
Это потому, что не может быть реального пути, не говоря уже о том, к которому вы можете получить доступ. Существует множество возможных провайдеров документов, немногие из которых будут иметь все свои документы локально на устройстве, и немногие из них будут иметь файлы во внешнем хранилище, где вы сможете работать с ними.
У меня есть довольно много данных, которые будут загружены и сохранены, и я хочу, чтобы пользователь мог разместить их там, где они хотят
Затем используйте API-интерфейсы Storage Access Framework вместо того, чтобы думать, что документы / деревья, которые вы получаете из Storage Access Framework, всегда локальны. Или не использовать ACTION_OPEN_DOCUMENT_TREE
,
В 5.0 я хочу разрешить пользователю получать права на чтение / запись для этой папки
Это обрабатывается поставщиком хранилища как часть взаимодействия пользователя с этим поставщиком хранилища. Вы не вовлечены.
public static String findFullPath(String path) {
String actualResult="";
path=path.substring(5);
int index=0;
StringBuilder result = new StringBuilder("/storage");
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) != ':') {
result.append(path.charAt(i));
} else {
index = ++i;
result.append('/');
break;
}
}
for (int i = index; i < path.length(); i++) {
result.append(path.charAt(i));
}
if (result.substring(9, 16).equalsIgnoreCase("primary")) {
actualResult = result.substring(0, 8) + "/emulated/0/" + result.substring(17);
} else {
actualResult = result.toString();
}
return actualResult;
}
Я пытался добавить каталог сохранения по умолчанию до того, или пользователь не выберет пользовательский каталог с помощью пользовательского интерфейса SAF на экране настроек моего приложения. Пользователи могут пропускать выбор папки, и приложение может зависнуть. Чтобы добавить папку по умолчанию в память устройства, вы должны
DocumentFile saveDir = null;
saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
String uriString = saveDir.getUri().toString();
List<UriPermission> perms = getContentResolver().getPersistedUriPermissions();
for (UriPermission p : perms) {
if (p.getUri().toString().equals(uriString) && p.isWritePermission()) {
canWrite = true;
break;
}
}
// Permitted to create a direct child of parent directory
DocumentFile newDir = null;
if (canWrite) {
newDir = saveDir.createDirectory("MyFolder");
}
if (newDir != null && newDir.exists()) {
return newDir;
}
Этот фрагмент создаст каталог в основной памяти устройства и предоставит разрешения на чтение / запись для этой папки и подпапок. Вы не можете напрямую создать иерархию MyFolder/MySubFolder, вам следует снова создать другой каталог.
Вы можете проверить, есть ли у этого каталога разрешение на запись, насколько я видел на 3 устройствах, он возвращает true, если он создан с использованием DocumentFile
вместо File
учебный класс. Это простой способ создания и предоставления прав на запись для Android >= 5.0 без использования ACTION_OPEN_DOCUMENT_TREE
public static String findFullPath(String path) {
String actualResult="";
path=path.substring(5);
int index=0;
StringBuilder result = new StringBuilder("/storage");
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) != ':') {
result.append(path.charAt(i));
} else {
index = ++i;
result.append('/');
break;
}
}
for (int i = index; i < path.length(); i++) {
result.append(path.charAt(i));
}
if (result.substring(9, 16).equalsIgnoreCase("primary")) {
actualResult = result.substring(0, 8) + "/emulated/0/" + result.substring(17);
} else {
actualResult = result.toString();
}
return actualResult;
}
эта функция дает мне абсолютный путь от дерева uri. это решение работает на большинстве устройств, которые я тестировал на более чем 1000 устройствах. он также дает нам правильный абсолютный путь к папке, которая содержится на карте памяти или OTG.
How it Works?
в основном у большинства устройств путь начинается с префикса /Storage/. а средняя часть пути содержит имя смонтированной точки, например / emulated / 0 / для внутреннего хранилища, или некоторую строку, например /C0V54440/ etc (просто пример). и последний сегмент - это путь от корня хранилища до папки типа / movie / piratesofthecarribian
Итак, путь, который мы построили из:- /tree/primary:movie/piratesofthecarribian, выглядит так:- /storage/emulated/0/movie/piratesofthecarribian
Вы можете найти больше информации о моем репозитории на github. посетите там, чтобы получить код Android о том, как преобразовать древовидный uri в фактический абсолютный путь.
gihub ( https://github.com/chetanborase/TreeUritoAbsolutePath)