Android N定制设备自定义Document Provider无法识别USB OTG求助
问题分析与解决方案
看起来你的自定义Document Provider没显示USB内容的核心问题在于路径指向错误和Document ID与实际USB存储路径未关联,咱们一步步拆解修复:
1. 核心问题定位
- 你的
queryChildDocuments方法里,把父路径拼到了APP内部存储的getFilesDir()下,这完全不是USB存储的位置,自然看不到USB文件 queryRoots中设置的COLUMN_DOCUMENT_ID为"/",没有和实际的USB挂载路径绑定,导致后续无法正确定位USB目录
2. 修复步骤与代码示例
第一步:正确获取USB存储的挂载路径
Android N上USB OTG存储通常挂载在/storage/[USB名称]或/mnt/media_rw/[UUID]目录下,我们需要先拿到这个路径:
private List<String> getUsbMountPaths() { List<String> usbPaths = new ArrayList<>(); StorageManager storageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE); if (storageManager != null) { List<StorageVolume> volumes = storageManager.getStorageVolumes(); for (StorageVolume volume : volumes) { // 判断是否为可移除的USB存储(非模拟SD卡) if (volume.isRemovable() && !volume.isEmulated()) { try { // Android N需用反射获取路径(API26+才有getPath()方法) Method pathMethod = StorageVolume.class.getMethod("getPath"); String usbPath = (String) pathMethod.invoke(volume); usbPaths.add(usbPath); } catch (Exception e) { e.printStackTrace(); // 备选方案:遍历/mnt/media_rw目录找USB挂载点 File mediaRwDir = new File("/mnt/media_rw"); if (mediaRwDir.exists() && mediaRwDir.isDirectory()) { for (File dir : mediaRwDir.listFiles()) { if (dir.isDirectory() && !dir.getName().startsWith("emulated")) { usbPaths.add(dir.getAbsolutePath()); } } } } } } } return usbPaths; }
第二步:修正queryRoots方法,关联USB路径
把根节点指向USB存储,用自定义格式的Document ID绑定实际路径:
@Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); List<String> usbPaths = getUsbMountPaths(); // 遍历所有连接的USB设备(这里示例处理第一个,多设备可循环添加) if (!usbPaths.isEmpty()) { String usbRootPath = usbPaths.get(0); MatrixCursor.RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, "usb_root_0"); // 唯一标识USB根节点 row.add(Root.COLUMN_ICON, R.drawable.ic_menu_manage); row.add(Root.COLUMN_TITLE, "USB驱动器"); row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_LOCAL_ONLY); row.add(Root.COLUMN_SUMMARY, "已连接的USB存储"); row.add(Root.COLUMN_DOCUMENT_ID, "usb:" + usbRootPath); // 用"usb:"前缀标识USB路径 row.add(Root.COLUMN_MIME_TYPES, "*/*"); } Uri rootsUri = DocumentsContract.buildRootsUri(BuildConfig.DOCUMENTS_AUTHORITY); getContext().getContentResolver().notifyChange(rootsUri, null); return result; }
第三步:修正queryChildDocuments方法,解析USB路径
根据Document ID找到对应的USB目录,遍历其中的文件:
@Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); String parentPath; // 解析Document ID对应的实际路径 if (parentDocumentId.startsWith("usb:")) { parentPath = parentDocumentId.substring(4); // 去掉"usb:"前缀 } else { // 子目录的Document ID格式为"usb:/storage/UsbDriveA/xxx" parentPath = parentDocumentId.substring(4); } File dir = new File(parentPath); if (!dir.exists() || !dir.isDirectory()) { return result; } for (File file : dir.listFiles()) { String documentId = "usb:" + file.getAbsolutePath(); includeFile(result, documentId, file); // 完善文件属性填充 } return result; }
第四步:完善includeFile方法,正确填充文件属性
需要把文件的名称、大小、类型等信息正确写入Cursor:
private void includeFile(MatrixCursor result, String documentId, File file) { MatrixCursor.RowBuilder row = result.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, documentId); row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); row.add(Document.COLUMN_SIZE, file.length()); // 区分目录和文件的MIME类型 String mimeType = file.isDirectory() ? DocumentsContract.Document.MIME_TYPE_DIR : getFileMimeType(file); row.add(Document.COLUMN_MIME_TYPE, mimeType); row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); // 设置文件/目录的操作权限 int flags = file.isDirectory() ? Document.FLAG_DIR_SUPPORTS_CREATE : Document.FLAG_SUPPORTS_READ; row.add(Document.COLUMN_FLAGS, flags); } private String getFileMimeType(File file) { String extension = MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()); String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); return mimeType != null ? mimeType : "*/*"; }
第五步:权限与启动Intent优化
- 在
AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
- 启动SAF时,用
ACTION_OPEN_DOCUMENT_TREE更适合浏览目录:
if (id == R.id.nav_file_explorer) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, 1); }
3. 额外说明
- 如果设备支持多USB同时接入,可以在
queryRoots中循环添加多个根节点 - Android N对外部存储访问限制较严,确保你的APP已获得存储权限
- 测试时可以先手动确认USB的实际挂载路径(比如通过adb shell查看),验证路径是否正确
内容的提问来源于stack exchange,提问作者reverb
相关产品推荐
相关产品推荐

