该应用程序使用ACTION_OPEN_DOCUMENT_TREE将用户发送到 SAF 选择器:
void openStoragePicker() {
String messageTitle = "Choose directory app to use";
Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(Intent.createChooser(intent, messageTitle), Dry.REQUEST_CHOOSE_APP_DIR);
}
在onActivityResult中,我们获取持久权限并存储 Uri 的字符串:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
switch (resultCode) {
case Activity.RESULT_OK:
if (requestCode == Dry.REQUEST_CHOOSE_APP_DIR) {
if (resultData == null) {
Log.d(Dry.TAG, "result data null");
} else {
if (resultData.getData() != null) {
Uri uri = resultData.getData();
Storage.releasePersistedPermissions(this);
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Storage.setSharedPrefString(uri.toString(), Storage.SHARED_PREF_APP_DIR_URI, this);
dbw.clearAlreadyPlayed();
}
}
}
break;
case Activity.RESULT_CANCELED:
//
break;
}
}
我们在需要时重新创建树 Uri:
static Uri getTheDir(Context context) {
String result = Storage.getSharedPrefString(SHARED_PREF_APP_DIR_URI, context);
if (result == DEFAULT_SHARED_PREF_STRING) {
return null;
}
Uri dirUriParsed = Uri.parse(Uri.decode(result));
Log.d(Dry.TAG, "the dir uri parsed: " + dirUriParsed.toPath());
return dirUriParsed;
}
我们想要列出文件,我们可以使用此处显示的模式。
static ArrayList<String> getFiles(Context context) {
ArrayList<String> fileStrings = new ArrayList<>();
Uri rootUri = getTheDir(context);
if (rootUri == null) {
return fileStrings;
}
long startTime = System.currentTimeMillis();
ContentResolver contentResolver = context.getContentResolver();
String theDocToReturnChildrenFor = DocumentsContract.getTreeDocumentId(rootUri);
Log.d(Dry.TAG, "theDocToReturnChildrenFor: " + theDocToReturnChildrenFor);
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, theDocToReturnChildrenFor);
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while(!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0);
Cursor c = contentResolver.query(childrenUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null);
try {
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
if (isDirectory(mime)) {
if (Arrays.asList(SUBDIRECTORIES_TO_OMIT).contains(name)) {
continue;
}
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
dirNodes.add(newNode);
} else {
for (String ext: SUPPORTED_FILE_EXTENSIONS) {
if (name.endsWith(ext)) {
fileStrings.add(docId);
break;
}
}
}
}
} finally {
closeQuietly(c);
}
}
Log.d(Dry.TAG, "fileStrings length: " + fileStrings.size() + "time spent building song list: " + ((System.currentTimeMillis() - startTime) / 1000.0) + "s");
return fileStrings;
}
但是,只有当目录恰好是存储卷中的顶级目录时,这才会按预期工作。如果用户选择的目录不是卷根的直接子目录,那么当我们尝试时DocumentsContract.getTreeDocumentId(rootUri)
,它返回的不是该 URI 的文档 ID,而是卷根之前其最高父级的文档 ID!
打印重建的 Uri 的日志调用给出以下输出:
解析的 dir uri:/tree/primary:a test dir/a child test dir/3rd level dir
但是另一个打印文档 ID 的日志调用会打印:
theDocToReturnChildrenFor:主要:测试目录
我在做吗?这是Android错误吗?我注意到这个问题描述了这种方法的完全相同的行为。通过遵循既定的递归列表模式可以解决该问题,但是,该用户说:
几乎就像 getTreeDocumentId(rootUri) 正在返回 getRootId(rootUri) 应该返回的内容。
这种方法的文档没有帮助,它们很简短并且有错字,意思不清楚。DocumentsContract.getTreeDocumentId 文档。
该应用的目标SDK为30。设备Android版本也是api 30(Android 11)。
如果有人可以帮助我为用户选择的目录获取正确的文档 ID,我将不胜感激。