最近做热修复相关的开发,对其中核心功能点文件一致性校验和diff差分做个简单的记录。这次先记录文件一致性校验。
热修复时做文件的一致性校验需要做到这么几个点:
1)单文件的内容比对,两个文件的内容是否一致。
2)多文件的内容比对,确认两组文件文件内容是否一致。
3)多端同步问题。需要保证无论是从Mac还是Win上打出来的包都是相同的,发送到IOS和Android平台上也能校验出来文件是否一致,是否发生了变化
第一个问题
第一个问题其实很简单,只要计算文件的Hash值,用两个文件的Hash值进行比对即可。只要Hash值不一致,那就可以认为文件发生了变化。稍微有点干扰的地方是Hash算法的选择。文件完整性校验,第一反应是直接计算文件的md5值,然后比对这个文件md5。但查了资料发现,有些热修复框架里居然用的是SHA-256算法,而不是使用MD5。
MD5和SHA-256的区别,首先在于输出的长度。Md5的输出一般是一个32位长的16进制字符串。SHA-256算法得出的长度是比Md5多一倍,是个64位长的16进制字符串。其次是加密速度,肯定是Md5要快很多。再次就是安全性,SHA-256碰撞的可能性会比Md5低。(此外Md5有时间会因为程序员的一时疏忽,代码bug导致计算时出现异常,首位为0时,计算出来的hash值只有31位。这样做多端同步时会比对失败,而且不细查的情况下很容易被忽略)。
手机端的文件有个特点就是文件数目庞大,而且以小文件居多。这种场景下使用Md5和SHA-256耗时区别不明显。所以综合考虑使用SHA-256算法。
第二个问题和第三个问题
这两个问题其实是应该算做同一个问题。既然能比对确认一组文件和另外一组文件的差异,那自然可以检查出不同平台上的文件是否有差异。平台的差异对文件影响不大。就算有,目前处理起来也很方便。例如在Mac平台构建时带入的.DS_Store文件, 只要过滤掉.DS_Store这些因为平台特性带出来的特殊文件即可做到多端同步。
多文件的比对第一步是把文件找出来。
这里简单处理,直接从根目录开始做深度优先遍历,遍历完整个目录即可。
第二步,如何比对
-
最开始的想法是做个Map结构,以每次扫描到的文件名为key,文件hash值为value。但是这个方案有个bug,不够通用。遇到在一组文件的不同目录下存在同名文件时就会错乱。(有些项目因为自身业务特殊性问题,会在使用工具将代码打包时,将文件做混淆压缩等处理。顺带会将所有文件的文件名加以处理,保证每个文件文件名的唯一性。比如之前做过的WebView离线包,这个项目的离线包打包时就会将所有文件名做特殊处理。例如一个头像图片,文件名为 head.png,处理完之后文件名会变为head.xxxxxxx.png。文件名中插入了一个唯一的hash值。从而保证了所有文件名的唯一性)
-
针对上面的漏洞,做了个简单的调整。将文件相对路径Path 和文件名加一起作为key,还是使用Hash值作为value。
上图这种场景,就会生成两条记录/HotFix/drawable/launcher.png --> xxxxxxxxxxx;/HotFix/drawable-v24/launcher.png --> xxxxxxxxxxxx.
使用的时候我们将这个map结构转化为json格式的config文件,从一端传递到另外一端。
{ "/HotFix/drawable/launcher.png": "xxxxxxxxxxx", "/HotFix/drawable-v24/launcher.png": "xxxxxxxxxxxx" }接收端使用自己生成的local_config和远端下发的remote_config进行比对。
这个方案功能上基本可用。但是在实际使用过程中存在不少问题。1)随着文件的增多,这个config文件会进一步膨胀,体积越大,传输时时间耗时越长,接收的成功率也会下降。2)config文件这样的结构,从一端发送到另外一端,存在序列化和反序列化。进一步增加了复杂性,降低了比对的成功率。3)文件内容是无序的,比对时效率不高。
-
继续改进。针对第2个方案的缺陷,想了一下其实config文件大不不必传来传去。只要多端算法一致,使用相同的算法步骤,输入同一组文件,自然能计算出一致的结果。
例如上图这样的一组文件。不再死磕map结构。将相对路径+文件名+hash值放在一起作为一条记录,那么生成的就是一条条类似 /HotFix/drawable/p1.png_xxxxxx 这样的数据记录。整体用List结构组织起来。又因为要做多端同步,将list结构中的内容做字典序排序。那么就会得到这样的一个json。
[ "/HotFix/drawable/p1.png_xxxxxx", "/HotFix/drawable/p2.png_xxxxxx", "/HotFix/drawable/p3.png_xxxxxx", "/HotFix/drawable-v24/p1.png_xxxxxx", "/HotFix/drawable-v24/p2.png_xxxxxx", "/HotFix/drawable-v24/p3.png_xxxxxx"]将这个json转化为String,再对其进行SHA-256,就能得到一个64位长的16进制字符串。这个最终生成的字符串可以视为这组文件的指纹。比对时将该组文件和指纹一起发给对端,对端接收后使用算法生成指纹。两个指纹对比如果一样,视为比对通过。不一样自然比对不通过。
目前看这套最终方案性能、安全性,使用的简易性都还可以。
PS:刚开始想到这个方案的时候还有点激动,后来查资料一看,React Native的CodePush框架里,比对文件一致性的逻辑也是这样写的,汗一个。原来大牛N年前早就解决了,代码还写的更简单精致。