Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reanimated接入问题 #10

Open
ljunb opened this issue Jun 9, 2022 · 3 comments
Open

Reanimated接入问题 #10

ljunb opened this issue Jun 9, 2022 · 3 comments

Comments

@ljunb
Copy link
Owner

ljunb commented Jun 9, 2022

背景

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

接入

babel-preset缺失

首次接入时,两者版本分别是 Reanimated@2.8.0GestureHandler@2.4.0,基本属于最新稳定版本。参照官方文档的 安装步骤,在项目根目录添加了 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 后,验证通过。

参考资料

@NJHu
Copy link

NJHu commented Jul 18, 2022

🐂🍺

@dubiao
Copy link

dubiao commented Jul 21, 2022

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

@ljunb
Copy link
Owner Author

ljunb commented Jul 26, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants