做为技术方向选型的重点,热更新/热修复是一个绕不过去的问题。本文将介绍目前的React Native(简称RN)解决方案,以后重点介绍咱们即将采用的方案(包括源代码)。react
React Native热更新核心的问题是如何进行js代码的动态更新。若是不考虑更新包的大小,彻底能够将整个js代码包(即编译后的jsbundle)放到服务器,由客户端来进行更新,可若是为了修复一个bug,要下载全部的js代码,接受不了...
怎么办?拆分!RN热更新最核心的一点是编译后的jsbundle是稳定的,即若是代码不变的状况下,每次编译后的jsbundle是同样的;而若是只是改动了部分代码,编译后先后差别就在这改动的代码。固然前提是RN的版本是不变的状况。
基于这点,目前公开的热更新方案一个是微软的Code Push,一个是React Native中文网中的react-native-pushy。经过这篇小文对这两个方案的实际分析应用来看,比较麻烦,容易出错。58同城也有对这两个方案进行了分析,并根据自身的业务特色实现了本身的热更新方案。git
热更新的方案是基于jsbundle的稳定性,咱们的方案也是这样。咱们方案的肯定包括两个主要的点:1)jsbundle差别化处理;2)jsbundle的加载逻辑理解。理解了这两点,就理解了咱们的方案:即在一个迭代周期中,上线版本包括全部的js代码(基础jsbundle),随后产生的多个热更新都将和这个基础jsbundle进行差别化处理,产生多个补丁,每一个新的补丁覆盖前一个补丁,即对每个线上版本始终须要加载一个补丁。客户端下载补丁后,从新加载基础jsbundle,在加载过程当中将最新下载的jsbundle合并到基础jsbundle中,实现热更新。
咱们的这个方案并不是一个完美方案,你们的方案都不是。缘由在于,热更新中的js代码依赖线上的RN环境,依赖客户端提供的桥接接口,一旦出现当前的接口环境不支持新业务的开发,客户端版本就须要迭代。所以能够说,RN能够减小发版的次数,并非说彻底不用发版了。
这个问题清楚了,对于补丁可能很大的顾虑就能够消除了。据测试发现,修改了一个文件,补丁小于1KB。github
差别化代码的拆分和合并使用了google-diff-match-patch,支持各个平台。如下示例代码为iOS,安卓/js对应的方法相似。
基本的思路是,将基础jsbundle和包含热更新jsbundle转为string,而后对string进行比较,最后将差别代码存储为文件放到server端。
这里是生成补丁的代码:web
+ (NSString *)getDiffOfOldString:(NSString *)oldString newString:(NSString *)newString { DiffMatchPatch *diffMatchPatch = [[DiffMatchPatch alloc] init]; NSMutableArray *diff = [diffMatchPatch diff_mainOfOldString:oldString andNewString:newString]; NSMutableArray *patchDiff = [diffMatchPatch patch_makeFromDiffs:diff]; NSString *patchString = [diffMatchPatch patch_toText:patchDiff]; return patchString; }
实际操做中建议写一个简单的生成补丁的web页面,支持上传基础jsbundle和新jsbundle,这样能够方便的获取补丁文件、测试以及上传。react-native
客户端检测到server端有新的补丁后进行下载,下载后通知RN从新加载基础jsbundle,在加载的过程当中将新的补丁合并到基础jsbundle中,随后一并加载到内存中。合并的过程须要hack jsbundle的加载类:服务器
@interface RCTBatchedBridge (RN) @end @implementation RCTBatchedBridge (RN) + (void)load { NSError *error = nil; [self jr_swizzleMethod:@selector(executeSourceCode:) withMethod:@selector(wb_executeSourceCode:) error:&error]; if (error) { NSLog(@"inject patch code fail: %@", error); } } - (void)wb_executeSourceCode:(NSData *)sourceCode { //合并patch if ([WBBridgeManager sharedManager].hasNewPatch) { sourceCode = [WBPatchManger combinePatchWithSourceCode:sourceCode]; } [self wb_executeSourceCode:sourceCode]; } @end
上面的sourceCode就是jsbundle加载到内存中的二进制数据,将其转为string,补丁也转为string,将这两个string进行合并(代码以下),再转换为新的二进制数据,最后将新的二进制数据加入到加载流程中,从而实现热更新。app
+ (NSString *)combinePatch:(NSString *)patchString withOldString:(NSString *)oldString { DiffMatchPatch *diffMatchPatch = [[DiffMatchPatch alloc] init]; NSError *error = nil; NSMutableArray *patchs = [diffMatchPatch patch_fromText:patchString error:&error]; if (error) { NSLog(@"diff error: %@", error); } NSArray *result = [diffMatchPatch patch_apply:patchs toString:oldString]; return result && result.count > 0 ? result[0] : oldString; }
若是合并出现测试
AssertMacros: hash <= (~(UniChar)0x00), Hash value has exceeded UniCharMax! file: /Users/…/Pods/Google-Diff-Match-Patch/DiffMatchPatchCFUtilities.c, line: 391
错误,请检查存\取文件的数据类型是否一致。google
涉及热更新最核心的部分已经介绍完了。code
回头看看咱们的热更新方案,生成补丁的方法简单、补丁小、客户端热更新下载/合并逻辑简单,基本知足了咱们对于热更新的需求。