APP中RN頁面熱更新流程-ReactNative源碼分析

平時使用WebStorm或VSCode對RN工程中的文件修改後,在鍵盤上按一下快捷cmd+s進行文件保存,此時當前調試的RN頁面就會自動進行刷新,這是RN開發相比於原生開發一個很大的優點:熱更新。
那麼,從按一下快捷cmd+s到RN頁面展示出最新的JS頁面,這個過程是怎樣發生的呢?下面根據時間順序來梳理一下。
這裏約定後面說的原生部分是指iOS端,ReactNative源碼分析指的是iOS端集成的RN框架分析。
 
原生APP中RN頁面的熱更新簡要流程
React Native應用包含兩部分:原生代碼和JavaScript代碼。JavaScript和原生代碼通信的橋樑是Bridge,而Bridge的實現又依賴於JSThread的runloop。
當JS有消息要傳遞給原生時,它會把消息封裝成事件放入到JSThread的runloop的事件隊列中,而JSThread的runloop會監聽消息隊列中的事件,一旦有事件需要處理,就會將其交給Bridge處理,從而實現JavaScript和原生代碼的相互調用和數據傳輸。
 
基於JavaScript和原生代碼的消息傳遞機制,RN熱更新步驟如下:
1.在iOS原生中的RN頁面觸發熱更新時,首先是JS環境中的websocket收到了Metro服務器的通知,在這個通知中包含了需要更新的JS bundle的URL地址,然後js將這個通知事件放到了JSThread的runloop中傳到了RCTCxxBridge。
2.RCTCxxBridge收到熱更新事件後,調用JavaScriptCore框架中的方法來執行一個JavaScript腳本,這個腳本會告訴JavaScript環境去下載新的JS bundle並執行它。事件又回到了JS。
3.JavaScript環境調用下載命令向遠程服務器請求新的JS bundle,事件又被轉回到了原生側。
4.原生側下載最新的bundle.js,下載完成後調用加載,執行js方法。
5.重新設置RN頁面的根組件。
 
熱更新觸發條件
React Native在調試模式下有兩種熱更新方式:Hot Reloading和Live Reload。Hot Reloading可以實現代碼的增量更新,而Live Reload技術則是全量更新。
下面以在index.js中新增一個組件註冊語句爲例。
在RN項目中index.js是ReactNative項目的入口文件,RN啓動時首先會執行這個文件,把組件註冊到AppRegistry中,這裏在index.js的文件底部順序添加一個組件註冊,然後按快捷鍵cmd+s保存修改
AppRegistry.registerComponent('FlatListDemo', () => FlatListDemo);
這時本地的Packager服務會監聽本地文件系統的變化,當有文件修改並保存時,Packager會運行RN命令行工具,自動生成一個新的bundle文件
react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/main.jsbundle --assets-dest ios
然後將生成的bundle文件上傳至服務器,接着Metro 服務器通過 WebSocket 鏈接發送消息給APP應用程序。

 

觸發熱更新

在APP中RN框架啓動時,會在APP的JS運行上下文中創建一個websocket鏈接並與js服務器建立連接,它用於監聽Metro服務器發來的bundle.js更新通知。
當Packger打了新的bundle.js並上傳到js服務器後,Metor服務器就會向與它鏈接的websocket發送更新通知,js引擎收到服務器的通知後,就會做後續的事件處理。
具體js建立websocket的代碼如下:
在iOS端ReactNative框架中,RCTCxxBridge文件中的+ (void)runRunLoop方法內負責創建並運行JS引擎對應的jsThread的runloop。它在APP啓動時被創建,用來處理js引擎的任務和事件,並保證了_jsThread的常駐,不被銷燬。
runloop被註冊時機在RN框架啓動時:
js引擎收到服務器的通知後,被包成一個原生runloop的事件源放入到事件隊列中,然後jsThread的runloop開始處理事件。
然後把事件加入到RCTMessageThread中進行異步處理
RCTCxxBridge調用js腳本,讓js處理與最新bundle.js下載相關的事件。把事件傳入到js事件隊列中
js引擎處理完JS層面的事件後,將事件轉回給原生,讓原生代碼執行實際的下載工作
原生側的RCTDevSettings模塊的reloadWithReason:方法進行處理。調用ReloadCommand監聽的觸發器,進行觸發reloadCommand命令
RCTReloadCommand發送didReceiveReloadCommand收到下載指令
接着調用RCTBridge中的didReceiveReloadCommand方法,RCTBridge中銷燬之前的js緩存,調用setUp進行js環境重置,js資源下載
js資源下載完成後,執行js代碼
js資源reload完成後,調用js引擎,展示目標組件
當js文件加載成功後,APP會重新創建一個RCTRootContentView, 並將舊的移除,把新的添加上去。新的RCTRootContentView.reactTag使用規則遞增
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
  RCTAssert(bridge != nil, @"Bridge cannot be nil");
  if (!bridge.valid) {
    return;
  }
  
  [_contentView removeFromSuperview];
  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
                                                    bridge:bridge
                                                  reactTag:self.reactTag
                                            sizeFlexiblity:_sizeFlexibility];
  [self runApplication:bridge];
  
  _contentView.passThroughTouches = _passThroughTouches;
  [self insertSubview:_contentView atIndex:0];
  
  if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
    self.intrinsicContentSize = self.bounds.size;
  }
}
  
- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag" : _contentView.reactTag,
    @"initialProps" : _appProperties ?: @{},
  };
  
  RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
  [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];
}
  

至此,APP中RN頁面的熱更新主要流程結束。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章