React Native源码分析之Native UI的封装和管理.docx
- 文档编号:7022086
- 上传时间:2023-01-16
- 格式:DOCX
- 页数:13
- 大小:154.26KB
React Native源码分析之Native UI的封装和管理.docx
《React Native源码分析之Native UI的封装和管理.docx》由会员分享,可在线阅读,更多相关《React Native源码分析之Native UI的封装和管理.docx(13页珍藏版)》请在冰豆网上搜索。
ReactNative源码分析之NativeUI的封装和管理
【ReactNative】源码分析之NativeUI的封装和管理
ReactNative作为使用React开发Native应用的新框架,随着时间的增加,无论是社区还是个人对她的兴趣与日递增。
此文目的是希望和大家一起欣赏一下ReactNative的部分源码。
阅读源码好处多多,让攻城狮更溜的开发ReactNative应用的同时,也能梳理RN项目的设计思路,增加自己的内功修为,^_^。
好的,就让我们轻松的开始吧。
此篇是以Android平台源码分析为主,分享NativeUI的封装和管理,重点涉及react-native源码中com.facebook.react.uimanager包中的相关类。
通过下图对剖析的源码部分有个整体的概念,这是从下向上的调用关系。
因为上层是向我们直接暴露的类,所以我们采用从上向下的分析过程,以ReactImageManager作为切入点进行分析。
两个原因
图片是任何应用都必不可少的元素
ReactImageView封装Facebook的Fresco图片框架,在剖析的过程中可同时梳理RN封装第三方框架的过程。
首先看一下ReactImageManager的代码实现:
@ReactModule(name=ReactImageManager.REACT_CLASS)
publicclassReactImageManagerextendsSimpleViewManager
protectedstaticfinalStringREACT_CLASS="RCTImageView";
@Override
publicStringgetName(){
returnREACT_CLASS;
}
@Override
publicReactImageViewcreateViewInstance(ThemedReactContextcontext){
returnnewReactImageView(
context,
getDraweeControllerBuilder(),
getCallerContext());
}
}
此处的ReactImageView就是ReactNative封装的图像处理相关的NativeUI,他的定义如下,使用过Facebook的Fresco图片开源项目的开发者应该会很熟悉GenericDraweeView类,继承她实现自己的图片展示逻辑。
publicclassReactImageViewextendsGenericDraweeView{}
通过ReactImageManager对本地ReactImageView进行管理。
知识点一:
封装React可以使用的NativeUIView,需要创建一个ViewManager进行管理。
可以说这是标准ViewManager的官方推荐的写法,继承SimpleViewManager重写getName和createViewInstance方法,但是此处我们不禁会问–为什么?
为什么要重写这两个方法,在源码中是什么用的调用关系,导致了这种结果。
OK~,ViewManager中定义我们关心的getName和createViewInstance抽象方法。
而createViewInstance的使用是在createView方法中,看源码:
/**
*ViewManager类源码
*Createsaviewandinstallseventemittersonit.
*/
publicfinalTcreateView(
ThemedReactContextreactContext,
JSResponderHandlerjsResponderHandler){
Tview=createViewInstance(reactContext);
addEventEmitters(reactContext,view);
if(viewinstanceofReactInterceptingViewGroup){
((ReactInterceptingViewGroup)view).setOnInterceptTouchEventListener(jsResponderHandler);
}
returnview;
}
此方法完成两件事:
创建本地View对象,通过抽象方法createViewInstance(reactContext)完成,所以子类必须实现这个方法,否则View对象为空。
通过抽象方法addEventEmitters()注册事件的类型。
(比如我们自定义的监听事件,需要子类在此方法中注册)
OK~,以ViewManager的createView()为切入口,看一下整个创建可以被React使用的NativeUI的调用过程。
NativeViewHierarchyManager
查看createView()的调用,引出一个新的类,名字叫NativeViewHierarchyManager,同样位于com.facebook.react.uimanager包中。
在她的实现中,有这么一段代码,
publicvoidcreateView(
ThemedReactContextthemedContext,
inttag,
StringclassName,
@NullableReactStylesDiffMapinitialProps){
UiThreadUtil.assertOnUiThread();
try{
ViewManagerviewManager=mViewManagers.get(className);
Viewview=viewManager.createView(themedContext,mJSResponderHandler);
mTagsToViews.put(tag,view);
mTagsToViewManagers.put(tag,viewManager);
view.setId(tag);
if(initialProps!
=null){
viewManager.updateProperties(view,initialProps);
}
}finally{
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
此方法完成以下几个工作:
做线程判断,此方法必须在UI线程中调用。
通过ClassName获取到对应的ViewManager;
创建View实例,对应到我们剖析的主角就是ReactImageView,使用的方法就是上文提到的ViewManager的createView方法;
分别存储View和ViewManger到mTagsToViews和mTagsToViewManagers中;
设置新创建的View的Id,为什么要这么做?
是为了重用,减少开销,由于不是通过XML的形式创建,所以View并没有对应的ID,需要手动去设置,这里设置的ID值为传递过来的参数Tag
如果所有属性都初始化(@ReactPro注解的方法)完成,做一次回调,通知ViewManager去做属性全部初始化成功之后的操作。
最终会调用ViewManager的updateProperties函数,目的是更新属性Props和给子类刷新的机会。
publicfinalvoidupdateProperties(TviewToUpdate,ReactStylesDiffMapprops){
ViewManagerPropertyUpdater.updateProps(this,viewToUpdate,props);
onAfterUpdateTransaction(viewToUpdate);
}
更新属性。
更新之后要做的事情交给子类去实现。
例如我们的主角ReactImageManager要做的事情就是:
//ReactImageManager源码
@Override
protectedvoidonAfterUpdateTransaction(ReactImageViewview){
super.onAfterUpdateTransaction(view);
view.maybeUpdateView();
}
判断是否需要更新ImageView试图,如果需要马上更新。
知识点二:
如果你的需求中要求在属性都初始化完成之后,要做一些处理,请重写onAfterUpdateTransaction方法。
OK~,NativeViewHierarchyManager类设计用途,除了触发ViewManager创建NativeUI的衍生对象对外,还有哪些?
请看类图:
NativeViewHierarchyManager通过两个主要类控制NativeUIView的创建、更新、布局修改、属性变化等。
其中一个是上文提到的ViewManager类,另外一个是ViewManagerRegister类。
后者存放一个ViewManager的映射关系,通过getName的返回值作为key值,而getName的返回值,也是在JavaScript中定义Module时使用名字,如此当JavaScript调用React组件时,通过名称可以找到对应的ViewManager,通过ViewManager可以找到对应的NativeUIView,从而可以使用JavaScript构建原生应用效果。
帖一下下ViewManagerRegister的代码,方便理解viewManager.getName()方法的使用。
//ViewManagerRegistry源码
publicViewManagerRegistry(List
for(ViewManagerviewManager:
viewManagerList){
mViewManagers.put(viewManager.getName(),viewManager);
}
}
知识点三:
自定义ViewManager为什么要重写getName方法?
其一为JavaScript使用封装后的ReactView时,能对应到原生自定义的ViewManager,从而操作View;其二JavaScript当创建组件类时会使用这个名字。
知识点四:
自定义ViewManager重写createViewInstance的目的是创建NativeUIView的对象,并且添加到本地视图层级结构中。
UIViewOperationQueue
那么我的问题又来了,谁调用的NativeViewHierarchyManager的createView方法呐?
传递的Tag又是如何定义的?
OK,我们在源码中找到UIViewOperationQueue这个Java类,好样的,根据名字感觉她是UIView的执行队列。
具体是不是呐,那我们来看下代码:
//UIViewOperationQueue源码
privatefinalNativeViewHierarchyManagermNativeViewHierarchyManager;
privatefinalclassCreateViewOperationextendsViewOperation{
privatefinalThemedReactContextmThemedContext;
privatefinalStringmClassName;
privatefinal@NullableReactStylesDiffMapmInitialProps;
...
@Override
publicvoidexecute(){
mNativeViewHierarchyManager.createView(
mThemedContext,
mTag,
mClassName,
mInitialProps);
}
}
代码写的清晰明了,当有UI操作(动画、View的层次结构发生变化的时候),就会执行execute方法,也就是调用NativeViewHierarchyManager的createView方法创建新的View对象。
来个庖丁解牛CreateViewOperation在哪里被调用?
//UIViewOperationQueue源码
@GuardedBy("mNonBatchedOperationsLock")
privateArrayDeque
publicvoidenqueueCreateView(
ThemedReactContextthemedContext,
intviewReactTag,
StringviewClassName,
@NullableReactStylesDiffMapinitialProps){
synchronized(mNonBatchedOperationsLock){
mNonBatchedOperations.addLast(
newCreateViewOperation(
themedContext,
viewReactTag,
viewClassName,
initialProps));
}
}
创建一个数组队列,队列的名字为mNonBatchedOperations,每次调用enqueueCreateView方法,向数组队列中添加一个创建View的操作。
那么除了创建本地视图,她还定义了那些操作呐:
ViewOperation:
根据Tag,指定原生View去操作;
RemoveRootViewOperation:
删除TootView的操作;
UpdatePropertiesOperation:
更新属性操作;
UpdateLayoutOperation:
更新NativeView的位置和大小的操作;
ManageChildrenOperation:
管理子视图操作;
RegisterAnimationOperation:
注册动画的操作;
AddAnimationOperation:
增加动画的操作;
SetLayoutAnimationEnabledOperation:
设置布局动画是否可用的操作
MeasureOperation:
绘制操作
…
可以把UIViewOperationQueue看成一个缓冲带,他不去完成实质性的操作,真正的实现都在NativeViewHierarchyManager中完成,他将JavaScript要对NativeView做的所有操作都放在对应队列中,缓存起来批量处理。
根据上面的代码,创建NativeView衍生对象的操作,已经放到了队列中,那么是谁操作的队列去添加操作(Operation)呐?
comeon搞起~
NativeViewHierarchyOptimizer
不难跟到NativeViewHierarchyOptimizer类,看名字像是NativeViewHierarchy的优化程序,看代码后,你还别说还真是做优化本地UI视图层级结构的工作的,看看此类的官方介绍:
负责优化本地视图层次结构,同时仍然遵循JS指定的最终UI样式。
基本上,JS向我们发送了一个节点层次结构,虽然在JS中容易理解,但是直接转换为本地视图效率很低。
这个类位于UIManagerModule(直接接收来自JS的视图命令)和UIViewOperationQueue之间,它使本地视图层次上的实际操作入队。
它能够从UIManagerModule获取指令,并将输出指令传递到本地视图层次结构,使用较少的视图,实现相同的效果。
对于NativeViewHierarchyOptimizer的优化过程,咱们看一下他的实现思路,代码如下:
privatestaticfinalbooleanENABLED=true;
/**
*HandlesacreateViewcall.Mayormaynotactuallycreateanativeview.
*/
publicvoidhandleCreateView(
ReactShadowNodenode,
ThemedReactContextthemedContext,
@NullableReactStylesDiffMapinitialProps){
if(!
ENABLED){
inttag=node.getReactTag();
mUIViewOperationQueue.enqueueCreateView(
themedContext,
tag,
node.getViewClass(),
initialProps);
return;
}
booleanisLayoutOnly=node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME)&&
isLayoutOnlyAndCollapsable(initialProps);
node.setIsLayoutOnly(isLayoutOnly);
if(!
isLayoutOnly){
mUIViewOperationQueue.enqueueCreateView(
themedContext,
node.getReactTag(),
node.getViewClass(),
initialProps);
}
}
这里的ENABLED非常有意思,默认值是true,是私有的常量,不可重新赋值,那就逗了,所有if(!
ENABLED)里面的代码永远不会执行,这是我的理解,如果你有其他的理解,欢迎交流。
通过isLayoutOnly来判断是否向创建View的队列中添加元素,这里引入了两个关键类ReactShadowNode和ViewProps,先来说一下ViewProps的Java类,其定义了很多属性名的常量。
//ViewProps源码
publicstaticfinalStringALIGN_ITEMS="alignItems";
publicstaticfinalStringALIGN_SELF="alignSelf";
publicstaticfinalStringOVERFLOW="overflow";
publicstaticfinalStringBOTTOM="bottom";
...
另外将只导致布局变化(LayoutChange),不引起重绘(noDrawing)的常量放在HashSet中,起名为LAYOU_ONLY_PROPS,在NativeViewHierarchyOptimizer类中运用,起到优化本地试图层级的效果。
代码中的判断条件为节点为View类型并且仅改变布局属性的话,就不需要重新创建本地View的实例,否则创建,通过这种逻辑来优化本地View的实例创建,从而节省内存开支。
当然NativeViewHierarchyOptimizer还做了其他命令的优化工作,将优化后需要NativeView执行的操作,存储到上文中的UIViewOperationQueue中,等待JavaScript批处理执行。
OK~,那么JavaScript命令又是通过什么传递到NativeViewHierarchyOptimizer中的呐?
ReactShadowNode类又是如何传递过来的,JavaScript和Native通信的过程中扮演什么样的角色?
?
?
我们离真相越来越近了,ComeOn~~
UIImplementation
答案是通过UIImplementation类,看一小部分源码实现:
//UIImplementation源码
protectedvoidhandleCreateView(
ReactShadowNodecssNode,
introotViewTag,
@NullableReactStylesDiffMapstyles){
if(!
cssNode.isVirtual()){
mNativeViewHierarchyOptimizer.handleCreateView(cssNode,cssNode.getThemedContext(),styles);
}
}
调用的方法很熟悉,上文刚介绍完,继续跟
/**
*UIImplementation源码
*InvokedbyReacttocreateanewnodewithagiventag,classnameandproperties.
*/
publicvoidcreateView(inttag,StringclassName,introotViewTag,ReadableMapprops){
ReactShadowNodecssNode=createShadowNode(className);
ReactShadowNoderootNode=mShadowNodeRegistry.getNode(rootViewTag);
cssNode.setReactTag(g);
cssNode.setViewClassName(className);
cssNode.setRootNode(rootNode);
cssNode.setThemedContext(rootNode.getThemedContext());
mShadowNodeRegistry.addNode(cssNode);
ReactStylesDiffMapstyles=null;
if(props!
=null){
styles=newReactStylesDiffMap(props);
cssNode.updateProperties(styles);
}
handleCreateView(cssNode,rootViewTag,styles);
}
在createView函数中,最后调用了handleCreateView,另外让人兴奋的是,找到了ReactShadowNode的源头,在这里根据className创建名称为cssNode的ReactShadowNode对象,上文使用的node.getReactTag()获取tag的方法,根源就在此处。
在函数的注解中介绍到React通过给定的tag、类名、属性调用这个函数去创建一个新的节点。
注:
在Androi
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- React Native源码分析之Native UI的封装和管理 Native 源码 分析 UI 封装 管理