问题
Alright, I've searched and searched and no one has my exact answer, or I missed it. I'm having my users select a directory by:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, READ_REQUEST_CODE);
In my activity I want to capture the actual path, which seems to be impossible.
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);
The folder I selected is at:
Device storage/test/
I've tried all of the following ways to get an exact path name, but to no avail.
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
Basically I need to turn /tree/1AF6-3708:
into /storage/emulated/0/
or whatever each device calls it's storage location. All other available options return /tree/1AF6-37u08:
also.
There are 2 reasons I want to do it this way.
1) In my app I store the file location as a shared preference because it is user specific. I have quite a bit of data that will be downloaded and stored and I want the user to be able to place it where they want, especially if they have an additional storage location. I do set a default, but I want versatility, rather than the dedicated location of:
Device storage/Android/data/com.app.name/
2) In 5.0 I want to enable the user to get read/write permissions to that folder and this seems the only way to do that. If I can get read/write permissions from a string that would fix this issue.
All solutions I've been able to find relate to Mediastore, which doesn't help me exactly. I have to be missing something somewhere or I must have glazed over it. Any help would be appreciated. Thanks.
回答1:
This will give you the actual path of the selected folder (assuming the selected folder resides in external storage or external sd card)
Uri treeUri = data.getData();
String path = FileUtil.getFullPathFromTreeUri(treeUri,this);
where FileUtil is the following class
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;
}
}
回答2:
In my activity I want to capture the actual path, which seems to be impossible.
That's is because there may not be an actual path, let alone one you can access. There are many possible document providers, few of which will have all their documents locally on the device, and few of those that do will have the files on external storage, where you can work with them.
I have quite a bit of data that will be downloaded and stored and I want the user to be able to place it where they want
Then use the Storage Access Framework APIs, rather than thinking that documents/trees that you get from the Storage Access Framework are always local. Or, do not use ACTION_OPEN_DOCUMENT_TREE
.
In 5.0 I want to enable the user to get read/write permissions to that folder
That is handled by the storage provider, as part of how the user interacts with that storage provider. You are not involved.
回答3:
I was trying to add a default save directory before or if user does not select a custom directory using SAF UI in preferences screen of my app. It's possible for users to miss selecting a folder and app may crash. To add a default folder in device memory you should
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;
}
This snippet will create a directory inside main memory of device and grant read/write permissions for that folder and sub-folders. You can't directly create MyFolder/MySubFolder hierarchy, you should create another directory again.
You can check if that directory has permission to write, as far i seen on 3 devices, it returns true if it's created using DocumentFile
instead of File
class. This is a simple method for creating and granting write permission for Android >= 5.0 without using ACTION_OPEN_DOCUMENT_TREE
来源:https://stackoverflow.com/questions/34927748/android-5-0-documentfile-from-tree-uri