解决ActivityResultContracts.CreateDocument()获取文件类型为null问题
问题原因
拿不到扩展名的核心原因有两个:
- 你使用了
ActivityResultContracts.CreateDocument的无参构造方法,该方法已经被废弃,没有传入默认MIME类型时,系统返回的content://Uri 大概率不会携带正确的MIME信息,直接导致contentResolver.getType(uri)返回null。 - 依赖
MimeTypeMap从MIME类型反推扩展名本身就不可靠,不同文档提供方(系统文件管理器、第三方文件App、云盘等)返回的MIME类型可能错漏、缺失,完全不适合作为判断文件格式的依据。
正确实现方案
不要从MIME类型反推格式,直接通过ContentResolver查询Uri对应的DISPLAY_NAME字段拿到完整文件名,再从文件名中解析扩展名,这个方案对所有合法的content Uri生效,不受提供方实现影响。
第一步:修正Launcher初始化
给CreateDocument传入默认MIME类型,比如保存图片就传"image/*",如果固定保存png可以传"image/png":
private ActivityResultLauncher<String> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("image/*"), uri -> { if (uri == null) return; Bitmap bitmap = cameraViewModel.getImage(); if (bitmap == null) return; ContentResolver contentResolver = mainActivity.getContentResolver(); // 拿到完整文件名 String fileName = getFileNameFromUri(contentResolver, uri); // 解析扩展名 String ext = getFileExtension(fileName); // 匹配压缩格式 Bitmap.CompressFormat format = getCompressFormatByExt(ext); // 自动关流,不用手动写close try (OutputStream outputStream = contentResolver.openOutputStream(uri)) { if (outputStream != null) { // 质量参数:PNG是无损的,质量参数会被忽略,JPEG/WebP按需求传0-100 int quality = (format == Bitmap.CompressFormat.JPEG) ? 90 : 100; bitmap.compress(format, quality, outputStream); } } catch (IOException e) { e.printStackTrace(); } });
第二步:实现工具方法
从Uri查询文件名
private String getFileNameFromUri(ContentResolver resolver, Uri uri) { String fileName = null; // 处理content协议的Uri if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { try (Cursor cursor = resolver.query( uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null )) { if (cursor != null && cursor.moveToFirst()) { int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (nameIndex != -1) { fileName = cursor.getString(nameIndex); } } } catch (Exception e) { e.printStackTrace(); } } // 兜底处理file协议等特殊情况 if (fileName == null) { fileName = uri.getLastPathSegment(); } return fileName; }
从文件名解析扩展名
private String getFileExtension(String fileName) { if (fileName == null) return ""; int dotIndex = fileName.lastIndexOf("."); if (dotIndex == -1 || dotIndex == fileName.length() - 1) { return ""; } return fileName.substring(dotIndex + 1).toLowerCase(Locale.ROOT); }
根据扩展名匹配压缩格式
private Bitmap.CompressFormat getCompressFormatByExt(String ext) { switch (ext) { case "jpg": case "jpeg": return Bitmap.CompressFormat.JPEG; case "webp": // 适配不同SDK版本的WebP格式,API30以上可以用无损WEBP if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return Bitmap.CompressFormat.WEBP_LOSSLESS; } else { return Bitmap.CompressFormat.WEBP; } case "png": default: // 默认用PNG,支持透明通道,兼容性最好 return Bitmap.CompressFormat.PNG; } }
第三步:调用Launcher时传入默认文件名
建议在调用launch时传入带默认扩展名的文件名提示,减少用户手动输入错误的概率:
// 比如默认建议保存为png格式,文件名带时间戳避免重复 activityResultLauncher.launch("IMG_" + System.currentTimeMillis() + ".png");
注意事项
- 不要依赖
contentResolver.getType()的返回值判断格式,这个值完全由文档提供方决定,很多场景下会返回null或者错误类型。 - 流操作统一用
try-with-resources语法,不需要手动调用close(),避免异常场景下流泄漏。 - PNG格式是无损压缩,
compress方法的quality参数对PNG无效,传100即可。 - 如果需要支持更多格式(比如HEIC),可以自己在
getCompressFormatByExt方法里加对应判断,注意API版本兼容性。
内容的提问来源于stack exchange,提问作者user330844
相关产品推荐
相关产品推荐

