跳到主要内容

Android Chrome:accept="image/*" 会丢 EXIF,不加 accept 就不会

📷 同一张照片、同一个页面、同一个 Chrome:<input type="file" accept="image/*"> 选出来的文件没有 EXIF,把 accept 去掉后 EXIF 完好无损。区别不在前端代码,而在于 Chrome 拉起了不同的 Android 选图入口

现象

<!-- ❌ 上传后 EXIF 丢失(至少 GPS 没了,常常是全部没了) -->
<input type="file" accept="image/*" />

<!-- ✅ 上传后 EXIF 完整保留 -->
<input type="file" />

两者选的是磁盘上同一个文件,但前者拿到的 File

  • size 比原图小(或完全不同)
  • name 可能变成 image.jpg1000012345.jpg 这类系统生成的名字
  • exifr.parse(file) 返回 undefined 或缺失 GPS

为什么

accept 在 Android 上不只是个“过滤器”,它会改变 Chrome 发出的 Intent,从而改变是谁在给你文件。

写法Chrome 拉起的东西文件来源EXIF
accept="image/*"系统图片选择器 / Photo Picker / Google 相册MediaStore 媒体数据库返回的副本被脱敏或重编码
accept通用文件选择器(Documents UI / 文件管理器)文件系统上的原始字节完整保留

具体到两条路径:

路径一:图片选择器(加了 accept)

相册原图(EXIF 完整)
MediaStore / Photo Picker / Google Photos Provider
系统生成一份“可分享副本”
Android 10+ 自动删除 GPS(需 ACCESS_MEDIA_LOCATION 才给原图,Chrome 不申请)
• 云端照片/HEIC 还会被下载、缩图、转码成 JPEG

Chrome 拿到的就是这份副本

关键在于:脱敏发生在文件交给浏览器之前。到达 JS 的 File 本身就不带 EXIF,前端怎么读都读不出来。

路径二:文件选择器(不加 accept)

不声明类型时,Chrome 拉起的是通用文件浏览器。它把图片当成普通文件直接读取字节流,不走媒体库的“分享副本”逻辑,因此 EXIF(包括 GPS)原样保留。


验证方法

把两个 input 放在同一个页面上,选同一张照片对比:

<input id="a" type="file" accept="image/*" />
<input id="b" type="file" />
<script type="module">
import exifr from 'https://cdn.jsdelivr.net/npm/exifr/dist/full.esm.mjs'
for (const id of ['a', 'b']) {
document.getElementById(id).onchange = async (e) => {
const file = e.target.files[0]
const exif = await exifr.parse(file, true)
console.log(id, {
name: file.name,
size: file.size,
tags: exif ? Object.keys(exif).length : 0,
gps: await exifr.gps(file),
})
}
}
</script>

典型输出:atags 为 0 或缺 GPS,b 标签齐全,且 b.size 等于原图大小。


解决方案

去掉 accept****。

<input type="file" />

代价是用户能选中非图片文件,所以把类型校验搬到 JS 里:

input.addEventListener('change', () => {
const file = input.files[0]
if (!file) return
// 不依赖 accept,自己校验(建议同时用魔数字节而不只看 MIME)
if (!/^image\//.test(file.type)) {
alert('请选择图片文件')
input.value = ''
return
}
upload(file) // 直传原始 File,不要过 canvas
})

几个配套注意点:

  • 不要用 accept=".jpg,.jpeg,.png" 代替。只要声明了图片类型,部分机型依旧会路由到图片选择器,需实测。
  • 删掉 capture,它会直接调起相机,同样拿不到完整元数据。
  • 拿到原图后不要再用 canvas 压缩canvas.toBlob() 会把刚保住的 EXIF 全部丢掉。
  • 即使不加 accept,如果用户从 Google 相册/云盘入口选图,仍可能拿到副本。引导用户从本地文件夹(DCIM/Camera)选择最稳。

一句话总结

安卓上 accept="image/*" 等于告诉系统“给我一张图片”,系统便给你一份去隐私、可能转码过的副本;不写 accept 则是“给我这个文件”,拿到的就是原始字节,EXIF 自然还在。