@Jefferson Palomique
一. 序
最近又看到有人在讨论,微信原图泄露隐私的事情。起因好像是有人在社交媒体上发布了一张照片,然后被网友定位到具体的生活范围,甚至直接定位到居住的小区,楼层门牌号等信息,想想还是很可怕的。
在这个事件中的「网友定位」过程,其实很大一部分信息来自照片内容本身泄露的信息。各种警匪片大家应该也看过,专业人士可以通过一张照片分析出很多有用的信息,例如从窗户阳光的晒入角度,就可以分析出房间的朝向,有一些镜面的反光,也可以分析拍照环境的更多信息等等。
但这里说到微信原图暴露的信息,其实就是我们拍照时,在照片中携带的 Exif 信息。这个 Exif 信息是相机在拍照时,专门记录的一些属性信息和拍摄数据。例如 GPS 定位数据、拍摄时间、拍摄时相机的方向等。
Exif 信息只是拍摄图片文件的固有信息,不光微信原图分享,其实你通过任何形式,将源文件分享出去,都是会携带这些信息。但其中 GPS 涉及到定位,而定位信息又是一个在某些人看来,比较「敏感」的信息。
为什么说是某些人呢?图片携带的定位信息,暴露出去确实可能会造成困扰,但是某些时刻,这又是可以救命的,例如《民警巧用图片定位,解救被骗 CX 组织受害人》之类的新闻,我想大家应该也看过。另外也有很多人反映,不使用原图会导致接收到的图片模糊(被压缩),这也是一部分人偏爱发送「原图」的原因。
一边确实有隐私保护的需求,另一边也有发送无损原图的需要。微信是怎么解决的呢?在发送图片的时候,提供了一个「原图」的选项,将是否发送图片源文件的选择权,交到了用户自己的手里。值得一提的是,微信发送图片,为了传输效率以及隐私的考虑,默认是发送压缩后的图片的,在压缩的过程中,会清理 EXIF 信息,所以如果你找不到「原图」的选项,那么就说明这里只支持发送「压缩图」,例如微信朋友圈。
到这就清晰了,所谓微信原图泄露的隐私,只是图片在拍摄时存入的固有信息 EXIF,而只要通过处理 EXIF 信息就会被抹去。所以如果想对方接收到 EXIF 信息,就发送原图,反之则可以通过压缩处理一下再发送。
本来到这里就算完了,不过作为一个技术向的公众号,我们再继续科普一下照片 EXIF 信息以及在 Android 开发时如何读取和修改它。
二. 图片的 EXIF
2.1 什么是 Exif?
Exif(Exchangeable image file format)表示可交换图像文件格式,是专门记录数码相机拍照时的一些参数,例如定位信息、拍摄设备方向、曝光、色彩等信息,并将这些信息写入图片文件中,以保证在传输时保留这些信息。
Exif 可以被附加到 JPEG、TIFF、RIFF 等文件中,简单来说,图片文件中有一块特殊的区域,可以存放一些额外的 Exif 信息,以保证我们在使用图片时更方便。
这本身没什么坏处,除了 GPS 定位信息会让我们敏感之外,其他信息更多的是为了辅助我们使用。例如前面提到的拍摄照片时,设备的方向信息,就可以保证无论我们拍照时,手机的方向是侧着的或者是倒着的,拍照后在相册中预览时,永远保持正着的原因。
一般的图片处理软件,都可以读取出图片的 Exif 信息,并且支持修改。随便找一个在线查看 Exif 信息的工具网站,就可以查看到我上传图片的 Exif 信息。

图片的 Exif 信息,没有任何的保护,我们可以对其任意的修改。但是这些信息你也看到了,没有修改的意义,通常就是压缩时直接抹去。
2.2 压缩会损失 Exif
压缩会损失掉 Exif 信息,这也就是为什么在不发送原图的时候,接收者是读取不到 Exif 信息的。这很好理解,以现在图片手机摄像头支持的像素来说,一张大画幅的照片,随随便便就是上十 MB 甚至几十 MB,这对发送的网络和存储都是有压力的,所以通常 App 的做法是在发送前,本地做一次图片压缩。
前面提到,Exif 信息会记录拍照时,拍照设备持握的方向,例如有时我们会将手机倒过来拍全身照。这照片在相册展示时,永远都是正确的方向,我不会得到一个头朝下的照片。
如果压缩会导致 Exif 丢失,为什么压缩后的图片,在显示时依然可以保证显示方向的正确?
这就要说到图片压缩时的策略,我们就拿 Android 下比较出名的开源图片压缩库 Luban 举例,Github 上很多图片压缩库都是借鉴或者引用它来实现的。
在 Luban 的 Engine.java
文件中,可以找到相关的代码,逻辑很简单,就是在压缩前,先将图片按照 Exif 中记录的方向,旋转后再进行处理。

这也就是为什么图片压缩时,虽然抹去了 Exif 信息,但是图片显示的方向依然是正确的原因。
2.3 压缩后保留 Exif 信息
保留 Exif 信息这种需求,我确实想不到有什么场景需要在压缩后,保留此信息的。
但是如果有必要的话,最简单的处理方式,就是在压缩前,将图片的 Exif 读取存储,压缩后再写入图片中。
那么这就又涉及到,我们如何编码读取和写入图片的 Exif 信息。
三. 操作 Exif 信息
3.1 使用 ExifInterface
在 Android 中,需要 ExifInterface
来读取 Exif 信息,如果你直接在 AS 中搜索这个类,可以发现在 android.media
包下,确实有一个 ExifInterface
,但是我不建议使用它。
自从 Android Support 25.1.0 开始,又添加了一个新的支持库:ExifInterface。这是由于 Android 7.1 对 ExifInterface 做了重大修改,因此建议使用此 Support 包,它最低支持到 Api 9+。
随着 AndroidX 的发布,对 ExifInterface 也做了迁移支持,只不过现在的版本还是 Beta01。

使用方式并没有什么太大的差异,完全取决于你项目的要求,这里举例就使用最新的 28.+ 了
api "com.android.support:exifiinterface:28.+"
Support 包和 android.media
中的 ExifInterface
基本操作,都是类似的,都提供了对指定图片的 Exif 信息进行读写的功能,区别在于 Support 包中包含了 140 多个不同的属性,而其中近 100 个是 android 7.1 中新增的。
3.2 获取 ExifInterface
ExifInterface 存在两个构造函数,可以传递一个图片文件路径或者图片的 InputStream。

1、使用 InputStream 获得的 ExifInterface 无法被修改,而直接读取的图片文件,则可以修改。
2、ExifInterface 无法处理远端的 InputStream,例如是从 HttpURIConnection 返回的输入流,所以这里建议使用 content:// 或者 file:// 这种 Uri 路径。
3.3 读取 Exif 信息
获得 ExifInterface 对象之后,就可以对其进行操作。
大多数的 Exif 属性,只需要视情况使用 getAttributeInt()
、getAttributeDouble()
、getAttribute()
(适用于 String)。它们分别表示不同类型的属性。这些方法接收一个 String 类型的参数,这些参数都以常量的形式,以 TAG_Xxx 为开头,被标记在 ExifInterface 中。
具体想知道不同的 TAG_Xxx 需要使用什么方法获取,可以直接看文档。

其中注释就已经标记了该属性代表的类型。
下面举个最常见的例子,获取图片的拍摄方向,用于在显示的时候进行旋转。

当然,还有一些其它比较重要的信息,例如谣传微信原图暴露的位置信息,可以通过 getLatLong()
方法获取到一个 float 的数组,分别表示经度和维度,getAltitude()
获取拍摄的海拔高度,单位是 米 。还有一些图片,如果自带缩略图,可以使用 getThumbnail()
方法获取到。更多操作,详见代码文档,这里就不一一举例了。
需要注意的是,Exif 是一个不严谨的数据,它不存在任何必须的标记字段,每个标记字段值,都是可选的,所以我们在读取的时候,一定要考虑读取时的异常处理。
3.4 写入 Exif 信息
ExifInterface 其实是不可信的,它只能作为一个参考。因为任何程序都可以对它进行修改。
修改 Exif 信息可以使用 setAttribute()
方法,它接收一个 key-value 的键值对。用于标记待修改的 Tag 和最终修改后的值。在修改完成之后,还需调用 saveAttributes()
方法,否者不会将设置的 Exif 信息写入到图片文件中。
还有一点需要注意,虽然文档中表明,Exif 信息是一个弱校验的数据,但是它对 TAG 的值是有要求的,如果不是它本身定义的值,保存并不会报错,但是读取的时候,会返回 null 。
对 Exif 的使用,这里就不单独举例了,有兴趣可以看看我之前的文章《Android 读取 Exif 示例》
四. 小结时刻
到这里你应该就清楚了,微信原图泄露的只是照相机 App 在拍照时,对图片写入的固有信息,并没有什么太多的秘密,这些信息在图片压缩时就会被抹去。
最后再总结一下:
图片在拍照时,会写入 Exif 信息到图片文件中,直接发送文件会保留此信息。
99% 的图片压缩,都会抹去 Exif 信息。
Android 下读取 Exif 信息,可以使用 ExifInterface。
本文对你有帮助吗?留言、转发、点好看是最大的支持,谢谢!
「联机圆桌」????推荐我的知识星球,一年 50 个优质问题,上桌联机学习。
公众号后台回复成长『成长』,将会得到我准备的学习资料。
