React Native熱更新


前言

作為技術方向選型的重點,熱更新/熱修復是一個繞不過去的問題。本文將介紹目前的React Native(簡稱RN)解決方案,之后重點介紹我們即將采用的方案(包括源代碼)。

React Native熱更新分析

React Native熱更新核心的問題是如何進行js代碼的動態更新。如果不考慮更新包的大小,完全可以將整個js代碼包(即編譯后的jsbundle)放到服務器,由客戶端來進行更新,可如果為了修復一個bug,要下載所有的js代碼,接受不了...
怎么辦?拆分!RN熱更新最核心的一點是編譯后的jsbundle是穩定的,即如果代碼不變的情況下,每次編譯后的jsbundle是一樣的;而如果只是改動了部分代碼,編譯后前后差異就在這改動的代碼。當然前提是RN的版本是不變的情況。
基於這點,目前公開的熱更新方案一個是微軟的Code Push,一個是React Native中文網中的react-native-pushy。通過這篇小文對這兩個方案的實際分析應用來看,比較麻煩,容易出錯。58同城也有對這兩個方案進行了分析,並根據自身的業務特點實現了自己的熱更新方案。

我們的方案

熱更新的方案是基於jsbundle的穩定性,我們的方案也是這樣。我們方案的確定包括兩個主要的點:1)jsbundle差異化處理;2)jsbundle的加載邏輯理解。理解了這兩點,就理解了我們的方案:即在一個迭代周期中,上線版本包括所有的js代碼(基礎jsbundle),隨后產生的多個熱更新都將和這個基礎jsbundle進行差異化處理,產生多個補丁,每個新的補丁覆蓋前一個補丁,即對每一個線上版本始終需要加載一個補丁。客戶端下載補丁后,重新加載基礎jsbundle,在加載過程中將最新下載的jsbundle合並到基礎jsbundle中,實現熱更新。
我們的這個方案並非一個完美方案,大家的方案都不是。原因在於,熱更新中的js代碼依賴線上的RN環境,依賴客戶端提供的橋接接口,一旦出現當前的接口環境不支持新業務的開發,客戶端版本就需要迭代。因此可以說,RN可以減少發版的次數,並不是說完全不用發版了。
這個問題清楚了,對於補丁可能很大的顧慮就可以消除了。據測試發現,修改了一個文件,補丁小於1KB。

1) 生成補丁

差異化代碼的拆分和合並使用了google-diff-match-patch,支持各個平台。以下示例代碼為iOS,安卓/js對應的方法類似。
基本的思路是,將基礎jsbundle和包含熱更新jsbundle轉為string,然后對string進行比較,最后將差異代碼存儲為文件放到server端。
這里是生成補丁的代碼:

+ (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,這樣可以方便的獲取補丁文件、測試以及上傳。

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進行合並(代碼如下),再轉換為新的二進制數據,最后將新的二進制數據加入到加載流程中,從而實現熱更新。

+ (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

錯誤,請檢查存\取文件的數據類型是否一致。

后記

涉及熱更新最核心的部分已經介紹完了。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM