流水不争先,争的是滔滔不绝

微信“红包照片”背后的技术难题

微信QQ 云聊IM 1275℃

前言

2016年除夕夜,微信正式发布了限时功能——红包照片,这个功能与微信的其它特性相比非常小众,一是它的时效性很短(很快就下线了)、二是受限于玩法它几乎没有给普通人了解或熟悉的时间(也就间接影响了它的受众范围)。

有鉴于此,且微信红包功能深度与微信的产品理念相融合,我们从产品的角度讨论功能几乎没有意义,那么本文将着重从技术的角度为大家讲讲微信开发团队在技术实现上遇到过什么难题,以及都是如何克服并解决的。

先看UI界面

再看技术逻辑

1、主要模块

从功能实现角度看,红包照片并不复杂。主要包含图中几个模块,基本就可以实现红包照片这个功能:

  1. 发表权限入口/红点控制逻辑,客户端要对所有入口,以及引导进行控制,流量导控;
  2. 发表,负责红包照片资源上传,发表逻辑;
  3. 小图加载,红包照片在timeline有独特的展示效果,以区别普通的朋友圈图片;
  4. 大图加载,红包照片在点击大图之后会出现模糊的大图,以及随机的一个挖洞效果;
  5. 支付,这是红包照片最区别与其他照片的地方,你必须为这个照片发送一个红包才能查看完整的照片;
  6. 统计,客户端为依据上报的统计来发现问题,异常,数据分析。

2、更多细节

如果从单独的简单的每一个模块来看,似乎都不是很难,但我们整个开发过程到发布第一个版本也才不到两个多月的时间,那么对于客户端开发,这两个月意味着什么?

“时间”——数着小时过日子,还要适应需求不断的变化;没有大型的灰度的途径,代码质量必须依靠开发自己来保证高质量;

“性能”——红包照片的独特在于模糊,挖洞,这些性能消耗在Android上都会表现得特别的突出,怎么去保证从性能角度上它的体验效果和普通照片是无差异的;

“安全”——既然红包照片强调了红包,任何绕过红包查看照片都是违反游戏规则的,怎么在有限的时间内花最小的代价去尽量规避这种现象的发生;

“控制”——朋友圈是一个很扩散的场景,春晚活动关注度高,不确定因素多,不管是发表,还是红包照片支付,我们很难预见具体的量级,怎么去做流量控制?保证系统的可用性。

紧凑的版本迭代

1、基本原则

实际上最终发布的红包照片功能并不是第一个可供发布的版本,这个功能的开发是不断迭代的,产品方案不断变化; 对于客户端而言是,代码要随着产品设计不断的推倒,体验才能最终成型。

从抢照片,到照片大冒险,讨红包,红包照片。玩法不断的在变,客户端要适应这种开发方式,起码需要满足:

“快”——快速开发,能够应对产品不停的方案迭代,改变,快速出demo体验;

“稳”——在快速开发迭代的过程中,必须保证质量,对于类似一年只会有一次的项目,发布之后基本不允许有暴露严重问题再修复的机会。

2、实施方法

为此,最靠谱的就是以扩展的方式来写代码;多增加,少修改;而相比其他功能模块,需要更多的:

沟通、平台一致:小红屋的开发模式,直接面对面沟通,同步,类似功能必须要保证Android和iOS平台统一的体验;

不断的Code Review:代码首先要过关,特别在代码层面对关键路径做仔细的评估,甚至一些关键逻辑,我们拿iOS的代码逻辑和Android进行对比,来消灭错误逻辑,理解偏差;

细节梳理:不断的整理细节,梳理异常路径,保证各个模块健全,稳定;

测试、体验并重复之:内部测试和不断的真实体验,来发现问题。

最后,项目在发布之前都还保持着保密的状态,内部的每一次的小规模演练就变得非常重要,抓住每一次演练的机会,进行检验。

极致的性能是用户体验之本

1、初始版本性能很差

如上图所示,估计这是用户对红包照片印象最深的地方,随机打开一张朋友圈红包照片,每次都会随机显示一个圆圈类型的洞,暴露图片局部的内容;而如果想看完整的照片,必须通过”发红包看照片”来实现,这是红包照片的核心玩法。

模糊图片并不是功能一开始就有的设计,而是一个个demo版本演变来的, 因此一张红包照片和一张普通照片对后台存储来说是没有什么不同的。从模块设计角度来说,一张红包照片的发表/下载流程上和一张普通图片的发表/下载几乎没有差异;那么这些表现出来的差异,模糊、挖洞就得交给客户端来做。

第一次点开朋友圈中的红包照片要经过哪些步骤呢?

这些步骤的处理对Android来说都是很大的性能考验。第一个版本,第一次点击图片,从完成下载、模糊、抠洞、显示看到图片,大概花了大约4s的时间,显然这是不可接受的一个体验。 那我们是怎么做的呢?

2、大图还是小图?

模糊一张朋友圈大图需要 1500ms左右,而模糊一张小图只要50ms不到,差不多30倍的差距,拿大图来做模糊其实很不划算。通过调节模糊半径,在确定显示效果ok的情况下,我们选择了用小图来做模糊效果而不是大图。

3、如何“挖洞”?

但我们注意到,在模糊图出现的圆洞区域却是很清晰的画面(小图缩放达不到这种效果),给人感觉整个图片好像就是清晰的样子,这又是怎么实现的呢?

首先,挖洞的位置并不是简单的随机,而是控制有一定的规则,比如说有一定的概率出现在人脸的周围。同样的道理,拿大图来做人脸识别和小图来做人脸识别差距也是非常明显的,至少10倍以上的差距,即便小图识别效果可能没有大图的效果好,但这种换取代价还是非常值得的。因此我们也首先在小图上做人脸识别,记录对应的结果坐标,通过对图片局部解码从大图找到对应的区域,Clip出一个圆圈,画到一个ARGB_8888的面板上,再将边缘进行一定的透明度调整,盖到原始图片上即可!

4、优化后的性能很满意

通过这些过程,最后我们将整体的体验缩减到1s内。

安全性至关重要

1、传统做法的不足

朋友圈的图片是利用HTTP从CDN服务器直接下载,而非使用私有的协议。 同时存储在客户端本地。

朋友圈红包照片跟普通照片并无差异,而HTTP本身就很容易被监听抓包,Android操作系统APP的大数据存储也是存储在公共的存储空间上。功能一旦预演,发布定会有很多的抓包、导出资源的工具等等,破坏朋友圈红包照片的相关玩法。

2、关键技术点

下载安全:更改朋友圈的下载方式,从项目的时间来看这几乎是不可能的;

本地存储安全保护:本地存储也要足够安全保护,否则依然很容易从本地拉取到下载后的数据。

3、方案要达到的要求

简单的抓包获取的数据的是无法解读的;

客户端的数据是加密存储;

加密的算法足够简单、快速,客户端存储的是加密数据,意味着客户端的每次读取/写入图片都是一个加解密的过程,不可能为了防止少数人破坏游戏规则而给所有的人都带来更差的体验;

开发足够快、改动少,朋友圈暴露读取文件的接口很多,期望能花最小的代价完成改动。

4、最终制定的方案

最后我们选择在HTTP中返回加密的数据,而加密的数据必须依赖朋友圈数据中隐藏对应的key来解密,且支持流式加解密。一张图片可以对应一个不同的密钥,允许后台从非加密切换到加密,或者加密切换到非加密。

朋友圈暴露读取文件的接口很多,读小图、读大图、读模糊图、写大图、写小图、检查exif等等都是对图片文件的操作。要考虑密钥怎么传递,减少改动量和修改的复杂度,加密的bitmap怎么解析,怎么和普通的图片数据做到兼容?

来看看最后优化后实现方式:

使用者通过路径生成器,来获取路径,如果是红包照片的数据,则将密钥通过一定规则隐藏在Path路径中,密钥变更会使得Path也不同。所有的文件接口聚合到FileOperataion,发现是加密的Path则返回特殊的EncInputStream/EncOutputStream,否则返回普通的InputStream/OuputStream;EncStream 则拦截了InputStream/OutputStream的 read 和 write 等方法,将数据通过加解密模块处理完再返回应用层;从整体上来看,我们的改动也只涉及到了蓝色/黄色/红色的区域模块。

实际上,在保证保密/安全的工作上我们还做了很多其他工作。比如发布的版本,为了使得项目进行保密,资源(String)等都是加密处理的,当然也犯过一些错误,比如1月26号的预热,代码暴露图片路径的接口,使得简单的通过Xposed注入替换路径就可以绕过游戏规则(关于微信红包被破解请查看此链接)。

功能上线后的可控性

朋友圈红包作为今年的一个新的尝试,本身就获取到高度的关注,但又缺少历史经验,对于在朋友圈这种环境下如此大型的活动的,一方面要控制朋友圈的质量,一方面可能带来的巨大的流量,甚至很难估算;所有的方案都不能保证是万无一失的,如何保证系统的可用性,一方面就是要可控,能够随时调整策略。

需要达到的要求是:发表权限可控,发表人数可控,发表次数可控,所有文案可控,支付金额可控等等。

举个例子,比如发红包看照片这个按钮,所谓的“可控”包含以下内容:

  1. 控制,后台对客户端要有足够个控制逻辑,比如金额区间,甚至可以区分不同的人群。
  2. 缓存,很多人不断的对金额随机,客户端要有一定的缓存时间,减少服务器的压力。
  3. 提额,预备提额可以减少服务器的压力。
  4. 彩蛋,有一定的几率出现彩蛋(中奖),同理可以减少服务端的压力。

统计数据

2016猴年春晚,朋友圈红包照片:

产生了2900w红包照片

互动总数超过1.92亿次

版权声明:部分文章、图片等内容为用户发布或互联网整理而来,仅供学习参考。如有侵犯您的版权,请联系我们,将立刻删除。
点击这里给我发消息