【React Native】源码分析之Native UI的封装和管理
来源:互联网 发布:中国轻工业出版社 知乎 编辑:程序博客网 时间:2024/05/21 11:01
转载请注明出处:http://blog.csdn.net/u013531824/article/details/54020287,谢谢。
ReactNative作为使用React开发Native应用的新框架,随着时间的增加,无论是社区还是个人对她的兴趣与日递增。此文目的是希望和大家一起欣赏一下ReactNative的部分源码。阅读源码好处多多,让攻城狮更溜的开发ReactNative应用的同时,也能梳理RN项目的设计思路,增加自己的内功修为,^_^。
好的,就让我们轻松的开始吧。此篇是以Android
平台源码分析为主,分享Native UI的封装和管理,重点涉及react-native源码中com.facebook.react.uimanager
包中的相关类。
通过下图对剖析的源码部分有个整体的概念,这是从下向上的调用关系。
因为上层是向我们直接暴露的类,所以我们采用从上向下的分析过程,以ReactImageManager
作为切入点进行分析。两个原因
- 图片是任何应用都必不可少的元素
- ReactImageView封装Facebook的Fresco图片框架,在剖析的过程中可同时梳理RN封装第三方框架的过程。
首先看一下ReactImageManager的代码实现:
@ReactModule(name = ReactImageManager.REACT_CLASS)public class ReactImageManager extends SimpleViewManager<ReactImageView> { protected static final String REACT_CLASS = "RCTImageView"; @Override public String getName() { return REACT_CLASS; } @Override public ReactImageView createViewInstance(ThemedReactContext context) { return new ReactImageView( context, getDraweeControllerBuilder(), getCallerContext()); } }
此处的ReactImageView
就是ReactNative封装的图像处理相关的Native UI ,他的定义如下,使用过Facebook
的Fresco
图片开源项目的开发者应该会很熟悉GenericDraweeView
类,继承她实现自己的图片展示逻辑。
public class ReactImageView extends GenericDraweeView {}
通过ReactImageManager
对本地ReactImageView
进行管理。
知识点一:封装React可以使用的Native UI View,需要创建一个ViewManager进行管理。
可以说这是标准ViewManager
的官方推荐的写法,继承SimpleViewManager
重写getName
和createViewInstance
方法,但是此处我们不禁会问–为什么?为什么要重写这两个方法,在源码中是什么用的调用关系,导致了这种结果。
下面看一张ViewManager的继承关系图:
上图可以清晰反馈ReactImageManager
的继承关系,最终定位到ViewManager
类,同时SimpleViewManager
负责对View
的管理,而对ViewGroup
的封装需要继承ViewGroupManager
实现。也许上面问题的答案我们可以在他的超父类ViewManager
中找到答案。
看一下ViewManager的类图可以给我们什么信息:
OK~,ViewManager
中定义我们关心的getName
和createViewInstance
抽象方法。而createViewInstance
的使用是在createView
方法中,看源码:
/** * ViewManager类源码 * Creates a view and installs event emitters on it. */public final T createView( ThemedReactContext reactContext, JSResponderHandler jsResponderHandler) { T view = createViewInstance(reactContext); addEventEmitters(reactContext, view); if (view instanceof ReactInterceptingViewGroup) { ((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler); } return view;}
此方法完成两件事:
- 创建本地View对象,通过抽象方法
createViewInstance(reactContext)
完成,所以子类必须实现这个方法,否则View
对象为空。 - 通过抽象方法
addEventEmitters()
注册事件的类型。(比如我们自定义的监听事件,需要子类在此方法中注册)
OK ~ , 以ViewManager的createView()
为切入口,看一下整个创建可以被React使用的Native UI的调用过程。
NativeViewHierarchyManager
查看createView()
的调用,引出一个新的类,名字叫NativeViewHierarchyManager
,同样位于com.facebook.react.uimanager
包中。在她的实现中,有这么一段代码,
public void createView( ThemedReactContext themedContext, int tag, String className, @Nullable ReactStylesDiffMap initialProps) { UiThreadUtil.assertOnUiThread(); try { ViewManager viewManager = mViewManagers.get(className); View view = 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
和给子类刷新的机会。
public final void updateProperties(T viewToUpdate, ReactStylesDiffMap props) { ViewManagerPropertyUpdater.updateProps(this, viewToUpdate, props); onAfterUpdateTransaction(viewToUpdate);}
- 更新属性。
- 更新之后要做的事情交给子类去实现。
例如我们的主角ReactImageManager要做的事情就是:
//ReactImageManager源码@Overrideprotected void onAfterUpdateTransaction(ReactImageView view) { super.onAfterUpdateTransaction(view); view.maybeUpdateView();}
判断是否需要更新ImageView试图,如果需要马上更新。
知识点二:如果你的需求中要求在属性都初始化完成之后,要做一些处理,请重写onAfterUpdateTransaction方法。
OK~,NativeViewHierarchyManager
类设计用途,除了触发ViewManager创建Native UI的衍生对象对外,还有哪些?请看类图:
NativeViewHierarchyManager
通过两个主要类控制Native UI View的创建、更新、布局修改、属性变化等。其中一个是上文提到的ViewManager
类,另外一个是ViewManagerRegister
类。后者存放一个ViewManager
的映射关系,通过getName
的返回值作为key值,而getName
的返回值,也是在JavaScript
中定义Module
时使用名字,如此当JavaScript
调用React组件时,通过名称可以找到对应的ViewManager
,通过ViewManager
可以找到对应的Native UI View,从而可以使用JavaScript
构建原生应用效果。帖一下下ViewManagerRegister
的代码,方便理解viewManager.getName()
方法的使用。
//ViewManagerRegistry源码 public ViewManagerRegistry(List<ViewManager> viewManagerList) { for (ViewManager viewManager : viewManagerList) { mViewManagers.put(viewManager.getName(), viewManager); } }
知识点三: 自定义ViewManager为什么要重写getName方法?其一为JavaScript使用封装后的ReactView时,能对应到原生自定义的ViewManager,从而操作View;其二JavaScript当创建组件类时会使用这个名字。
知识点四:自定义ViewManager重写createViewInstance的目的是创建Native UI View的对象,并且添加到本地视图层级结构中。
UIViewOperationQueue
那么我的问题又来了,谁调用的NativeViewHierarchyManager
的createView
方法呐?传递的Tag
又是如何定义的?OK,我们在源码中找到UIViewOperationQueue
这个Java
类,好样的,根据名字感觉她是UIView
的执行队列。具体是不是呐,那我们来看下代码:
//UIViewOperationQueue源码private final NativeViewHierarchyManager mNativeViewHierarchyManager;private final class CreateViewOperation extends ViewOperation { private final ThemedReactContext mThemedContext; private final String mClassName; private final @Nullable ReactStylesDiffMap mInitialProps; ... @Override public void execute() { mNativeViewHierarchyManager.createView( mThemedContext, mTag, mClassName, mInitialProps); }}
代码写的清晰明了,当有UI
操作(动画、View
的层次结构发生变化的时候),就会执行execute
方法,也就是调用NativeViewHierarchyManager
的createView
方法创建新的View
对象。来个庖丁解牛CreateViewOperation
在哪里被调用?
// UIViewOperationQueue源码@GuardedBy("mNonBatchedOperationsLock")private ArrayDeque<UIOperation> mNonBatchedOperations = new ArrayDeque<>();public void enqueueCreateView( ThemedReactContext themedContext, int viewReactTag, String viewClassName, @Nullable ReactStylesDiffMap initialProps) { synchronized (mNonBatchedOperationsLock) { mNonBatchedOperations.addLast( new CreateViewOperation( themedContext, viewReactTag, viewClassName, initialProps)); }}
创建一个数组队列,队列的名字为mNonBatchedOperations
,每次调用enqueueCreateView
方法,向数组队列中添加一个创建View的操作。
那么除了创建本地视图,她还定义了那些操作呐:
- ViewOperation:根据Tag,指定原生View去操作;
- RemoveRootViewOperation:删除TootView的操作;
- UpdatePropertiesOperation:更新属性操作;
- UpdateLayoutOperation:更新Native View的位置和大小的操作;
- ManageChildrenOperation:管理子视图操作;
- RegisterAnimationOperation:注册动画的操作;
- AddAnimationOperation : 增加动画的操作;
- SetLayoutAnimationEnabledOperation:设置布局动画是否可用的操作
- MeasureOperation:测量操作
- …
可以把UIViewOperationQueue看成一个缓冲带,他不去完成实质性的操作,真正的实现都在NativeViewHierarchyManager中完成,他将JavaScript要对Native View做的所有操作都放在对应队列中,缓存起来批量处理。根据上面的代码,创建Native View衍生对象的操作,已经放到了队列中,那么是谁操作的队列去添加操作(Operation)呐?
come on 搞起~
NativeViewHierarchyOptimizer
不难跟到NativeViewHierarchyOptimizer
类,看名字像是NativeViewHierarchy
的优化程序,看代码后,你还别说还真是做优化本地UI视图层级结构的工作的,看看此类的官方介绍:
负责优化本地视图层次结构,同时仍然遵循JS指定的最终UI样式。 基本上,JS向我们发送了一个节点层次结构,虽然在JS中容易理解,但是直接转换为本地视图效率很低。 这个类位于UIManagerModule(直接接收来自JS的视图命令)和UIViewOperationQueue之间,它使本地视图层次上的实际操作入队。它能够从UIManagerModule获取指令,并将输出指令传递到本地视图层次结构,使用较少的视图,实现相同的效果。
对于NativeViewHierarchyOptimizer
的优化过程,咱们看一下他的实现思路,代码如下:
private static final boolean ENABLED = true;/** * Handles a createView call. May or may not actually create a native view. */public void handleCreateView( ReactShadowNode node, ThemedReactContext themedContext, @Nullable ReactStylesDiffMap initialProps) { if (!ENABLED) { int tag = node.getReactTag(); mUIViewOperationQueue.enqueueCreateView( themedContext, tag, node.getViewClass(), initialProps); return; } boolean isLayoutOnly = 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源码 public static final String ALIGN_ITEMS = "alignItems"; public static final String ALIGN_SELF = "alignSelf"; public static final String OVERFLOW = "overflow"; public static final String BOTTOM = "bottom"; ...
另外将只导致布局变化(Layout Change),不引起重绘(no Drawing)的常量放在HashSet
中,起名为LAYOU_ONLY_PROPS
,在NativeViewHierarchyOptimizer
类中运用,起到优化本地试图层级的效果。
代码中的判断条件为节点为View
类型并且仅改变布局属性的话,就不需要重新创建本地View
的实例,否则创建,通过这种逻辑来优化本地View
的实例创建,从而节省内存开支。
当然NativeViewHierarchyOptimizer
还做了其他命令的优化工作,将优化后需要Native View执行的操作,存储到上文中的UIViewOperationQueue
中,等待JavaScript批处理执行。
OK~,那么JavaScript命令又是通过什么传递到NativeViewHierarchyOptimizer
中的呐?ReactShadowNode类又是如何传递过来的,JavaScript和Native通信的过程中扮演什么样的角色???
我们离真相越来越近了,Come On~~
UIImplementation
答案是通过UIImplementation类,看一小部分源码实现:
//UIImplementation源码protected void handleCreateView( ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) { if (!cssNode.isVirtual()) { mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles); }}
调用的方法很熟悉,上文刚介绍完,继续跟
/** * UIImplementation 源码 * Invoked by React to create a new node with a given tag, class name and properties. */public void createView(int tag, String className, int rootViewTag, ReadableMap props) { ReactShadowNode cssNode = createShadowNode(className); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); cssNode.setReactTag(tag); cssNode.setViewClassName(className); cssNode.setRootNode(rootNode); cssNode.setThemedContext(rootNode.getThemedContext()); mShadowNodeRegistry.addNode(cssNode); ReactStylesDiffMap styles = null; if (props != null) { styles = new ReactStylesDiffMap(props); cssNode.updateProperties(styles); } handleCreateView(cssNode, rootViewTag, styles);}
在createView
函数中,最后调用了handleCreateView
,另外让人兴奋的是,找到了ReactShadowNode
的源头,在这里根据className
创建名称为cssNode
的ReactShadowNode
对象,上文使用的node.getReactTag()
获取tag
的方法,根源就在此处。在函数的注解中介绍到React
通过给定的tag
、类名、属性调用这个函数去创建一个新的节点。
注: 在Android中,布局的每个元素我们称之为View。在React中,因为采用Web的思想,布局中的元素被称之为节点(node)。
所以分析到这里,不用看ReactShadowNode
的源码实现,我们也能猜测到他的用途,他代表了React
布局中的一个元素,对应Native
布局层级中的一个View
。他的属性包括节点Tag(ReactTag)
、节点类名(ViewClassName
)、根节点信息(rootNode
)、位置、自身大小等等信息,可以理解为React
虚拟数上的一个最基础的节点。拥有这些信息,就可获取到当前节点的位置进行布局。
再进一步,创建ReactShadowNode
方法:
//UIImplementation源码protected ReactShadowNode createShadowNode(String className) { ViewManager viewManager = mViewManagers.get(className); return viewManager.createShadowNodeInstance();}
奥,好熟悉竟然是ViewManager
,我们就是从这个类作为入口进行分析的啊,OK~,看createShadowNodeInstance()
方法,
//ViewManager源码/** * This method should return a subclass of {@link ReactShadowNode} which will be then used for * measuring position and size of the view. In mose of the cases this should just return an * instance of {@link ReactShadowNode} */public abstract C createShadowNodeInstance();
原来是一个抽象方法,那我们的主角ReactImageManager
需要去实现这个方法,找一下发现在SimpleViewManager
里进行了实现,
//SimpleViewManager源码@Overridepublic LayoutShadowNode createShadowNodeInstance() { return new LayoutShadowNode();}
LayoutShadowNode
提供了基本的布局属性,如宽高、flex
等等,这里也使用到了ViewProps定义的一些属性常量。
public class LayoutShadowNode extends ReactShadowNode { @ReactProp(name = ViewProps.WIDTH, defaultFloat = CSSConstants.UNDEFINED) public void setWidth(float width) { setStyleWidth(CSSConstants.isUndefined(width) ? width : PixelUtil.toPixelFromDIP(width)); }
这里就给了我们想象的空间,除去这些基本的布局属性,如果我们想自定义View
,就可以继承LayoutShadowNode
,添加自定义的布局属性,在createShadowNodeInstance()
中进行初始化,同样可以被React
承认。具体可以参考ReactTextInlineImageShadowNode
类的实现,添加ImageSpan
的过程。
知识点五:通过继承LayoutShadowNode,添加自定义的布局属性。就想我们在Android中自定义View添加新属性,需要在XML中注册相同。
另外根据源码可以了解到,UIImplement还做了一件大事,首先他先创建了ReactShadowNode
,我们称之为影子节点,然后通过UIImplemention
创建由指定影子节点的相关属性创建的Native View。如此一个Native View对应一个ReactShadowNode
,JavaScript可以控制影子节点属性,从而改变Native View的布局和形态。
注:ReactShadowNode,网上称之为影子节点,感觉还挺好听的,JS可以直接控制他的属性,从而对Native View进行布局(位置、大小、内容等)。
OK~ ,谁可以控制UIImplementation
的调用?
UIManagerModule
答案是com.facebook.react.uimanager
包中的关键类,名字叫UIManagerModule。通俗一点说,此类的方法可以被JavaScript调用,也就是可以接收JavaScript的命令。然后再调用UIImplementation
去执行具体的操作。
这是在JavaScript线程(非UI线程)对View进行布局和测量的一个关键类,是JS控制Native View的入口。然后根据我们上面的一起的一步一步的分析,最终实现控制Native View的效果。
看下面一小部分源码:
//UIManagerModule源码@ReactMethodpublic void createView(int tag, String className, int rootViewTag, ReadableMap props) { mUIImplementation.createView(tag, className, rootViewTag, props);}
调用UIImplementation
创建Native View
。
总结
最后,我们对上文的整个剖析,做个总结,通过下图的梳理,希望能对我们理解这部分源码思路有所帮助(此图良心出品^_^)。
好的,但是问题又来了,为什么UIManagerModule方法能够被JavaScript调用,答案是方法被@ReactMethod
注解 ,但是为什么?没事就问句问什么^_^,我们下篇文章再详解分析。
谢谢阅读,希望能对您理解ReactNative有帮助~~~
- 【React Native】源码分析之Native UI的封装和管理
- react native封装UI
- React-Native-源码分析
- React Native之底层源码分析篇
- React Native组件源码分析之Image
- React Native之底层源码分析篇
- React Native Application和Activity源码分析
- React Native之原生UI组件封装---适配Android
- React Native之原生UI组件封装---适配Android
- React Native 封装原生UI组件(iOS)
- React Native封装原生UI组件
- [React Native混合开发]React Native for iOS之CSS和UI布局
- react native之知乎日报源码分析一
- ReactNative进阶之react-native-storage的使用及封装
- react native Toast封装
- react-native http封装
- react-native modal封装
- React Native 控件封装
- 每天学习opensatck(10)
- Python初学者笔记(3):输出列表中的奇数/奇数项,字符串中的偶数项,字符串大小写转换
- 学习笔记之Android利用UncaughtExceptionHandler捕获全局异常
- hibernate
- BZOJ3511 土地划分
- 【React Native】源码分析之Native UI的封装和管理
- [生存志] 第98节 荀子谈性恶
- 总结HoloLens的一些小功能
- activity向另一个activity的fragment传值的问题
- yarn常用命令
- uva11021 Tribles
- cmd中检测远程的ip和端口是否处于监听状态
- 正则表达式入门(一)
- Linux -mv命令的10个实用例子