做爲技術方向選型的重點,熱更新/熱修復是一個繞不過去的問題。本文將介紹目前的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
回頭看看咱們的熱更新方案,生成補丁的方法簡單、補丁小、客戶端熱更新下載/合併邏輯簡單,基本知足了咱們對於熱更新的需求。