如何确定内容实际MIME类型?修改扩展名后如何获取文件真实MIME类型?
嘿,我懂你现在的困扰——你用getContentResolver.getType()这个方法,在文件扩展名正常的时候没问题,但只要改了后缀就失效了对吧?这很正常,因为这个方法本质上很多时候是依赖文件扩展名或者系统媒体数据库里的记录来返回MIME类型的,不是真正去解析文件内容本身。
要解决“修改扩展名后获取真实MIME类型”的问题,核心思路是直接解析文件内容的特征标识,而不是依赖扩展名。下面给你两个实用的解决方案:
几乎所有标准文件类型的开头几个字节都有固定的“魔数”(Magic Number),比如JPEG的开头是FF D8 FF,PNG是89 50 4E 47。我们可以读取文件的前几个字节,通过匹配这些魔数来判断真实MIME类型。
针对你的Uri场景,修改后的代码如下:
public static String getRealMimeTypeFromUri(Context context, Uri uri) { InputStream inputStream = null; try { // 从Uri获取输入流 inputStream = context.getContentResolver().openInputStream(uri); if (inputStream == null) { return null; } // 读取文件前8个字节(足够覆盖大部分常见文件的魔数) byte[] headerBytes = new byte[8]; int bytesRead = inputStream.read(headerBytes); if (bytesRead <= 0) { return null; } // 匹配各类文件的魔数 if (headerBytes[0] == (byte) 0xFF && headerBytes[1] == (byte) 0xD8) { return "image/jpeg"; } else if (headerBytes[0] == (byte) 0x89 && headerBytes[1] == (byte) 0x50 && headerBytes[2] == (byte) 0x4E && headerBytes[3] == (byte) 0x47) { return "image/png"; } else if (headerBytes[0] == (byte) 0x47 && headerBytes[1] == (byte) 0x49 && headerBytes[2] == (byte) 0x46) { return "image/gif"; } else if (headerBytes[0] == (byte) 0x25 && headerBytes[1] == (byte) 0x50 && headerBytes[2] == (byte) 0x44 && headerBytes[3] == (byte) 0x46) { return "application/pdf"; } else if (headerBytes[0] == (byte) 0x50 && headerBytes[1] == (byte) 0x4B && headerBytes[2] == (byte) 0x03 && headerBytes[3] == (byte) 0x04) { return "application/zip"; } else if (headerBytes[0] == (byte) 0x00 && headerBytes[1] == (byte) 0x00 && headerBytes[2] == (byte) 0x01 && headerBytes[3] == (byte) 0xBA) { return "video/mp4"; } // 如果没匹配到, fallback 到系统方法(作为兜底) return context.getContentResolver().getType(uri); } catch (IOException e) { e.printStackTrace(); // 异常时也兜底到系统方法 return context.getContentResolver().getType(uri); } finally { // 记得关闭输入流 try { if (inputStream != null) { inputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } }
这个方法的优点是轻量,不需要引入第三方库,适合只需要支持常见文件类型的场景。你可以根据需求继续添加更多文件类型的魔数匹配规则。
如果需要支持大量不常见的文件类型,手动写魔数会很繁琐,这时候可以用Apache Tika——一个专门用于检测文件类型和提取内容的库,它能自动识别几乎所有标准文件的MIME类型。
首先在你的build.gradle中添加依赖:
implementation 'org.apache.tika:tika-core:2.9.1'
然后编写解析代码:
public static String getRealMimeTypeWithTika(Context context, Uri uri) { InputStream inputStream = null; try { inputStream = context.getContentResolver().openInputStream(uri); if (inputStream == null) { return null; } Tika tika = new Tika(); // Tika会自动解析文件内容返回真实MIME类型 return tika.detect(inputStream); } catch (IOException e) { e.printStackTrace(); return context.getContentResolver().getType(uri); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } }
这个方法的优点是覆盖范围极广,不用自己维护魔数规则,但缺点是会增加APK的体积(Tika core库大概几百KB),适合对文件类型支持要求高的应用。
再补充解释下:getContentResolver.getType(uri)在处理本地文件Uri时,通常有两种逻辑:
- 如果是媒体文件,会查询系统的媒体数据库,而数据库里的MIME类型是文件被扫描时根据扩展名记录的,修改扩展名后数据库不会自动更新;
- 如果是非媒体文件,可能直接通过Uri对应的文件扩展名来推断MIME类型。
所以不管哪种情况,它都没有真正解析文件内容,自然在扩展名被修改后返回错误的结果。
内容的提问来源于stack exchange,提问作者jack

