19、React Native 深入解析:原生调用与动画机制

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 &params)
{
  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 的底层机制,开发出更加高效、稳定和具有吸引力的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值