即时通讯头像策略

May 27, 2019

一、前言

当今几乎所有流行移动应用,无论是新闻资讯、大众娱乐,还是即时通讯,充分利用人类易对视觉冲击做出反应的特点,通过大量的图片和视频组合内容,提高用户对应用的依赖程度。本次研究重点圈定在一个特定领域内,即主题所说的即时通讯头像策略。

用户使用即时通讯应用时,快速识别正在聊天好友身份的方式,首先是映入眼眸的好友头像,而不是人物名称,所以头像具有快速辨别用户身份的作用。

同时,该头像可反映主人当时的性格、心理、生活状态。例如,情侣对来自另一方头像的信息更加敏感;在同一个聊天群内,通过头像就能发现两位异性是情侣;心理状态不佳的用户更偏向使用冷色系的头像。可知头像在应用的用户体验和用户交互上发挥重要作用。

二、类型划分

在需求层面,头像包含三种分类:

  • 个人头像
  • 群头像
  • 其他头像

2.1 技术共性

暂时抛开业务细节,先聊聊头像图片的技术共性。一般来说,保存图片格式类型主要为:jpgpngwebp

jpg 是使用时间最长、最通用、体积小的有损图像压缩格式,多端应用应优先支持,适合作为头像的缩略图。

png 相比 jpg 更大的优势是其无损压缩能力。因为缩略图有损压缩,用肉眼几乎无法辨别的视觉差异,换取图像更小的传输体积,所以无损压缩 png 对比 jpg 并不占优势。在查看大图的场景中,图片可以被放大,所以 png 在这方面能保留更多细节,具有更高的可用性。

随后,Google 推出有损压缩格式 webp,用比 jpg 更小的文件体积,提供更高视觉质量。不过 webp 推出时间较晚,注定在部分应用或浏览器上存在兼容问题。不过考虑到其非一般的技术优越性,个人认为,放弃浏览器或移动端旧版本兼容是值得的。

头像图像格式选择,不考虑兼容性问题,技术选型优先级如下:webp > jpg > png

参考文章:

2.2 个人头像

个人头像比较容易理解,就是个人自行设置的个性化头像,具有鲜明的性格特点,能从个人头像的选图推断出好友的性格。

从业务层面来说,个人头像的加载需求最简单。拿微信举例,在Android手机上布局大小约为 50dp*50dp (推测),所以在Android的 xxhdpi 的UI设计大小相当于 150px*150px,iOS的三倍图也是这个规格。

avatar_1_grid

因此个人图像的大小,完全就是UI布局的实际大小,并且不需要任何 内边距

2.3 群头像

群头像相比个人头像业务略显复杂,和个人头像最大的区别除内嵌头像数量之外,就是具有内边距这一特征。内边距的应用,是为了令用户能更容易识别群头像内成员的数量。如果所有头像都紧贴在一起,则很难看出是群聊还是私聊。

样式则有四宫格、九宫格和群自定义类型三种。n宫格 头像的展示顺序,又分为遵循 权重优先入群顺序 两种分类。

权重优先 表示组合n宫格所包含的n位用户中,权重最高的用户头像永远排列在第一,即使此用户不是群主,随后按照权重递减排列。这在企业OA、政府公务等(政治正确型)应用中较为常见。

入群顺序 顾名思义,即根据用户进入群聊的先后顺序,排列群头像中个人用户的顺序。这种模式不关注某位用户或群主的优先级,只关心进入群聊前N位用户是否改变。如果这N位用户没有离开群聊,或不足N位用户时无新用户进入,则群头像无需更新。

这也是微信群头像所使用的模式。基于此模式,不仅极大减少服务端生成新的群头像的频率,而且同时节省客户端更新群头像流量和电量的消耗。

2.3.1 四宫格头像

4宫格头像和9宫格头像没有本质区别

avatars_4_grids

2.3.2 九宫格头像

avatars_9_grids

2.3.3 群头像自定义

除了类似微信的N宫格模式,另一种是类似QQ群头像的自定义模式,群主可以自行上传图片做为群头像。 群头像自定义和N宫格的头像可以搭配使用,但是从代码可维护的角度来看,个人建议只选择其一作为产品最终需求。

2.4 其他头像

其他头像 的逻辑,应划分到非自然人头像,例如微信小程序的头像。这类头像和个人头像加载逻辑没有本质差别,直接套用上述逻辑即可。

三、技术谈

上述内容从产品的角度思考,而后续讨论则总结自工作经验的技术重点。

从用户侧看,头像加载是个简单得不能更简单的功能。但是从技术的角度看,隐藏着海量的技术决策和众多问题解决方案。因为涉及技术面很广,所以这里选择几个印象深刻的部分进行探讨。

3.1 图像缓存

作为头像图片,虽然体积相比用户聊天图片更小。但是在好友数量导致头像很多的情况下,总流量就会积少成多。正好,移动端流行的图片加载框架发挥出一臂之力。

在Android端,常用的图片加载框架是Glide。虽然Glide可选不同的缓存策略、图片压缩配置,但是总结长期使用经验来说,Glide的设计并不合适头像加载的需求,反而更适合用户日常聊天图片加载。

例如:好友(即对方)更换头像完成后,本人客户端残留好友旧头像,而此头像很大概率不会再次使用。

Glide的缓存由其自行管理,清除缓存操作只能无差别清除所有磁盘缓存,不支持指定哈希值失效图片的能力。已有用户提过相关issue,作者明确回复不支持相关功能。

由此可知,这张图片一直存储在磁盘中,导致缓存空间越来越大。这个问题可配置Glide 的DiskLruCache达到一定程度的保护,但这个方案终究治标不治本,没有通过深入的、更细粒度的手段控制缓存。

3.2 群头像合成

为了减少图像生成的次数,可以向微信借鉴:只要群组内前N为用户没有进出群聊,该群头像就不会更新。需明确说明的是,即使这N位用户中任意一位用户头像修改了,已生成的群头像也不会更新。这样做可以大大减少新群头像生成的次数。

虽然群头像合成可以在客户端完成,也可以在服务端完成,但是根据个人经验,群头像生成最好在服务端完成。

假设,通过移动端生成满9人群头像,移动端需要先发出9个并发HTTP请求分别获取用户的个人头像。这些头像最小也是 150px*150px。总共下载9张 150px*150px 图片,随后在客户端把9张图片进行压缩、合并,生成目标群头像并缓存到本地磁盘。

本地生成群头像,不仅需要发出9次网络访问的流量,而且还耗费移动端宝贵的处理器性能,个人已经觉得很过分。

扩大范围扩大到一万个用户来说,假设每个用户需生成10个9宫格群头像,即使群聊中用户的头像重复且已缓存,粗劣估算下也需要:1万用户*10个群*4张每个群=40万次 网络访问。如果生成操作在服务端完成,那么客户端只需根据群ID向服务端发起10万次访问,即可保证所有群头像加载完成。

况且,客户端生成群头像这一方式,还没把客户端重复获取个人头像、处理器内存消耗,移动网络的 RRC连接DNS查询TCP建立,和后续访问延迟等问题考虑在内。

无论基于什么样的考量,都不应该在移动端完成群头像合成工作。不幸的是,由于后端服务诡异、扭曲的设计,导致作者不得不在移动端实现令人作呕的群头像拼接功能,在此警醒后来者不要重蹈覆辙。

3.3 缓存失效机制

对大多数图片加载框架来说,通过HTTP地址获取图片后只要缓存没清理,图片框架就不会访问网络检查图片是否有变更。

只在首次图片获取时访问一次网络,能避免冗余网络检查带来流量和电量负担,但也失去了定时甚至实时更新的能力。对应的,每次加载同一张图片,都检查是否需要更新图片(HTTP Header方式),虽说能减少开发者深入操控缓存的重复劳动,但也显著增加流量和耗电用量。

想做到两全其美,可以考虑把 唯一哈希值图片HTTP地址 组合为元组,只要其中参数没有变化,本地就不需要更新图片。同时,通过网络长链接或长轮询的更新信息,告知元组信息已失效,触发下一次图片的拉取操作。为了支持此能力,这要求开发者调整图片加载框架的用法,例如:配合 GlideGlideUrlObjectKey 使用。

四、总结

本文行文简略,鉴于不同产品需求的特殊性,文章内容不能做到面面俱到。望上述内容,能给正在构思相关功能的开发者带来启发,让大家少走弯路。