React Native 源码浅析
来源:互联网 发布:北京数据堂公司英语 编辑:程序博客网 时间:2024/06/05 03:20
React Native 源码浅析
1.RN是如何完成bundle文件加载的?
2.Native和JS之间是如何通讯的?
3.JS布局是怎么样被渲染到ReactRootView上的?
下面通过对RN源码(版本:0.40.0)的分析,尝试找找这3个问题的答案~
chengyuan-macpro:AwesomeProject chengyuan$ react-native -V0.40.0
一、JSBundle加载过程
上一篇博客React Native 用法介绍有提到过将js文件打包成bundle,存放到assets目录下,RN页面启动后会加载bundle包,下面就具体看下bundle的加载过程~
public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity { private final ReactActivityDelegate mDelegate; protected ReactActivity() { mDelegate = createReactActivityDelegate(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDelegate.onCreate(savedInstanceState); } //省略非关键代码}
这里使用委托模式将 Activity 的生命周期及事件传递委托给 ReactActivityDelegate进行处理,这样用是为了让 ReactFragmentActivity 也能复用该处理逻辑。
在ReactActivityDelegate的onCreate中调用了loadApp
// ReactActivityDelegate.javaprotected void loadApp(String appKey) { // 创建RN根视图 mReactRootView = createRootView(); mReactRootView.startReactApplication( getReactNativeHost().getReactInstanceManager(), appKey, getLaunchOptions()); // 将RN视图添加到Activity中 getPlainActivity().setContentView(mReactRootView);}
通过startReactApplication完成ReactContext初始化
// ReactRootView.java public void startReactApplication( ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions) { // 确保再UI线程运行 UiThreadUtil.assertOnUiThread(); // 省略非关键代码 if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground(); } // 宽高计算完成后添加布局监听 if (mWasMeasured) { attachToReactInstanceManager(); } }
createReactContextInBackground这个方法只会在 Application 创建时调用一次,
重新加载 JS时将会调用 recreateReactContextInBackground方法。recreateReactContextInBackground调用了recreateReactContextInBackgroundInner方法
// XReactInstanceManagerImpl.javaprivate void recreateReactContextInBackgroundInner() { UiThreadUtil.assertOnUiThread(); // 调试模式,从服务器加载 bundle if (mUseDeveloperSupport && mJSMainModuleName != null) { // 省略代码 return; } // 正式环境,从本地加载 bundle recreateReactContextInBackgroundFromBundleLoader(); }
继续跟踪,发现启动异步任务ReactContextInitAsyncTask完成ReactContext初始化
// XReactInstanceManagerImpl.java private final class ReactContextInitAsyncTask extends AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> { @Override protected void onPreExecute() { if (mCurrentReactContext != null) { tearDownReactContext(mCurrentReactContext); mCurrentReactContext = null; } } @Override protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) { // 省略代码 try { JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create(); return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader())); } catch (Exception e) { return Result.of(e); } } // 省略代码}
doInBackground中调用createReactContext创建ReactContext,通过第二个参数params[0].getJsBundleLoader()携带的bundle信息决定从哪里加载bundle文件
// XReactInstanceManagerImpl.java private ReactApplicationContext createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) { // 省略代码 catalystInstance.runJSBundle(); return reactContext; }
runJSBundle执行了mJSBundleLoader.loadScript(CatalystInstanceImpl.this);由于mJSBundleLoader由createAssetLoader创建,所以会调用如下方法
public static JSBundleLoader createAssetLoader( final Context context, final String assetUrl) { return new JSBundleLoader() { @Override public void loadScript(CatalystInstanceImpl instance) { instance.loadScriptFromAssets(context.getAssets(), assetUrl); } @Override public String getSourceUrl() { return assetUrl; } }; }
instance指向CatalystInstanceImpl,最终调用CatalystInstanceImpl中的native接口:
native void loadScriptFromAssets(AssetManager assetManager, String assetURL);
Bundle 的加载逻辑最终还是交由 C++ 层进行处理。
runJSBundle加载bundle文件流程如下:
二、Native与Java通讯机制
Native—>JS
先看下bugly提供的一张完整通讯流程图
从createReactContext切入,看下ReactContext上下文对象创建过程中,具体做了哪些操作
// XReactInstanceManagerImpl.javaprivate ReactApplicationContext createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) { // 省略代码 // 注册JS层模块,通过它把所有的 JavaScriptModule 注册到 CatalystInstance,将 JS 的可调用 API 暴露给 Java。 JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder(); final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); if (mUseDeveloperSupport) { reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); } // 省略代码 try { CoreModulesPackage coreModulesPackage = new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider); processPackage( coreModulesPackage, reactContext, moduleSpecs, reactModuleInfoMap, jsModulesBuilder); } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } // 代码省略}
JavaScriptModuleRegistry.Builder注册了哪些JavaScriptModule呢?需要查看下processPackage接口代码
private void processPackage( ReactPackage reactPackage, ReactApplicationContext reactContext, List<ModuleSpec> moduleSpecs, Map<Class, ReactModuleInfo> reactModuleInfoMap, JavaScriptModuleRegistry.Builder jsModulesBuilder) { // 省略代码 for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) { jsModulesBuilder.add(jsModuleClass); } Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); }
进入到reactPackage.createJSModules的接口中可以查看到添加了哪些JavaScriptModule,代码如下
// CoreModulesPackage.java @Override public List<Class<? extends JavaScriptModule>> createJSModules() { List<Class<? extends JavaScriptModule>> jsModules = new ArrayList<>(Arrays.asList( DeviceEventManagerModule.RCTDeviceEventEmitter.class, JSTimersExecution.class, RCTEventEmitter.class, RCTNativeAppEventEmitter.class, AppRegistry.class, com.facebook.react.bridge.Systrace.class, HMRClient.class)); if (ReactBuildConfig.DEBUG) { jsModules.add(DebugComponentOwnershipModule.RCTDebugComponentOwnership.class); jsModules.add(JSCHeapCapture.HeapCapture.class); jsModules.add(JSCSamplingProfiler.SamplingProfiler.class); } return jsModules; }
有JSTimersExecution、RCTEventEmitter、RCTNativeAppEventEmitter和HMRClient,这些类继承至JavaScriptModule,以HMRClient类为例,搜索发现还有一个与它对应的HMRClient.js文件,这是为什么?
为了找到答案,需要深入了解下接口JavaScriptModule的作用了
package com.facebook.react.bridge;import com.facebook.proguard.annotations.DoNotStrip;/** * Interface denoting that a class is the interface to a module with the same name in JS. Calling * functions on this interface will result in corresponding methods in JS being called. * * When extending JavaScriptModule and registering it with a CatalystInstance, all public methods * are assumed to be implemented on a JS module with the same name as this class. Calling methods * on the object returned from {@link ReactContext#getJSModule} or * {@link CatalystInstance#getJSModule} will result in the methods with those names exported by * that module being called in JS. * * NB: JavaScriptModule does not allow method name overloading because JS does not allow method name * overloading. */@DoNotStrippublic interface JavaScriptModule {}
从 JavaScriptModule 的头注释中,可以了解到大概的意思是:
一个接口如果继承了 JavaScriptModule 接口,做相应的配置,就可以实现这个 Native 接口到 Javascript 中同名模块的映射,调用CatalystInstance#getJSModule接口就可以直接访问js对应的接口~
接下来的重点就是看下getJSModule是如何访问js接口的?
//CatalystInstanceImpl.java @Override public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) { return Assertions.assertNotNull(mJSModuleRegistry) .getJavaScriptModule(this, executorToken, jsInterface); }
又调用到JavaScriptModuleRegistry的getJavaScriptModule方法,getJavaScriptModule使用了动态代理,所有调用js接口的逻辑都由JavaScriptModuleInvocationHandler#invoke来完成代理,动态代理可以参考之前的博客Design pattern–代理模式
private static class JavaScriptModuleInvocationHandler implements InvocationHandler { private final WeakReference<ExecutorToken> mExecutorToken; private final CatalystInstance mCatalystInstance; private final JavaScriptModuleRegistration mModuleRegistration; public JavaScriptModuleInvocationHandler( ExecutorToken executorToken, CatalystInstance catalystInstance, JavaScriptModuleRegistration moduleRegistration) { // 省略代码 } @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // 省略代码 NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray(); mCatalystInstance.callFunction( executorToken, mModuleRegistration.getName(), method.getName(), jsArgs ); return null; } }
invoke调用了mCatalystInstance.callFunction方法,查看代码
// CatalystInstanceImpl.javaprivate native void callJSFunction( ExecutorToken token, String module, String method, NativeArray arguments);
有调用到C++接口,进入CatalystInstanceImpl.cpp
// CatalystInstanceImpl.cppvoid CatalystInstanceImpl::callJSFunction( JExecutorToken* token, std::string module, std::string method, NativeArray* arguments) { instance_->callJSFunction(token->getExecutorToken(nullptr), std::move(module), std::move(method), std::move(arguments->array));}
instance_是Instance对象,再打开Instance.cpp
// Instance.cppvoid Instance::callJSFunction(ExecutorToken token, std::string&& module, std::string&& method, folly::dynamic&& params) { callback_->incrementPendingJSCalls(); nativeToJsBridge_->callFunction(token, std::move(module), std::move(method), std::move(params));}
接着又调用JSExecutor的callFunction方法,JSExector.cpp 最终将调用转发到 JSCHelper.cpp 中执行evaluateScript 的函数,从而执行 JS 的调用。
至此,从 Java -> C++ 层调用链结束,JSC 将执行 JS 调用,在 JS Framewrok 层接收来自 C++的调用为 MessageQueue.js 的 callFunctionReturnFlushedQueue。在调用 CallFunction 执行 Js 后,会调用 flushedQueue 更新队列。
js—->native
留一张bugly的流程图,源码细节不在赘述:
三、RN画面渲染
这里以登录页面的输入框TextInput为例,看下渲染过程~
ReactNative的UI组件渲染流程:
js端
通过requireNativeComponent -> createReactNativeComponentClass -> ReactNativeBaseComponent下mountComponent的调用关系,最终在mountComponent中调用UIManager组件创建View:UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);Native端
UIManager调用对应组件类型的ViewManager(单例,管理类)创建实例。
// TextInput.js _renderAndroid: function() { // 省略代码 const textContainer = <AndroidTextInput ref={this._setNativeRef} {...props} mostRecentEventCount={0} onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} onSelectionChange={this._onSelectionChange} onTextInput={this._onTextInput} text={this._getText()} children={children} disableFullscreenUI={this.props.disableFullscreenUI} />; // 省略代码 },
AndroidTextInput是通过requireNativeComponent引入的, 而requireNativeComponent是require方法引入,可以猜到requireNativeComponent也是一个js文件
// requireNativeComponent.jsfunction requireNativeComponent( viewName: string, componentInterface?: ?ComponentInterface, extraConfig?: ?{nativeOnly?: Object},): Function { const viewConfig = UIManager[viewName]; if (!viewConfig || !viewConfig.NativeProps) { warning(false, 'Native component for "%s" does not exist', viewName); return UnimplementedView; } viewConfig.uiViewClassName = viewName; viewConfig.validAttributes = {}; viewConfig.propTypes = componentInterface && componentInterface.propTypes; // The ViewConfig doesn't contain any props inherited from the view manager's // superclass, so we manually merge in the RCTView ones. Other inheritance // patterns are currenty not supported. const nativeProps = { ...UIManager.RCTView.NativeProps, ...viewConfig.NativeProps, }; for (const key in nativeProps) { let useAttribute = false; const attribute = {}; const differ = TypeToDifferMap[nativeProps[key]]; if (differ) { attribute.diff = differ; useAttribute = true; } const processor = TypeToProcessorMap[nativeProps[key]]; if (processor) { attribute.process = processor; useAttribute = true; } viewConfig.validAttributes[key] = useAttribute ? attribute : true; } viewConfig.validAttributes.style = ReactNativeStyleAttributes; if (__DEV__) { componentInterface && verifyPropTypes( componentInterface, viewConfig, extraConfig && extraConfig.nativeOnly ); } return createReactNativeComponentClass(viewConfig);}
viewConfig携带了js所描述关于view的NativeProps,并传递给createReactNativeComponentClass
// createReactNativeComponentClass.jsvar createReactNativeComponentClass = function( viewConfig: ReactNativeBaseComponentViewConfig): ReactClass<any> { var Constructor = function(element) { this._currentElement = element; this._topLevelWrapper = null; this._hostParent = null; this._hostContainerInfo = null; this._rootNodeID = 0; this._renderedChildren = null; }; Constructor.displayName = viewConfig.uiViewClassName; Constructor.viewConfig = viewConfig; Constructor.propTypes = viewConfig.propTypes; Constructor.prototype = new ReactNativeBaseComponent(viewConfig); Constructor.prototype.constructor = Constructor; return ((Constructor: any): ReactClass<any>);};
再跟踪到ReactNativeBaseComponent.js,重点看mountComponent做了什么操作
// ReactNativeBaseComponent.jsmountComponent: function(transaction, hostParent, hostContainerInfo, context) { var tag = ReactNativeTagHandles.allocateTag(); this._rootNodeID = tag; this._hostParent = hostParent; this._hostContainerInfo = hostContainerInfo; // 省略代码 var updatePayload = ReactNativeAttributePayload.create( this._currentElement.props, this.viewConfig.validAttributes ); var nativeTopRootTag = hostContainerInfo._tag; UIManager.createView( tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload ); ReactNativeComponentTree.precacheNode(this, tag);this._registerListenersUponCreation(this._currentElement.props); this.initializeChildren( this._currentElement.props.children, tag, transaction, context ); return tag; },};
调用了UIManager.createView,但是跟踪进去的文件是UIManagerStatTracker.js,居然不是UIManager.js,这个就头疼了
进到UIManagerStatTracker.js文件,发现了三个接口:UIManager.createView、UIManager.updateView和UIManager.manageChildren,这三个方法在UIManagerModule.java中也出现过
// UIManagerModule.java@ReactModule(name = "RKUIManager")public class UIManagerModule extends ReactContextBaseJavaModule implements OnBatchCompleteListener, LifecycleEventListener, PerformanceCounter {@ReactMethod public void createView(int tag, String className, int rootViewTag, ReadableMap props) { if (DEBUG) { FLog.d( ReactConstants.TAG, "(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props); } mUIImplementation.createView(tag, className, rootViewTag, props); } @ReactMethod public void updateView(int tag, String className, ReadableMap props) { if (DEBUG) { FLog.d( ReactConstants.TAG, "(UIManager.updateView) tag: " + tag + ", class: " + className + ", props: " + props); } mUIImplementation.updateView(tag, className, props); } /** * Interface for adding/removing/moving views within a parent view from JS. * * @param viewTag the view tag of the parent view * @param moveFrom a list of indices in the parent view to move views from * @param moveTo parallel to moveFrom, a list of indices in the parent view to move views to * @param addChildTags a list of tags of views to add to the parent * @param addAtIndices parallel to addChildTags, a list of indices to insert those children at * @param removeFrom a list of indices of views to permanently remove. The memory for the * corresponding views and data structures should be reclaimed. */ @ReactMethod public void manageChildren( int viewTag, @Nullable ReadableArray moveFrom, @Nullable ReadableArray moveTo, @Nullable ReadableArray addChildTags, @Nullable ReadableArray addAtIndices, @Nullable ReadableArray removeFrom) { if (DEBUG) { FLog.d( ReactConstants.TAG, "(UIManager.manageChildren) tag: " + viewTag + ", moveFrom: " + moveFrom + ", moveTo: " + moveTo + ", addTags: " + addChildTags + ", atIndices: " + addAtIndices + ", removeFrom: " + removeFrom); } mUIImplementation.manageChildren( viewTag, moveFrom, moveTo, addChildTags, addAtIndices, removeFrom); }}
原来Rn和原生类似,也是先渲染内部子控件,然后再渲染外部控件。在render生命周期执行的时候会执行子控件的render方法,子控件会调用UIManager来把信息传递到原始的UIManagerModule,UIManagerModule根据传过来的Tag找到对应的UIManager,最后生成一个Operation添加到UI处理队列中,当mDispatchUIRunnables执行runable的时候调用Operation.execute抽象方法,其实就是调用UIManager.createViewInstance来真正生成View,然后调用viewManager.updateProperties 设置View的属性。这样一个控件就创建出来了。
参考:
https://segmentfault.com/a/1190000004586390
http://doslin.com/2017/03/15/react-native-source-code-analysis/
- React Native 源码浅析
- React-Native-源码分析
- React Native源码学习
- React Native源码编译
- React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)
- react-native-qiniu源码修改
- React Native Android Gradle 编译流程浅析
- React Native 六:使用源码构建React Native
- React的react-side-effect/react-document.title源码浅析
- react-native源码分析系列一
- react-native源码分析系列二
- react-native源码分析系列四 ReactRootView
- React Native之底层源码分析篇
- React-Native系列Android源码分析
- 更改React Native第三方组件源码
- React Native组件源码分析之Image
- React Native之底层源码分析篇
- React Native源码中JavaScriptCore详解
- AOP应用之------测试方法运行时间
- EA&UML日拱一卒-活动图::CallBehaviorAction
- java生成带二维码
- 数据类型
- Log4J.xml配置详解
- React Native 源码浅析
- java
- caffe下学习速率调整策略
- Struts2+poi实现Excel文件上传并插入数据库的操作
- 线段树部分总结 (单点,区间)更新,区间求和,求最大值(敌兵布阵&I Hate It&A Simple Problem with Integers)
- Token技术的功能及实现
- ExecutorService+LruCache+DiskLruCache用一个类打造图片加载库
- Ajax简单介绍及使用
- Linux安装软件方式介绍