React Native 深入解析:原生调用与动画机制
1. 原生调用机制
在 React Native 开发中,原生调用是实现 JavaScript 与原生代码交互的关键环节。下面我们将深入探讨原生调用的相关内容。
1.1 生成原生模块
首先,需要对原生模块的方法进行处理。具体步骤如下:
1. 遍历原生模块的所有方法。
2. 创建这些方法在 JavaScript 层的对应方法。
3. 将
genModule
注入为
__fbGenNativeModule
,以便从原生层调用。
global.__fbGenNativeModule = genModule;
1.2 生成方法
genMethod
函数用于生成原生方法的包装器,根据方法类型(
promise
或非
promise
)进行不同处理。
function genMethod (
moduleID: number, methodID: number, type: MethodType
) {
let fn = null;
if (type === 'promise') {
fn = function promiseMethodWrapper(...args: Array<any>) {
const enqueueingFrameError: ExtendedError = new Error();
return new Promise((resolve, reject) => {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData => reject(updateErrorWithErrorData(
errorData, enqueueingFrameError)
),
);
});
};
} else {
fn = function nonPromiseMethodWrapper(...args: Array<any>) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccessCallback = typeof lastArg === 'function';
const hasErrorCallback = typeof secondLastArg === 'function';
hasErrorCallback && invariant(hasSuccessCallback,
'Cannot have a non-function arg after a function arg.',
);
const onSuccess = hasSuccessCallback ? lastArg : null;
const onFail = hasErrorCallback ? secondLastArg : null;
const callbackCount = hasSuccessCallback + hasErrorCallback;
args = args.slice(0, args.length - callbackCount);
if (type === 'sync') {
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
} else {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
}
};
}
fn.type = type;
return fn;
}
这里的处理逻辑如下:
1. 对于
promise
类型的方法,使用
Promise
包装原生调用,并处理成功和失败回调。
2. 对于非
promise
类型的方法,根据参数判断是否有成功和错误回调,并进行相应处理。如果是同步方法,使用
callNativeSyncHook
调用;否则,使用
enqueueNativeCall
进行异步调用。
1.3 原生调用方法
所有原生调用最终会收敛到两个从原生层注入的方法:
enqueueNativeCall
和
callNativeSyncHook
。
enqueueNativeCall(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
) {
this.processCallbacks(
moduleID, methodID, params, onFail, onSucc
);
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
this._queue[PARAMS].push(params);
const now = Date.now();
if (
global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
) {
const queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
}
callNativeSyncHook(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
): any {
this.processCallbacks(
moduleID, methodID, params, onFail, Succ
);
return global.nativeCallSyncHook(
moduleID, methodID, params
);
}
这两个方法的作用如下:
1.
enqueueNativeCall
用于异步方法调用,会将方法调用信息加入队列,并在满足条件时调用
nativeFlushQueueImmediate
刷新队列。
2.
callNativeSyncHook
用于同步方法调用,直接调用
nativeCallSyncHook
执行原生方法。
1.4 执行 Bundle
JSCRuntime
是 JavaScript 与原生通信的核心,它封装了 JavaScriptCore。通过
JSEvaluateScript
函数执行脚本。
jsi::Value JSCRuntime::evaluateJavaScript(
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string &sourceURL) {
std::string tmp(
reinterpret_cast<const char *>(buffer->data()),
buffer->size()
);
JSStringRef sourceRef =
JSStringCreateWithUTF8CString(tmp.c_str());
JSStringRef sourceURLRef = nullptr;
if (!sourceURL.empty()) {
sourceURLRef = JSStringCreateWithUTF8CString(
sourceURL.c_str());
}
JSValueRef exc = nullptr;
JSValueRef res =
JSEvaluateScript(
ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
JSStringRelease(sourceRef);
if (sourceURLRef) {
JSStringRelease(sourceURLRef);
}
checkException(res, exc);
return createValue(res);
}
这个过程的关键步骤如下:
1. 将脚本数据转换为
JSStringRef
。
2. 调用
JSEvaluateScript
执行脚本。
3. 释放
JSStringRef
资源。
1.5 双向通信
通过 JavaScriptCore API 实现双向通信,将原生实例和函数注入全局对象。
void JSIExecutor::initializeRuntime() {
SystraceSection s("JSIExecutor::initializeRuntime");
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(
nativeModules_
)
)
);
}
具体步骤为:
1. 使用
JSContextGetGlobalObject
获取全局对象。
2. 使用
JSClassCreate
和
JSObjectMake
创建原生实例。
3. 使用
JSObjectSetProperty
将实例注入全局对象。
1.6 原生模块元数据
原生模块元数据存储在 C++ 层,通过
RCTNativeModule
包装
RCTModuleData
并存储在
Instance::moduleRegistry_
中。
static MethodCallResult
invokeInner(RCTBridge *bridge, RCTModuleData *moduleData,
unsigned int methodId, const folly::dynamic ¶ms)
{
if (!bridge || !bridge.valid || !moduleData) {
return folly::none;
}
id<RCTBridgeMethod> method = moduleData.methods[methodId];
NSArray *objcParams = convertFollyDynamicToId(params);
@try {
id result =
[method invokeWithBridge:bridge
module:moduleData.instance arguments:objcParams];
return convertIdToFollyDynamic(result);
} @catch (NSException *exception) {
if ([exception.name hasPrefix:RCTFatalExceptionName]) {
@throw exception;
}
}
return folly::none;
}
方法调用的步骤如下:
1. 从
RCTModuleData
中获取方法对象。
2. 调用方法并处理结果。
2. 动画机制
React Native 的原生驱动动画通过直接的原生到原生通信实现高性能,关键在于动画节点图(ANG)的建立和事件传递。
2.1 动画概述
动画的核心是原生事件源、动画值和值插值/计算,这些通过动画节点图(ANG)连接。ANG 在 JavaScript 层定义并传递到原生层,使动画可以在原生层独立执行。
graph LR
A[事件源] --> B[动画节点图]
B --> C[事件接收器]
2.2 创建动画组件
createAnimatedComponent
是动画逻辑的入口,它是一个高阶组件(HOC),用于包装组件并设置动画相关的元数据。
function createAnimatedComponent<Props: {+[string]: mixed, ...}, Instance>(
Component: React.AbstractComponent<Props, Instance>,
): AnimatedComponentType<Props, Instance> {
invariant(
typeof Component !== 'function' ||
(Component.prototype && Component.prototype.isReactComponent),
'`createAnimatedComponent` does not support stateless functional components; ' +
'use a class component instead.',
);
class AnimatedComponent extends React.Component<Object> {
_component: any;
_propsAnimated: AnimatedProps;
_attachNativeEvents() {
const scrollableNode = this._component?.getScrollableNode
? this._component.getScrollableNode()
: this._component;
for (const key in this.props) {
const prop = this.props[key];
if (prop instanceof AnimatedEvent && prop.__isNative) {
prop.__attach(scrollableNode, key);
}
}
}
_attachProps(nextProps) {
const oldPropsAnimated = this._propsAnimated;
this._propsAnimated = new AnimatedProps(
nextProps,
this._animatedPropsCallback,
);
}
_setComponentRef = setAndForwardRef({
getForwardedRef: () => this.props.forwardedRef,
setLocalRef: ref => {
this._prevComponent = this._component;
this._component = ref;
},
});
render() {
const props = this._propsAnimated.__getValue();
return (
<Component
{...props}
ref={this._setComponentRef}
/>
);
}
UNSAFE_componentWillMount() {
this._attachProps(this.props);
}
componentDidMount() {
this._propsAnimated.setNativeView(this._component);
this._attachNativeEvents();
}
}
return React.forwardRef(
function AnimatedComponentWrapper(props, ref) {
return (
<AnimatedComponent
{...props}
{...(ref == null ? null : {forwardedRef: ref})}
/>
);
}
);
}
主要步骤如下:
1. 不支持无状态函数组件,使用 ref 转发技术。
2. 设置组件引用。
3. 以
_propsAnimated
为起点建立 ANG。
4. 将
_propsAnimated
与当前组件绑定,连接 ANG 到事件接收器。
5. 将原生事件附加到当前组件,连接 ANG 到事件源。
2.3 建立动画节点图
ANG 的建立分为 JavaScript 层和原生层两个阶段。
2.3.1 JavaScript 层
从
_attachProps
方法开始,实例化
AnimatedProps
并调用
__attach
方法。
_attachProps(nextProps) {
this._propsAnimated = new AnimatedProps(
nextProps,
this._animatedPropsCallback,
);
}
class AnimatedProps extends AnimatedNode {
_props: Object;
constructor(props: Object, callback: () => void) {
super();
this.__attach();
}
__attach(): void {
for (const key in this._props) {
const value = this._props[key];
if (value instanceof AnimatedNode) {
value.__addChild(this);
}
}
}
}
这个过程通过递归调用
__attach
和
__addChild
遍历整个 ANG。
graph LR
A[AnimatedProps] --> B[AnimatedNode1]
A --> C[AnimatedNode2]
B --> D[AnimatedNode3]
2.3.2 原生层
通过
__makeNative
方法将动画节点转换为原生层表示。
__makeNative() {
if (!this.__isNative) {
this.__isNative = true;
for (const child of this._children) {
child.__makeNative();
NativeAnimatedHelper.API.connectAnimatedNodes(
this.__getNativeTag(),
child.__getNativeTag(),
);
}
}
super.__makeNative();
}
__getNativeTag(): number {
const nativeTag =
this.__nativeTag ?? NativeAnimatedHelper.generateNewNodeTag();
if (this.__nativeTag == null) {
this.__nativeTag = nativeTag;
NativeAnimatedHelper.API.createAnimatedNode(
nativeTag,
this.__getNativeConfig(),
);
this.__shouldUpdateListenersForNewNativeTag = true;
}
return nativeTag;
}
关键步骤如下:
1. 设置
__isNative
标志。
2. 递归调用子节点的
__makeNative
方法。
3. 调用
createAnimatedNode
注册节点。
4. 调用
connectAnimatedNodes
连接节点。
在原生层,
RCTNativeAnimatedNodesManager
负责创建和管理动画节点。
- (void)createAnimatedNode:(nonnull NSNumber *)tag
config:(NSDictionary<NSString *, id> *) config
{
static NSDictionary *map;
static dispatch_once_t mapToken;
dispatch_once(&mapToken, ^{
map = @{@"style" : [RCTStyleAnimatedNode class],
@"value" : [RCTValueAnimatedNode class],
@"props" : [RCTPropsAnimatedNode class],
@"interpolation" : [RCTInterpolationAnimatedNode class],
@"addition" : [RCTAdditionAnimatedNode class],
@"diffclamp": [RCTDiffClampAnimatedNode class],
@"division" : [RCTDivisionAnimatedNode class],
@"multiplication" : [RCTMultiplicationAnimatedNode class],
@"modulus" : [RCTModuloAnimatedNode class],
@"subtraction" : [RCTSubtractionAnimatedNode class],
@"transform" : [RCTTransformAnimatedNode class],
@"tracking" : [RCTTrackingAnimatedNode class]};
});
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
Class nodeClass = map[nodeType];
if (!nodeClass) {
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
return;
}
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
node.manager = self;
_animationNodes[tag] = node;
[node setNeedsUpdate];
}
- (void)connectAnimatedNodes:(nonnull NSNumber *)parentTag
childTag:(nonnull NSNumber *)childTag
{
RCTAssertParam(parentTag);
RCTAssertParam(childTag);
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
RCTAnimatedNode *childNode = _animationNodes[childTag];
RCTAssertParam(parentNode);
RCTAssertParam(childNode);
[parentNode addChild:childNode];
}
通过以上步骤,我们可以深入理解 React Native 的原生调用和动画机制,为开发高性能的应用提供支持。在实际开发中,我们可以根据这些原理进行优化和扩展,提高应用的性能和用户体验。例如,合理使用懒加载来优化启动时间,根据动画需求灵活调整 ANG 的结构等。同时,对于复杂的应用场景,我们可以结合这些机制实现更复杂的交互效果。
React Native 深入解析:原生调用与动画机制
2. 动画机制(续)
2.4 动画节点的关键方法
在动画节点图(ANG)的构建和管理过程中,有几个关键方法起到了重要作用。
-
__attach方法 :用于在 JavaScript 层建立 ANG。在AnimatedProps类中,通过递归调用__attach和addChild方法,遍历整个 ANG。这个过程确保了所有动画节点之间的连接关系被正确建立。
class AnimatedProps extends AnimatedNode {
_props: Object;
constructor(props: Object, callback: () => void) {
super();
this.__attach();
}
__attach(): void {
for (const key in this._props) {
const value = this._props[key];
if (value instanceof AnimatedNode) {
value.__addChild(this);
}
}
}
}
-
__makeNative方法 :负责将动画节点转换为原生层表示。它会递归调用子节点的__makeNative方法,并通过NativeAnimatedHelper.API调用原生方法connectAnimatedNodes来连接节点。
__makeNative() {
if (!this.__isNative) {
this.__isNative = true;
for (const child of this._children) {
child.__makeNative();
NativeAnimatedHelper.API.connectAnimatedNodes(
this.__getNativeTag(),
child.__getNativeTag(),
);
}
}
super.__makeNative();
}
-
__getNativeTag方法 :为动画节点生成唯一的标签,并调用createAnimatedNode方法在原生层创建节点。
__getNativeTag(): number {
const nativeTag =
this.__nativeTag ?? NativeAnimatedHelper.generateNewNodeTag();
if (this.__nativeTag == null) {
this.__nativeTag = nativeTag;
NativeAnimatedHelper.API.createAnimatedNode(
nativeTag,
this.__getNativeConfig(),
);
this.__shouldUpdateListenersForNewNativeTag = true;
}
return nativeTag;
}
2.5 原生层动画节点管理
在原生层,
RCTNativeAnimatedNodesManager
类负责创建和管理动画节点。以下是其主要方法的详细说明:
-
createAnimatedNode方法 :根据传入的配置信息创建动画节点。它使用工厂模式,根据节点类型创建相应的节点类实例,并将其存储在_animationNodes字典中。
- (void)createAnimatedNode:(nonnull NSNumber *)tag
config:(NSDictionary<NSString *, id> *) config
{
static NSDictionary *map;
static dispatch_once_t mapToken;
dispatch_once(&mapToken, ^{
map = @{@"style" : [RCTStyleAnimatedNode class],
@"value" : [RCTValueAnimatedNode class],
@"props" : [RCTPropsAnimatedNode class],
@"interpolation" : [RCTInterpolationAnimatedNode class],
@"addition" : [RCTAdditionAnimatedNode class],
@"diffclamp": [RCTDiffClampAnimatedNode class],
@"division" : [RCTDivisionAnimatedNode class],
@"multiplication" : [RCTMultiplicationAnimatedNode class],
@"modulus" : [RCTModuloAnimatedNode class],
@"subtraction" : [RCTSubtractionAnimatedNode class],
@"transform" : [RCTTransformAnimatedNode class],
@"tracking" : [RCTTrackingAnimatedNode class]};
});
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
Class nodeClass = map[nodeType];
if (!nodeClass) {
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
return;
}
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
node.manager = self;
_animationNodes[tag] = node;
[node setNeedsUpdate];
}
-
connectAnimatedNodes方法 :建立动画节点之间的连接关系。它从_animationNodes字典中获取父节点和子节点,并调用父节点的addChild方法将子节点添加到父节点的子节点列表中。
- (void)connectAnimatedNodes:(nonnull NSNumber *)parentTag
childTag:(nonnull NSNumber *)childTag
{
RCTAssertParam(parentTag);
RCTAssertParam(childTag);
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
RCTAnimatedNode *childNode = _animationNodes[childTag];
RCTAssertParam(parentNode);
RCTAssertParam(childNode);
[parentNode addChild:childNode];
}
2.6 动画事件传递
动画事件的传递是动画机制的核心部分。当一个原生事件发生时,它会通过 ANG 从事件源传递到事件接收器。在这个过程中,动画节点会根据预设的规则进行值的计算和插值,从而实现动画效果。
| 步骤 | 描述 |
|---|---|
| 1 | 原生事件触发,例如用户的手势操作。 |
| 2 | 事件通过 ANG 中的动画节点进行传递。每个动画节点会根据自身的配置对事件值进行处理,如计算、插值等。 |
| 3 | 处理后的事件值传递到事件接收器,更新组件的属性,从而实现动画效果。 |
3. 总结与应用建议
通过对 React Native 原生调用和动画机制的深入分析,我们可以总结出以下要点和应用建议:
3.1 原生调用方面
-
理解核心方法
:掌握
genModule、genMethod、enqueueNativeCall和callNativeSyncHook等核心方法的作用和实现原理,有助于优化 JavaScript 与原生代码的交互。 - 优化性能 :合理使用异步和同步调用,避免不必要的阻塞。对于频繁的异步调用,可以利用队列机制进行优化,减少通信开销。
- 注意双向通信 :在进行双向通信时,确保原生实例和函数正确注入全局对象,避免出现调用错误。
3.2 动画机制方面
- 构建 ANG :深入理解动画节点图(ANG)的构建过程,包括 JavaScript 层和原生层的实现。合理设计 ANG 的结构,确保动画节点之间的连接关系正确。
- 利用原生层优势 :充分利用原生层的高性能,通过直接的原生到原生通信实现动画,减少 JavaScript 线程的干预,提高动画的流畅度。
- 灵活应用 :根据不同的动画需求,灵活调整 ANG 的结构和动画节点的配置,实现多样化的动画效果。
在实际开发中,我们可以根据这些总结和建议,结合具体的项目需求,对 React Native 应用进行优化和扩展,提高应用的性能和用户体验。例如,在开发复杂的交互界面时,可以利用动画机制实现流畅的过渡效果;在处理大量数据时,合理使用原生调用优化数据传输和处理效率。
通过不断地学习和实践,我们可以更好地掌握 React Native 的底层机制,开发出更加高效、稳定和具有吸引力的应用程序。
超级会员免费看

被折叠的 条评论
为什么被折叠?



