React Native熱更新

前言

做爲技術方向選型的重點,熱更新/熱修復是一個繞不過去的問題。本文將介紹目前的React Native(簡稱RN)解決方案,以後重點介紹咱們即將採用的方案(包括源代碼)。react

React Native熱更新分析

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

1) 生成補丁

差別化代碼的拆分和合並使用了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

2) 合併補丁

客戶端檢測到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

回頭看看咱們的熱更新方案,生成補丁的方法簡單、補丁小、客戶端熱更新下載/合併邏輯簡單,基本知足了咱們對於熱更新的需求。

相關文章
相關標籤/搜索