rn-relates icon indicating copy to clipboard operation
rn-relates copied to clipboard

Reanimated接入问题

Open ljunb opened this issue 3 years ago • 3 comments
trafficstars

背景

最近准备在 App 中接入 ReanimatedGestureHandler,之前其实尝试接过,不过有未知报错,后续因为有业务跟进,便一再搁置。前段时间领导提到动效库的必要性,考虑到这两个库的高性能特性,因此再次尝试接入。

接入

babel-preset缺失

首次接入时,两者版本分别是 [email protected][email protected],基本属于最新稳定版本。参照官方文档的 安装步骤,在项目根目录添加了 babel.config.js 配置文件,并补充相应配置:

module.exports = {
  plugins: ['react-native-reanimated/plugin']
};

通过打进 XRN 的方式在工程中引用,并新建相应的测试页面,发现启动时报错:

error: SyntaxError: /XPMotors_ReactNative/node_modules/react-native/index.js: Unexpected token, expected "{" (13:7)

想到可能是因为新增 babel 的配置文件导致语法解析问题,因此上 RN 官网找到 0.62.x 版本对应的配置内容,补充后的内容:

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin']
};

ReanimatedModule缺失

清除缓存重新运行 yarn start --reset-cache,发现依然报错:

TypeError: undefined is not an object(evaluating 'this.InnerNativeModule.installCoreFunctions')

单纯报错信息上看,InnerNativeModule 为 undefined 了,从红屏的源码调用栈来看,最终找到 NativeReanimated.ts :

export class NativeReanimated {
  native: boolean;
  private InnerNativeModule: any;

  constructor(native = true) {
    // 1 检测模块代理是否存在,不存在则手动进行初始化
    if (global.__reanimatedModuleProxy === undefined) {
      const { ReanimatedModule } = NativeModules;
      ReanimatedModule?.installTurboModule();
    }
    // 2 赋值 InnerNativeModule
    this.InnerNativeModule = global.__reanimatedModuleProxy;
    this.native = native;
  }

  installCoreFunctions(valueSetter: <T>(value: T) => void): void {
    // 出错位置,this.InnerNativeModule 为 undefined
    return this.InnerNativeModule.installCoreFunctions(valueSetter);
  }
  ...
}

假设 global.__reanimatedModuleProxy 为空,那么通过 ReanimatedModule?.installTurboModule() 的方式手动赋值,继续搜索 installTurboModule 方法:

RCT_EXPORT_METHOD(installTurboModule)
{
  // TODO: Move initialization from UIResponder+Reanimated to here
}

是个空实现!继续找到 UIResponder+Reanimated.mm:

#ifndef DONT_AUTOINSTALL_REANIMATED

@implementation UIResponder (Reanimated)
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
  const auto installer = reanimated::REAJSIExecutorRuntimeInstaller(bridge, NULL);

#if RNVERSION >= 64
  // installs globals such as console, nativePerformanceNow, etc.
  return std::make_unique<ExecutorFactory>(RCTJSIExecutorRuntimeInstaller(installer));
#else
  return std::make_unique<ExecutorFactory>(installer);
#endif
}

@end

#endif

到这里发现有一个宏标记 DONT_AUTOINSTALL_REANIMATED,从命名上基本就能知道他是干嘛的了,所以100%可以确定 Reanimate 具备自动初始化的能力,那么初始化方法 jsExecutorFactoryForBridge 到底是怎么调用的?

实际上 jsExecutorFactoryForBridge 来自 RN 官方的 RCTCxxBridgeDelegate 协议,查看官方解释:

@protocol RCTCxxBridgeDelegate <RCTBridgeDelegate>

/**
 * In the RCTCxxBridge, if this method is implemented, return a
 * ExecutorFactory instance which can be used to create the executor.
 * If not implemented, or returns an empty pointer, JSIExecutorFactory
 * will be used with a JSCRuntime.
 */
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge;

@end

因此可以知道,Reanimated 团队通过实现该协议,实现了自己的 JSI 运行时实例,即是官方提到的 Separate JavaScript VM。找到 RN 官方调用位置:

// RCTCxxBridge.mm
- (void)start
{
    ...
    // Prepare executor factory (shared_ptr for copy into block)
    std::shared_ptr<JSExecutorFactory> executorFactory;
    if (!self.executorClass) {
      if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
        id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
        executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self];
      }
      ...
    }
}

所以 Reanimated 团队用了比较 hack 的方式,在 RN 初始化 Bridge 实例的过程中,来达到自动初始化 ReanimatedModule 的目的。

XRNManager背锅

通过以上源码,基本梳理清楚了 Reanimated 的整个初始化过程。但是不禁有疑问,为何会导致自动初始化失败?到底是什么环节导致问题?实际问题就出现在 UIResponder+ReanimatedRCTCxxBridge#delegate 上:

  • 首先一般的 RN 工程中,RCTBridge 都是在 AppDelegate 中进行初始化,此时 AppDelegate 作为 RCTBridge 的 delegate;同时 AppDelegate 是 UIResponder 的子类
  • 由于 UIResponder+Reanimated 分类实现了对应的协议方法,实际上已经在运行时中为 UIResponder 的 Metaclass 添加了 jsExecutorFactoryForBridge 方法
  • RN 初始化的时候,[cxxDelegate jsExecutorFactoryForBridge:self] 实际就是 [aAppDelegate jsExecutorFactoryForBridge:self]

目前 XRN 提供了适配层,整个 RN 运行环境的初始化工作都由 XRNManager 完成,而 XRNManager 是 NSObject 的子类,并非继承自 UIResponder,因此 [self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)] 返回结果其实是 NO,也就无法完成后续的初始化逻辑了。

// XRNManager.h
@interface XRNManager : NSObject<RCTBridgeDelegate>
@end

解决方案

修改 XRNManager 继承自 UIResponder 即可。重新打包 XRN 后,验证通过。

参考资料

ljunb avatar Jun 09 '22 10:06 ljunb

🐂🍺

NJHu avatar Jul 18 '22 07:07 NJHu

你要不要给原库提个PR?目前2.9.1还有这个问题。我是把 UIResponder+Reanimated 改继承自 NSObject 修成功了。但原理上并不明白与你说的 XRNManager : NSObject 有什么区别 来自:https://github.com/software-mansion/react-native-reanimated/issues/2791#issuecomment-1004779673

dubiao avatar Jul 21 '22 10:07 dubiao

@dubiao 不知官方设计 UIResponder+Reanimated 的初衷是不是因为 AppDelegate 是 UIResponder 的子类,正常来说改用 NSObject 分类的话就一劳永逸。XRNManager 也是 NSObject 的子类,肯定是无法查找到 UIResponder+Reanimated 里面的方法的

ljunb avatar Jul 26 '22 10:07 ljunb