阅读视图

发现新文章,点击刷新页面。

每日一题-构造最小位运算数组 I🟢

给你一个长度为 n 的质数数组 nums 。你的任务是返回一个长度为 n 的数组 ans ,对于每个下标 i ,以下 条件 均成立:

  • ans[i] OR (ans[i] + 1) == nums[i]

除此以外,你需要 最小化 结果数组里每一个 ans[i] 。

如果没法找到符合 条件 的 ans[i] ,那么 ans[i] = -1 。

质数 指的是一个大于 1 的自然数,且它只有 1 和自己两个因数。

 

示例 1:

输入:nums = [2,3,5,7]

输出:[-1,1,4,3]

解释:

  • 对于 i = 0 ,不存在 ans[0] 满足 ans[0] OR (ans[0] + 1) = 2 ,所以 ans[0] = -1 。
  • 对于 i = 1 ,满足 ans[1] OR (ans[1] + 1) = 3 的最小 ans[1] 为 1 ,因为 1 OR (1 + 1) = 3 。
  • 对于 i = 2 ,满足 ans[2] OR (ans[2] + 1) = 5 的最小 ans[2] 为 4 ,因为 4 OR (4 + 1) = 5 。
  • 对于 i = 3 ,满足 ans[3] OR (ans[3] + 1) = 7 的最小 ans[3] 为 3 ,因为 3 OR (3 + 1) = 7 。

示例 2:

输入:nums = [11,13,31]

输出:[9,12,15]

解释:

  • 对于 i = 0 ,满足 ans[0] OR (ans[0] + 1) = 11 的最小 ans[0] 为 9 ,因为 9 OR (9 + 1) = 11 。
  • 对于 i = 1 ,满足 ans[1] OR (ans[1] + 1) = 13 的最小 ans[1] 为 12 ,因为 12 OR (12 + 1) = 13 。
  • 对于 i = 2 ,满足 ans[2] OR (ans[2] + 1) = 31 的最小 ans[2] 为 15 ,因为 15 OR (15 + 1) = 31 。

 

提示:

  • 1 <= nums.length <= 100
  • 2 <= nums[i] <= 1000
  • nums[i] 是一个质数。

出海也有 “斩杀线”:哪吒汽车陷20亿泰债困局

出品 | 虎嗅汽车组

作者 | 魏微

头图 | 视觉中国


当“斩杀线”从游戏术语演变为描述经济脆弱性的热词,它精准捕捉了一种临界状态——个体或组织因资源、信用耗尽,陷入系统性崩溃的风险。


在汽车出海的复杂战场上,这样一击致命的“斩杀”机制同样存在。


近日,泰国财政部宣布计划起诉哪吒汽车泰国子公司“NETA Auto Thailand”,追讨其自2022年起累计获得的20亿泰铢(约合4亿元人民币)电动汽车购车补贴,对这家正处在破产重整关键阶段的中国新势力而言,这无异雪上加霜。


值得关注的是,哪吒汽车在泰国的遭遇,并非源于惨烈的市场肉搏,而是因母公司“残血”失能,触发了东道国产业政策的高压红线。


本期车圈脉动VOL.19,我们聚焦哪吒汽车的20亿泰债困局,并试图解析该事件对其他当地中国车企的影响。


从市场黑马到被政府追债


哪吒汽车在泰国市场一度展现出黑马势头。


2022年进入泰国市场后,哪吒与当地企业BGAC合作生产,初期投放NETA X(哪吒X)和NETA AYA(哪吒V)两款车型,精准契合当地消费者需求。


2024年3月哪吒汽车泰国工厂开启试生产,图片源自哪吒汽车原CEO张勇社交媒体


据当地媒体数据,哪吒汽车2024年在泰国纯电动新能源汽车市场的占有率一度冲至国际品牌第二,全年占有率达到11.4%。


哪吒汽车的品质在当地收获不少好评,甚至成为许多当地消费者的首款电动车。据虎嗅汽车从熟悉哪吒汽车人士了解,在业务停摆前,哪吒汽车在泰国快速积累了超过2万名用户。


繁荣的表象下,危机已潜伏。


根据泰国EV 3.0政策要求,哪吒汽车2025年本地生产目标为19000辆,然而受母公司经营危机拖累,截至2025年6月,其实际产量仅约4000辆,目标达成率不足25%,远未满足本地化生产承诺。


在2025年6月,哪吒汽车泰国子公司就因未能履行在泰国的生产承诺,失去了获得泰国政府EV3.0补贴的资格。


而这背后,正是国内母公司合众新能源正在“失血”。2025年合众新能源深陷财务危机,国内员工围堵上海总部向创始人方运舟讨薪的场景,成为公司资金链断裂的公开写照,随后公司正式进入破产重整程序。


受此影响,哪吒在泰国的经销商渠道急剧萎缩。据外媒报道,2025年7月时,哪吒汽车的泰国经销商数量已从60多家锐减到约20家。售后保障能力大幅下滑。


“(这件事)主要对部分哪吒车主有影响,涉及到他们车辆的保养、维修和二手车残值,其他的影响不大。”知情人士对虎嗅汽车表示。


另据当地媒体报道,哪吒泰国的中方管理层曾尝试以12亿—20亿泰铢的估值,将公司整体出售给泰资企业,但潜在投资者因品牌风险过高、持续经营能力存疑,最终放弃交易。


因此,在多次要求哪吒退还补贴未果后,泰国财政部于2026年1月宣布将启动诉讼,追讨全部20亿泰铢补贴,并计划冻结、扣押哪吒在泰资产(包括银行账户),通过法院强制执行退款。此次诉讼对象直接指向哪吒汽车在泰法人实体,不过泰国财政部尚未明确提起诉讼的具体时间表。


在相关消息爆出后,虎嗅汽车尝试联系哪吒汽车原泰国公司相关负责人,但截至发稿未获回应。


目前,哪吒汽车母公司的重整工作正进入关键阶段。



合众新能源重整已进入关键阶段,图片源自合众新能源管理人公众号


2025年9月,合众新能源管理人对外披露,破产重整程序唯一合格意向投资人完成报名,该主体已提交完整材料并缴纳5000万元保证金,进入方案谈判阶段。尽管有多家媒体将该意向投资方指向山子高科。然而,山子高科官方后续多次表示“还未最终确定”、“需以管理人公布为准”。截至目前,合众新能源管理人,以及受理该案的嘉兴中院也尚未发布最终裁定。


若重整顺利落地,或为哪吒泰国业务带来转机。“哪吒在泰国依旧有不错的用户基础,很多用户都希望哪吒能恢复正常。如果新的投资人能继续运营下去的话,一定会好起来的。”上述知情人士表示。


哪吒汽车让泰国紧急补bug


为推动电动汽车产业发展、实现2030年新能源汽车占比30%的目标,泰国政府自2022年起实施“EV 3.0”补贴计划,核心逻辑是明确的“以市场换产业”——通过政策红利吸引海外车企落地,推动本地制造与供应链升级。


该计划的补贴政策有明确门槛:针对售价不超过200万泰铢的纯电动乘用车,单车最高补贴15万泰铢(约合3万元人民币),同时配套免征进口关税等优惠。


但享受补贴的前提是严苛的生产配比要求:海外车企自2024年起需遵循1:1的进口与本地生产比例;若2025年才启动本地生产,该比例则上调至1:1.5。


政策红利下,中国车企纷纷布局泰国市场。截至2025年6月,比亚迪、广汽埃安、长安汽车、奇瑞汽车、上汽名爵、长城汽车等7家中国车企已在泰建厂,累计产能规划超60万辆,成为泰国新能源市场的核心力量。


也正是在2025年6月,哪吒汽车泰国子公司在当地的补贴资格被叫停后,泰国财政部紧急要求国家电动汽车政策委员会修改相关规定。财政部提议新增规则,要求电动汽车制造商每两个月提交一份补偿生产计划,若企业未能遵守,政府有权暂停补贴发放。


这也意味着,泰国电动汽车市场的政策也正在向更精细化管理的方向调整。对此,另有业内人士对虎嗅汽车表示:“哪吒汽车的事是个例,对其他车企影响不大,只要兑现投资承诺,‘填上坑’就行。”


咨询公司安永-博智隆的报告显示,印度尼西亚、马来西亚、泰国、新加坡等东南亚地区的电动汽车市场将迎来爆发式增长,预计到2035年,这些市场的电动汽车销售总额将从2021年的20亿美元增至800亿至1000亿美元,潜力巨大。而中国产品在泰国电动汽车市场的占比已高达75%,成为当地市场的绝对主力。


中汽协数据也印证了中国汽车出海的上升势头:2025年中国汽车出口量达到709.8万辆,同比增长21.1%;2026年预计将进一步增至740万辆。


下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

3314. 构造最小位运算数组 I

解法一

思路和算法

数组 $\textit{nums}$ 的长度是 $n$。对于 $0 \le i < n$ 的每个下标 $i$,满足 $(\textit{ans}[i] ~|~ (\textit{ans}[i] + 1)) = \textit{nums}[i]$。由于 $\textit{nums}[i]$ 是质数,即 $\textit{nums}[i]$ 至少等于 $2$,因此 $\textit{ans}[i]$ 一定是正整数。

最直观的计算 $\textit{ans}[i]$ 的方法是从小到大遍历每个正整数 $x$,寻找满足 $(x ~|~ (x + 1)) = \textit{nums}[i]$ 的最小正整数 $x$ 填入 $\textit{ans}[i]$,如果不存在符合要求的正整数 $x$ 则 $\textit{ans}[i] = -1$。

根据按位或运算的性质,必有 $(\textit{ans}[i] ~|~ (\textit{ans}[i] + 1)) \ge \textit{ans}[i]$,因此当 $\textit{ans}[i] > \textit{nums}[i]$ 时必有 $(\textit{ans}[i] ~|~ (\textit{ans}[i] + 1)) > \textit{nums}[i]$,计算 $\textit{ans}[i]$ 时只需要遍历不超过 $\textit{nums}[i]$ 的值。

代码

###Java

class Solution {
    public int[] minBitwiseArray(List<Integer> nums) {
        int n = nums.size();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = find(nums.get(i));
        }
        return ans;
    }

    public int find(int num) {
        for (int i = 1; i <= num; i++) {
            if ((i | (i + 1)) == num) {
                return i;
            }
        }
        return -1;
    }
}

###C#

public class Solution {
    public int[] MinBitwiseArray(IList<int> nums) {
        int n = nums.Count;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = Find(nums[i]);
        }
        return ans;
    }

    public int Find(int num) {
        for (int i = 1; i <= num; i++) {
            if ((i | (i + 1)) == num) {
                return i;
            }
        }
        return -1;
    }
}

复杂度分析

  • 时间复杂度:$O(nm)$,其中 $n$ 是数组 $\textit{nums}$ 的长度,$m$ 是数组 $\textit{nums}$ 中的最大元素。答案数组中的每个元素的计算时间都是 $O(m)$。

  • 空间复杂度:$O(1)$。注意返回值不计入空间复杂度。

解法二

思路和算法

当存在正整数 $x$ 满足 $(x ~|~ (x + 1)) = \textit{nums}[i]$ 时,由于 $x$ 和 $x + 1$ 当中一定有一个奇数,因此 $\textit{nums}[i]$ 一定是奇数。如果 $\textit{nums}[i]$ 是偶数,则不存在符合要求的 $x$,对应 $\textit{ans}[i] = -1$。当 $\textit{nums}[i]$ 是奇数时,可以取 $x = \textit{nums}[i] - 1$,则满足 $(x ~|~ (x + 1)) = \textit{nums}[i]$,因此如果 $\textit{nums}[i]$ 是奇数,则一定存在符合要求的 $x$。

由于 $\textit{nums}[i]$ 是质数,唯一的偶质数是 $2$,因此当 $\textit{nums}[i] = 2$ 时 $\textit{ans}[i] = -1$。当 $\textit{nums}[i] > 2$ 时 $\textit{ans}[i] > 0$,需要计算 $\textit{ans}[i]$。

考虑正整数 $x$ 和 $x + 1$ 的二进制表示的如下两种情况。

  • 当 $x$ 是偶数时,$x$ 的二进制表示的最低位是 $0$,$x + 1$ 的二进制表示等于 $x$ 的二进制表示的最低位从 $0$ 变成 $1$。

  • 当 $x$ 是奇数时,$x$ 的二进制表示最低位有 $k$ 个 $1$ 且从低到高第 $k$ 位等于 $0$(位数从 $0$ 开始计数),其中 $k \ge 1$,$x + 1$ 的二进制表示等于 $x$ 的二进制表示的最低 $k$ 位从 $1$ 变成 $0$ 且从低到高第 $k$ 位从 $0$ 变成 $1$。

因此,$x ~|~ (x + 1)$ 的二进制表示的结果等于 $x$ 的二进制表示的最右边的 $0$ 变成 $1$。

为了使 $\textit{ans}[i]$ 的值最小,需要找到 $\textit{nums}[i]$ 的二进制表示中的可以变成 $0$ 的最左边的 $1$,满足将 $0$ 变成 $1$ 之后在其右侧没有任何 $0$。计算方法如下。

  1. 计算 $\textit{nums}[i]$ 的二进制表示的最低连续 $1$ 的最大位数 $k$,满足二进制表示最低位有 $k$ 个 $1$ 且从低到高第 $k$ 位等于 $0$(位数从 $0$ 开始计数)。

  2. 计算 $\textit{ans}[i] = \textit{nums}[i] - 2^{k - 1}$。

根据位运算的性质,计算方法如下:计算 $\textit{lowbit} = ((\textit{nums}[i] + 1) ~&~ (-\textit{nums}[i] - 1)) >> 1$,则 $\textit{lowbit}$ 等于上述 $2^{k - 1}$,$\textit{ans}[i] = \textit{nums}[i] - \textit{lowbit}$。

代码

###Java

class Solution {
    public int[] minBitwiseArray(List<Integer> nums) {
        int n = nums.size();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = find(nums.get(i));
        }
        return ans;
    }

    public int find(int num) {
        if (num % 2 == 0) {
            return -1;
        } else {
            int lowbit = ((num + 1) & (-num - 1)) >> 1;
            return num - lowbit;
        }
    }
}

###C#

public class Solution {
    public int[] MinBitwiseArray(IList<int> nums) {
        int n = nums.Count;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = Find(nums[i]);
        }
        return ans;
    }

    public int Find(int num) {
        if (num % 2 == 0) {
            return -1;
        } else {
            int lowbit = ((num + 1) & (-num - 1)) >> 1;
            return num - lowbit;
        }
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。答案数组中的每个元素的计算时间都是 $O(1)$。

  • 空间复杂度:$O(1)$。注意返回值不计入空间复杂度。

数学

解法:数学

考虑 x or (x + 1) 是一个怎样的数。可以发现,x + 1 的二进制表示和 x 相比,其实就是把 x 从最低位开始的连续 $1$ 都变成 $0$,然后把下一位变成 $1$,更高位都不变。

因此我们找出 nums[i] 从最低位开始的连续 $1$,把连续 $1$ 的最后一位变成 $0$,就是答案。复杂度 $\mathcal{O}(n\log X)$,其中 $X = 10^9$ 是取值范围。

参考代码(c++)

###cpp

class Solution {
public:
    vector<int> minBitwiseArray(vector<int>& nums) {
        vector<int> ans;
        for (int x : nums) {
            if (x == 2) ans.push_back(-1);
            else {
                // 求从最低位开始的连续 1
                int p;
                for (p = 0; x >> p & 1; p++);
                // 把连续 1 的最后一位变成 0
                ans.push_back(x ^ (1 << (p - 1)));
            }
        }
        return ans;
    }
};

React Native之Android端Fabric 架构源码分析(下)

React Native之Android端Fabric 架构源码分析(下)

由于单章字数限制,这里将本文拆成上下两篇,上篇《React Native之Android端Fabric 架构源码分析(上)》

Fabric 组件

声明组件

要开发一个Fabric 组件,首先需要在TS代码中声明规范。我们使用npx create-react-native-library@latest创建一个Fabric组件库,然后参考根据官方文档的示例添加如下内容:

import type {
  CodegenTypes,
  HostComponent,
  ViewProps,
} from 'react-native';
import {codegenNativeComponent} from 'react-native';

type WebViewScriptLoadedEvent = {
  result: 'success' | 'error';
};

export interface NativeProps extends ViewProps {
  sourceURL?: string;
  onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}

export default codegenNativeComponent<NativeProps>(
  'CustomWebView',
) as HostComponent<NativeProps>;

这里主要有三部分:

  • WebViewScriptLoadedEvent 是一种支持的数据类型,用于存储事件需要从原生代码传递到 JavaScript 的数据。

  • NativeProps 定义了可以设置的组件属性。

  • codegenNativeComponent 允许我们为自定义组件生成代码,并为组件定义一个与原生实现相匹配的名称。

其中,对codegenNativeComponent函数进行分析,源码react-native/packages/react-native/Libraries/Utilities/codegenNativeComponent.js:

// 如果这个函数运行,说明视图配置没有在构建时通过 `GenerateViewConfigJs.js` 生成。
// 因此我们需要使用 `requireNativeComponent` 从视图管理器获取视图配置。
// `requireNativeComponent` 在 Bridgeless 模式下不可用。
// 例如:如果 `codegenNativeComponent` 不是从以 NativeComponent.js 结尾的文件中调用,
// 这个函数就会在运行时执行。
function codegenNativeComponent<Props: {...}>(
  componentName: string,
  options?: NativeComponentOptions,
): NativeComponentType<Props> {
  if (global.RN$Bridgeless === true && __DEV__) {
    console.warn(
      `Codegen didn't run for ${componentName}. This will be an error in the future. Make sure you are using @react-native/babel-preset when building your JavaScript code.`,
    );
  }
  // 确定基础组件名称
  let componentNameInUse =
    options && options.paperComponentName != null
      ? options.paperComponentName
      : componentName;

  // 省略部分代码......

  return (requireNativeComponent<Props>(
    // $FlowFixMe[incompatible-type]
    componentNameInUse,
  ): HostComponent<Props>);
}

根据注释可知,在新架构中,这个函数几乎没有实际意义,因为新架构是通过GenerateViewConfigJs.js 来生成实际的视图配置。

我们通过搜索codegenNativeComponent字符串,很容易定位到react-native/packages/babel-plugin-codegen/index.js文件,这里babel-plugin-codegen包就是检测codegenNativeComponent调用,解析TypeScript/Flow类型定义的工具包:

module.exports = function ({parse, types: t}) {
  return {
    pre(state) {
      this.code = state.code;
      this.filename = state.opts.filename;
      this.defaultExport = null;
      this.commandsExport = null;
      this.codeInserted = false;
    },

    // 省略部分代码.....
      ExportDefaultDeclaration(path, state) {
        if (isCodegenDeclaration(path.node.declaration)) {
          this.defaultExport = path;
        }
      },

      // 程序退出时进行替换
      Program: {
        exit(path) {
          if (this.defaultExport) {
            // 1. 生成ViewConfig代码
            const viewConfig = generateViewConfig(this.filename, this.code);
            // 2. 解析为AST
            const ast = parse(viewConfig, {
              babelrc: false,
              browserslistConfigFile: false,
              configFile: false,
            });

            // 3. 完全替换原始导出
            this.defaultExport.replaceWithMultiple(ast.program.body);

            if (this.commandsExport != null) {
              this.commandsExport.remove();
            }

            this.codeInserted = true;
          }
        },
      },
    },
  };
};


function generateViewConfig(filename /*: string */, code /*: string */) {
  // 解析TypeScript/Flow类型
  const schema = parseFile(filename, code);
  // 提取组件信息
  const libraryName = basename(filename).replace(
    /NativeComponent\.(js|ts)$/,
    '',
  );
  // 调用Codegen生成器
  return RNCodegen.generateViewConfig({
    libraryName,
    schema,
  });
}

function isCodegenDeclaration(declaration) {
  if (!declaration) {
    return false;
  }

  if (
    declaration.left &&
    declaration.left.left &&
    declaration.left.left.name === 'codegenNativeComponent'
  ) {
    return true;
  } else if (
    declaration.callee &&
    declaration.callee.name &&
    declaration.callee.name === 'codegenNativeComponent'
  ) {
    return true;
  } 
  // 省略......
  return false;
}

继续跟踪RNCodegen.generateViewConfig的实现,源码react-native/packages/react-native-codegen/src/generators/RNCodegen.js

const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');


module.exports = {
  allGenerators: ALL_GENERATORS,
  // 省略部分代码......
  generateViewConfig({
    libraryName,
    schema,
  }: Pick<LibraryOptions, 'libraryName' | 'schema'>): string {
    schemaValidator.validate(schema);

    const result = generateViewConfigJs
      .generate(libraryName, schema)
      .values()
      .next();

    if (typeof result.value !== 'string') {
      throw new Error(`Failed to generate view config for ${libraryName}`);
    }

    return result.value;
  },
};

最终是调用的GenerateViewConfigJs.js中的generate,源码react-native/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js

module.exports = {
  generate(libraryName: string, schema: SchemaType): FilesOutput {
    try {
      const fileName = `${libraryName}NativeViewConfig.js`;
      const imports: Set<string> = new Set();

      const moduleResults = Object.keys(schema.modules)
        .map(moduleName => {
          const module = schema.modules[moduleName];
          if (module.type !== 'Component') {
            return;
          }

          const {components} = module;

          return Object.keys(components)
            .map((componentName: string) => {
              const component = components[componentName];

              if (component.paperComponentNameDeprecated) {
                imports.add(UIMANAGER_IMPORT);
              }

              const replacedTemplate = ComponentTemplate({
                componentName,
                paperComponentName: component.paperComponentName,
                paperComponentNameDeprecated:
                  component.paperComponentNameDeprecated,
              });
      // 省略部分代码...... 

      return new Map([[fileName, replacedTemplate]]);
    } catch (error) {
      console.error(`\nError parsing schema for ${libraryName}\n`);
      console.error(JSON.stringify(schema));
      throw error;
    }
  },
};

这里我们根据ComponentTemplate中的模板,大概就能还原出生成的代码是什么样子:

// 输入:
// libraryName: "MyComponent"
// schema: { 解析后的TypeScript/Flow类型信息 }

// 输出:完整的JavaScript代码字符串


'use strict';

const {UIManager} = require("react-native")

let nativeComponentName = 'MyComponent';

export const __INTERNAL_VIEW_CONFIG = {
  uiViewClassName: 'MyComponent',
  bubblingEventTypes: {},
  directEventTypes: {},
  validAttributes: {
    opacity: true,
    backgroundColor: { process: require('react-native/Libraries/StyleSheet/processColor').default },
    // ... 其他属性
  }
};

export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);

这里最关键的就是最后一行,它将开发者编写的Fabric 组件规范进行了代码替换:

// 开发者编写
const CustomView = codegenNativeComponent<Props>('CustomView');

// Babel插件替换后
const CustomView = NativeComponentRegistry.get('CustomView', () => __INTERNAL_VIEW_CONFIG);

需要注意,babel-plugin-codegen工具并不像Codegen工具,会生成实际的代码文件。它是对AST语法树进行的动态修改和替换,也就是说它修改的是内存中的语法树,并不会写文件。

现在来重点追踪NativeComponentRegistry.get的实现,首先是导出位置react-native/packages/react-native/index.js

  get NativeComponentRegistry() {
    return require('./Libraries/NativeComponent/NativeComponentRegistry');
  },

定位到方法实现react-native/packages/react-native/Libraries/NativeComponent/NativeComponentRegistry.js

/**
 * 获取一个可以被 React Native 渲染的 `NativeComponent`。
 *
 * 提供的 `viewConfigProvider` 可能会被调用和使用,也可能不会,
 * 这取决于 `setRuntimeConfigProvider` 是如何配置的。
 */
export function get<Config: {...}>(
  name: string,
  viewConfigProvider: () => PartialViewConfig,
): HostComponent<Config> {
  // 注册ViewConfig到全局注册表
  ReactNativeViewConfigRegistry.register(name, () => {
    // 这里的回调函数会在React需要组件配置时被调用
    const {native, verify} = getRuntimeConfig?.(name) ?? {
      native: !global.RN$Bridgeless,   // 关键:新架构检测
      verify: false,
    };

    let viewConfig: ViewConfig;
    if (native) {
      // 旧架构:原生ViewManager
      viewConfig =
        getNativeComponentAttributes(name) ??
        createViewConfig(viewConfigProvider());
    } else {
      // 新架构:优先静态ViewConfig
      viewConfig =
        createViewConfig(viewConfigProvider()) ??
        getNativeComponentAttributes(name);
    }
    // 省略部分代码......
    return viewConfig;
  });

  // $FlowFixMe[incompatible-type] `NativeComponent` 实际上是字符串!
  return name;
}

继续跟踪ReactNativeViewConfigRegistry.register实现。源码react-native/packages/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js

const viewConfigCallbacks = new Map<string, ?() => ViewConfig>();


export function register(name: string, callback: () => ViewConfig): string {
  // 省略部分代码......
  viewConfigCallbacks.set(name, callback);
  return name;
}

这里基本上就是将返回ViewConfig的闭包给存了起来。

查找组件

JS层

在前面启动渲染一节我们知道了启动渲染的最终调用是JS层的AppRegistry.runApplication方法。沿着这条线,我们来分析一下JS层的组件加载与处理流程。源码react-native/packages/react-native/Libraries/ReactNative/AppRegistry.js

import * as AppRegistry from './AppRegistryImpl';

// 省略部分代码......
global.RN$AppRegistry = AppRegistry;

registerCallableModule('AppRegistry', AppRegistry);

export {AppRegistry};

继续跟踪源码react-native/packages/react-native/Libraries/ReactNative/AppRegistryImpl.js

const runnables: Runnables = {};


/**
 * Loads the JavaScript bundle and runs the app.
 *
 * See https://reactnative.dev/docs/appregistry#runapplication
 */
export function runApplication(
  appKey: string,
  appParameters: AppParameters,
  displayMode?: number,
): void {
  if (appKey !== 'LogBox') {
    const logParams = __DEV__ ? ` with ${JSON.stringify(appParameters)}` : '';
    const msg = `Running "${appKey}"${logParams}`;
    console.log(msg);
  }

  SceneTracker.setActiveScene({name: appKey});
  runnables[appKey](appParameters, coerceDisplayMode(displayMode));
}


/**
 * Registers an app's root component.
 *
 * See https://reactnative.dev/docs/appregistry#registercomponent
 */
export function registerComponent(
  appKey: string,
  componentProvider: ComponentProvider,
  section?: boolean,
): string {
  const scopedPerformanceLogger = createPerformanceLogger();
  runnables[appKey] = (appParameters, displayMode) => {
    const renderApplication = require('./renderApplication').default;
    renderApplication(
      componentProviderInstrumentationHook(
        componentProvider,
        scopedPerformanceLogger,
      ),
      appParameters.initialProps,
      appParameters.rootTag,
      wrapperComponentProvider && wrapperComponentProvider(appParameters),
      rootViewStyleProvider && rootViewStyleProvider(appParameters),
      appParameters.fabric,
      scopedPerformanceLogger,
      appKey === 'LogBox', // is logbox
      appKey,
      displayMode,
    );
  };
  if (section) {
    sections[appKey] = runnables[appKey];
  }
  return appKey;
}

可以看到,runApplication调用的是runnables对象中注册的闭包。而在我们React Native JS层的Bundle包中,首先就需要调用AppRegistry.registerComponent(appName, () => App)完成最根组件的注册。所以runApplication方法中调用的闭包,就是在registerComponent中注册的闭包。

继续跟踪renderApplication方法的实现,源码react-native/packages/react-native/Libraries/ReactNative/renderApplication.js

export default function renderApplication<Props: Object>(
  RootComponent: React.ComponentType<Props>,
  initialProps: Props,
  rootTag: any,
  WrapperComponent?: ?React.ComponentType<any>,
  rootViewStyle?: ?ViewStyleProp,
  fabric?: boolean,
  scopedPerformanceLogger?: IPerformanceLogger,
  isLogBox?: boolean,
  debugName?: string,
  displayMode?: ?DisplayModeType,
  useOffscreen?: boolean,
) {

  const performanceLogger = scopedPerformanceLogger ?? GlobalPerformanceLogger;

  // 构建React元素树
  // 外层:PerformanceLoggerContext.Provider - 提供性能监控上下文
  // 中层:AppContainer - React Native的根容器组件
  // 内层:RootComponent - 用户注册的应用组件(如App.js)
  let renderable: React.MixedElement = (
    <PerformanceLoggerContext.Provider value={performanceLogger}>
      <AppContainer
        rootTag={rootTag}
        fabric={fabric}
        WrapperComponent={WrapperComponent}
        rootViewStyle={rootViewStyle}
        initialProps={initialProps ?? Object.freeze({})}
        internal_excludeLogBox={isLogBox}>
        <RootComponent {...initialProps} rootTag={rootTag} />
      </AppContainer>
    </PerformanceLoggerContext.Provider>
  );

  // 开发模式调试包装
  if (__DEV__ && debugName) {
    const RootComponentWithMeaningfulName = getCachedComponentWithDebugName(
      `${debugName}(RootComponent)`,
    );
    renderable = (
      <RootComponentWithMeaningfulName>
        {renderable}
      </RootComponentWithMeaningfulName>
    );
  }

  //  实验性离屏渲染支持
  if (useOffscreen && displayMode != null) {
    // $FlowFixMe[incompatible-type]
    // $FlowFixMe[prop-missing]
    // $FlowFixMe[missing-export]
    const Activity: ActivityType = React.unstable_Activity;

    renderable = (
      <Activity
        mode={displayMode === DisplayMode.VISIBLE ? 'visible' : 'hidden'}>
        {renderable}
      </Activity>
    );
  }

  // 我们希望在使用 Fabric 时始终启用 concurrentRoot 功能
  const useConcurrentRoot = Boolean(fabric);

  // 省略部分性能日志打印......

  // 进入React渲染系统
  Renderer.renderElement({
    element: renderable,
    rootTag,
    useFabric: Boolean(fabric),
    useConcurrentRoot,
  });
}

继续跟踪Renderer.renderElement方法实现。源码react-native/packages/react-native/Libraries/ReactNative/RendererImplementation.js

export function renderElement({
  element,
  rootTag,
  useFabric,
  useConcurrentRoot,
}: {
  element: React.MixedElement,
  rootTag: number,
  useFabric: boolean,
  useConcurrentRoot: boolean,
}): void {
  if (useFabric) {
    if (cachedFabricRender == null) {
      // 获取实际渲染器
      cachedFabricRender = getFabricRenderer().render;
    }

    cachedFabricRender(element, rootTag, null, useConcurrentRoot, {
      onCaughtError,
      onUncaughtError,
      onRecoverableError,
    });
  } else {
    // 省略旧架构......
  }
}

function getFabricRenderer(): ReactFabricType {
  if (cachedFabricRenderer == null) {
    cachedFabricRenderer = require('../Renderer/shims/ReactFabric').default;
  }
  return cachedFabricRenderer;
}

继续跟踪react-native/packages/react-native/Libraries/Renderer/shims/ReactFabric.js

import {BatchedBridge} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

import type {ReactFabricType} from './ReactNativeTypes';

let ReactFabric: ReactFabricType;

if (__DEV__) {
  ReactFabric = require('../implementations/ReactFabric-dev');
} else {
  ReactFabric = require('../implementations/ReactFabric-prod');
}

global.RN$stopSurface = ReactFabric.stopSurface;

if (global.RN$Bridgeless !== true) {
  BatchedBridge.registerCallableModule('ReactFabric', ReactFabric);
}

export default ReactFabric;

这里根据开发环境选择了不同的渲染器。开发环境的渲染器体积更大,包含了许多调试、性能日志等信息,而生产环境的移除了注释和空白,变量名被压缩代码经过优化,减少包体积。

继续跟踪生产环境的渲染器,因为代码更加简洁,可更好的聚焦于调用的链路和流程。源码react-native/packages/react-native/Libraries/Renderer/implementations/ReactFabric-prod.js

exports.render = function (
  element,
  containerTag,
  callback,
  concurrentRoot,
  options
) {
  var root = roots.get(containerTag);
  if (!root) {
    // 创建新的FiberRootNode
    // 省略部分代码......

    initializeUpdateQueue(concurrentRoot);
    roots.set(containerTag, root);
  }
  // 启动渲染 
  updateContainer(element, root, null, callback);
  // 返回渲染后的公共实例引用
  a: if (((element = root.current), element.child))
    switch (element.child.tag) {
      case 27:
      case 5:
        element = getPublicInstance(element.child.stateNode);
        break a;
      default:
        element = element.child.stateNode;
    }
  else element = null;
  return element;
};

function updateContainer(element, container, parentComponent, callback) {
  parentComponent = container.current;
  // / 获取更新优先级
  var lane = requestUpdateLane(parentComponent);
  null === container.context
    ? (container.context = emptyContextObject)
    : (container.pendingContext = emptyContextObject);

  // 创建更新对象
  container = createUpdate(lane);
  container.payload = { element: element };
  callback = void 0 === callback ? null : callback;
  null !== callback && (container.callback = callback);

  // 将更新加入队列
  element = enqueueUpdate(parentComponent, container, lane);

  // 调度更新
  null !== element &&
    (scheduleUpdateOnFiber(element, parentComponent, lane),
    entangleTransitions(element, parentComponent, lane));
  return lane;
}

以上调用都比较简单,这里的核心是scheduleUpdateOnFiber方法,它负责调度更新。由于代码量较大,我们后面只追求核心逻辑,将省略大部分代码:

function scheduleUpdateOnFiber(root, fiber, lane) {
  // 省略......

  // 标记更新并触发调度
  markRootUpdated$1(root, lane);
  if (/* 不在渲染中 */) {
      ensureRootIsScheduled(root);                    // 确保根节点被调度
      if (/* 同步更新 */) {
        flushSyncWorkAcrossRoots_impl(0, !0);         // 立即执行同步工作
      }
   }
}


function flushSyncWorkAcrossRoots_impl(syncTransitionLanes, onlyLegacy) {
  if (!isFlushingWork && mightHavePendingSyncWork) {
    isFlushingWork = !0;
    do {
      var didPerformSomeWork = !1;
      // 遍历所有根节点执行同步工作
      for (var root = firstScheduledRoot; null !== root; ) {
        if (!onlyLegacy || 0 === root.tag)
          //省略......
          // 常规同步更新 
          performSyncWorkOnRoot(root, JSCompiler_inline_result));

        root = root.next;
      }
    } while (didPerformSomeWork);
    isFlushingWork = !1;
  }
}


function performSyncWorkOnRoot(root, lanes) {
  if (flushPendingEffects()) return null;
  performWorkOnRoot(root, lanes, !0);
}


function performWorkOnRoot(root$jscomp$0, lanes, forceSync) {
   // 选择同步或并发渲染
   var shouldTimeSlice = (!forceSync && /* 条件判断 */);
   var exitStatus = shouldTimeSlice
      ? renderRootConcurrent(root, lanes)            // 并发渲染
      : renderRootSync(root, lanes, !0);             // 同步渲染

   // 省略......
}

继续追踪同步渲染renderRootSync方法实现:

function renderRootSync(root, lanes, shouldYieldForPrerendering) {
   // 省略......

   // 同步工作循环
   workLoopSync();
   // 省略......
}


function workLoopSync() {
  for (; null !== workInProgress; ) performUnitOfWork(workInProgress);
}


function performUnitOfWork(unitOfWork) {
  // 处理单个Fiber节点
  var next = beginWork(unitOfWork.alternate, unitOfWork, entangledRenderLanes);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  null === next ? completeUnitOfWork(unitOfWork) : (workInProgress = next);
}


function completeUnitOfWork(unitOfWork) {
  var completedWork = unitOfWork;
  do {
    // 省略......
    unitOfWork = completedWork.return;
    var next = completeWork(
      completedWork.alternate,
      completedWork,
      entangledRenderLanes
    );
    if (null !== next) {
      workInProgress = next;
      return;
    }
    completedWork = completedWork.sibling;
    if (null !== completedWork) {
      workInProgress = completedWork;
      return;
    }
    workInProgress = completedWork = unitOfWork;
  } while (null !== completedWork);
  0 === workInProgressRootExitStatus && (workInProgressRootExitStatus = 5);
}

最终是调用的completeWork方法完成渲染工作:

function completeWork(current, workInProgress, renderLanes) {
  var newProps = workInProgress.pendingProps;
  switch (workInProgress.tag) {
    case 28:
    case 16:
    case 15:
    case 0:
    case 11:
    case 7:
    case 8:
    case 12:
    case 9:
    case 14:
      return bubbleProperties(workInProgress), null;
    case 1:
      return bubbleProperties(workInProgress), null;
    case 3:
      // 省略......
      // return  
    case 26:
    case 27:
    case 5:
      popHostContext(workInProgress);
      var type = workInProgress.type;
      if (null !== current && null != workInProgress.stateNode)
        // 省略......
      else {
        // 首次挂载
        if (!newProps) {  
          if (null === workInProgress.stateNode)
            throw Error(
              "We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."
            );
          bubbleProperties(workInProgress);
          return null;
        }
        current = rootInstanceStackCursor.current;
        renderLanes = nextReactTag;
        nextReactTag += 2;
        // 获取ViewConfig
        type = getViewConfigForType(type);
        var updatePayload = ReactNativePrivateInterface.createAttributePayload(
          newProps,
          type.validAttributes
        );
        // 创建原生节点
        current = {
          node: createNode(
            renderLanes,
            type.uiViewClassName,  //  使用ViewConfig中的原生类名
            current.containerTag,
            updatePayload,
            workInProgress
          ),
          canonical: {
            nativeTag: renderLanes,
            viewConfig: type,       // 保存ViewConfig引用
            currentProps: newProps,
            internalInstanceHandle: workInProgress,
            publicInstance: null,
            publicRootInstance: current.publicInstance
          }
        };
        workInProgress.flags |= 8;
        appendAllChildren(current, workInProgress, !1, !1);
        workInProgress.stateNode = current;
      }
      bubbleProperties(workInProgress);
      workInProgress.flags &= -16777217;
      return null;
    // 省略部分代码......
  }
}

此方法中的case都是整数,为了弄清楚其含义,我们可以查看React 仓库中的Tag定义。源码位于 ReactWorkTags.js

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostRoot = 3; // 宿主树的根节点。它可以嵌套在另一个节点内部
export const HostPortal = 4; // 一个子树。它可以是通往不同渲染器的入口点
export const HostComponent = 5;
// 省略......

很显然,我们需要查看的是React Native的Tag类型,也就是HostComponent,它的值是5,因此对应到completeWork中的处理代码就是我截取的这部分。这段代码中有一个关键的方法,就是getViewConfigForType,查看其定义,显示为getViewConfigForType = ReactNativePrivateInterface.ReactNativeViewConfigRegistry.get

可看到,这里就完全与前面声明组件一节最后分析到的ReactNativeViewConfigRegistry.js对应上了,我们前面分析的是ViewConfig的注册,现在来看一下get方法:

/**
 * 获取指定视图的配置。如果这是第一次使用该视图,此配置将从UIManager延迟加载
 */
export function get(name: string): ViewConfig {
  // 从viewConfigs Map中查找已缓存的ViewConfig
  let viewConfig = viewConfigs.get(name);
  if (viewConfig == null) {
    // 获取该组件的配置生成回调函数
    const callback = viewConfigCallbacks.get(name);
    if (typeof callback !== 'function') {
      // 省略日志......
    }
    viewConfig = callback();
    invariant(viewConfig, 'View config not found for component `%s`', name);

    processEventTypes(viewConfig);
    viewConfigs.set(name, viewConfig);

    // 配置设置后清除回调,这样我们就不会在注册过程中掩盖任何错误。
    viewConfigCallbacks.set(name, null);
  }
  return viewConfig;
}

通过ViewConfig,可以得到uiViewClassName,也就是声明的组件名称,我们继续查看原生节点创建的方法:

var _nativeFabricUIManage = nativeFabricUIManager,
  createNode = _nativeFabricUIManage.createNode,          


 createNode(
   renderLanes,
   type.uiViewClassName,  //  使用ViewConfig中的原生类名
   current.containerTag,
   updatePayload,
   workInProgress
 )

createNode方法来自于全局对象nativeFabricUIManager,通过变量名就知道,这个应该是来自于JSI定义的对象,代码不在JS层。

C++ 层

继续在C++中搜索nativeFabricUIManager,定位到源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

void UIManagerBinding::createAndInstallIfNeeded(
    jsi::Runtime& runtime,
    const std::shared_ptr<UIManager>& uiManager) {
  auto uiManagerModuleName = "nativeFabricUIManager";

  auto uiManagerValue =
      runtime.global().getProperty(runtime, uiManagerModuleName);
  if (uiManagerValue.isUndefined()) {
    // 全局命名空间中没有该绑定的实例;我们需要创建、安装并返回它
    auto uiManagerBinding = std::make_shared<UIManagerBinding>(uiManager);
    auto object = jsi::Object::createFromHostObject(runtime, uiManagerBinding);
    runtime.global().setProperty(
        runtime, uiManagerModuleName, std::move(object));
  }
}

可见这里的nativeFabricUIManager是一个jsi::HostObject对象,我们要查找其createNode方法,直接查看UIManagerBindingget方法:

jsi::Value UIManagerBinding::get(
    jsi::Runtime& runtime,
    const jsi::PropNameID& name) {
  auto methodName = name.utf8(runtime);

  // 将 shared_ptr<UIManager> 转换为原始指针
  // 为什么这样做?原因如下:
  // 1) UIManagerBinding 强引用(strongly retains)UIManager。
  //    JS VM 通过 JSI 强引用 UIManagerBinding。
  //    这些函数是 JSI 函数,只能通过 JS VM 调用;如果 JS VM 被销毁,
  //    这些函数无法执行,这些 lambda 也不会执行。
  // 2) UIManager 只有在所有对它的引用都被释放后才会被析构,包括
  //    UIManagerBinding。这只有在 JS VM 被析构时才会发生。因此,原始指针是安全的。
  //
  // 即使这样做是安全的,为什么不直接使用 shared_ptr 作为额外的保险呢?
  // 1) 在不需要的情况下使用 shared_ptr 或 weak_ptr
  // 是一种性能劣化(pessimisation)。
  //    在这种情况下,它会执行更多指令,但不会带来任何额外价值。
  // 2) 这些 lambda 的确切释放时机和方式很复杂。向它们添加 shared_ptr 会导致
  //    UIManager 可能存活更长时间,这是不必要的、复杂的认知负担。
  // 3) 有强烈怀疑认为,从这些 C++ lambda 中保留 UIManager(这些 lambda 被
  //    JSI 持有的对象所保留),在 Scheduler 和 JS VM 析构时导致了一些崩溃。
  //    如果 C++ 语义导致这些 lambda 在 JS VM 被析构后一个 CPU
  //    时钟周期(或更久) 才被释放,就可能发生这种情况。
  UIManager* uiManager = uiManager_.get();

  // Semantic: Creates a new node with given pieces.
  if (methodName == "createNode") {
    auto paramCount = 5;
    return jsi::Function::createFromHostFunction(
        runtime,
        name,
        paramCount,
        [uiManager, methodName, paramCount](
            jsi::Runtime& runtime,
            const jsi::Value& /*thisValue*/,
            const jsi::Value* arguments,
            size_t count) -> jsi::Value {
          try {
            validateArgumentCount(runtime, methodName, paramCount, count);

            auto instanceHandle =
                instanceHandleFromValue(runtime, arguments[4], arguments[0]);
            if (!instanceHandle) {
              react_native_assert(false);
              return jsi::Value::undefined();
            }

            return valueFromShadowNode(
                runtime,
                uiManager->createNode(
                    tagFromValue(arguments[0]),
                    stringFromValue(runtime, arguments[1]),
                    surfaceIdFromValue(runtime, arguments[2]),
                    RawProps(runtime, arguments[3]),
                    std::move(instanceHandle)),
                true);
          } catch (const std::logic_error& ex) {
            LOG(FATAL) << "logic_error in createNode: " << ex.what();
          }
        });
  }
  // 省略部分代码......

  return jsi::Value::undefined();
}

继续跟踪react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp中的createNode实现:

std::shared_ptr<ShadowNode> UIManager::createNode(
    Tag tag,                  // 节点标签
    const std::string& name,  // 组件名称
    SurfaceId surfaceId,
    RawProps rawProps,        // 原始属性
    InstanceHandle::Shared instanceHandle) const {
  TraceSection s("UIManager::createNode", "componentName", name);

  // 根据组件名称获取对应的ComponentDescriptor
  auto& componentDescriptor = componentDescriptorRegistry_->at(name);
  auto fallbackDescriptor =
      componentDescriptorRegistry_->getFallbackComponentDescriptor();

  PropsParserContext propsParserContext{surfaceId, *contextContainer_};

  // 创建ShadowNodeFamily,用于管理同一组件的不同实例
  auto family = componentDescriptor.createFamily(
      {.tag = tag,
       .surfaceId = surfaceId,
       .instanceHandle = std::move(instanceHandle)});

  // 解析和克隆属性
  const auto props = componentDescriptor.cloneProps(
      propsParserContext, nullptr, std::move(rawProps));
  // 创建初始状态
  const auto state = componentDescriptor.createInitialState(props, family);

  // 创建ShadowNode
  auto shadowNode = componentDescriptor.createShadowNode(
      ShadowNodeFragment{
          .props = fallbackDescriptor != nullptr &&
                  fallbackDescriptor->getComponentHandle() ==
                      componentDescriptor.getComponentHandle()
              ? componentDescriptor.cloneProps(
                    propsParserContext,
                    props,
                    RawProps(folly::dynamic::object("name", name)))
              : props,
          .children = ShadowNodeFragment::childrenPlaceholder(),
          .state = state,
      },
      family);

  if (delegate_ != nullptr) {
    delegate_->uiManagerDidCreateShadowNode(*shadowNode);
  }
  if (leakChecker_) {
    leakChecker_->uiManagerDidCreateShadowNodeFamily(family);
  }

  return shadowNode;
}

可以看到,这里通过componentDescriptorRegistry_来查找Fabric 组件描述对象,至于注册的地方,下一节注册组件会专门分析。

这里还有一点要注意,createShadowNode方法只是创建了虚拟的ShadowNode,并没有创建真正的原生视图。ShadowNode是Fabric中的虚拟DOM节点,用于布局计算。也就是说当完成布局和diff计算后,会生成MountItem指令。

Kotlin层

在前面启动渲染一节中,我们有分析到createViewUnsafe方法,回顾一下安卓原生组件的查找:

ViewManager viewManager = mViewManagerRegistry.get(componentName);

跟踪源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.kt

  @Synchronized
  public fun get(className: String): ViewManager<*, *> {
    // 1. Try to get the manager without the prefix.
    viewManagersMap[className]?.let {
      return it
    }

    // 2. Try to get the manager with the RCT prefix.
    val rctViewManagerName = "RCT$className"
    viewManagersMap[rctViewManagerName]?.let {
      return it
    }

    if (viewManagerResolver != null) {

      // 1. Try to get the manager without the prefix.
      val resolvedManager = getViewManagerFromResolver(className)
      if (resolvedManager != null) {
        return resolvedManager
      }

      // 2. Try to get the manager with the RCT prefix.
      val rctResolvedManager = getViewManagerFromResolver(rctViewManagerName)
      if (rctResolvedManager != null) {
        return rctResolvedManager
      }

      throw IllegalViewOperationException(
          "Can't find ViewManager '$className' nor '$rctViewManagerName' in ViewManagerRegistry, " +
              "existing names are: ${viewManagerResolver.getViewManagerNames()}"
      )
    }

    throw IllegalViewOperationException("No ViewManager found for class $className")
  }

  private fun getViewManagerFromResolver(className: String): ViewManager<*, *>? {
    val viewManager = viewManagerResolver?.getViewManager(className)
    if (viewManager != null) {
      viewManagersMap[className] = viewManager
    }
    return viewManager
  }

新架构的情况下,是通过getViewManagerFromResolver方法来查找,其中viewManagerResolver的类型是BridgelessViewManagerResolver,它是一个内部类,定义在ReactInstance.kt文件中:

private class BridgelessViewManagerResolver(
      private val reactPackages: List<ReactPackage>,
      private val context: BridgelessReactContext,
  ) : ViewManagerResolver {
    private val lazyViewManagerMap: MutableMap<String, ViewManager<*, *>> = HashMap()

    override fun getViewManager(viewManagerName: String): ViewManager<*, *>? {
      // 从懒加载包中查找
      val viewManager = getLazyViewManager(viewManagerName)
      if (viewManager != null) {
        return viewManager
      }
      // 如果通过延迟加载在所有 React 包中都找不到视图管理器,则回退到默认实现:立即初始化所有视图管理器
      return eagerViewManagerMap[viewManagerName]
    }

    private lateinit var _eagerViewManagerMap: Map<String, ViewManager<*, *>>

    @get:Synchronized
    val eagerViewManagerMap: Map<String, ViewManager<*, *>>
      get() {
        if (::_eagerViewManagerMap.isInitialized) {
          return _eagerViewManagerMap
        }

        val viewManagerMap: MutableMap<String, ViewManager<*, *>> = HashMap()
        for (reactPackage in reactPackages) {
          if (reactPackage is ViewManagerOnDemandReactPackage) {
            continue
          }

          val viewManagersInPackage = reactPackage.createViewManagers(context)
          for (viewManager in viewManagersInPackage) {
            // TODO(T173624687): Should we throw/warn when the same view manager name is registered
            // twice?
            viewManagerMap[viewManager.name] = viewManager
          }
        }

        _eagerViewManagerMap = viewManagerMap
        return viewManagerMap
      }

    @Synchronized
    fun getLazyViewManager(viewManagerName: String): ViewManager<*, *>? {
      // 先查缓存 
      if (lazyViewManagerMap.containsKey(viewManagerName)) {
        return lazyViewManagerMap[viewManagerName]
      }

      // 缓存未命中则遍历所有 reactPackages,调用 createViewManager(name) 创建
      for (reactPackage in reactPackages) {
        if (reactPackage is ViewManagerOnDemandReactPackage) {
          val viewManager = reactPackage.createViewManager(context, viewManagerName)
          if (viewManager != null) {
            // TODO(T173624687): Should we throw/warn when the same view manager name is registered
            // twice?
            lazyViewManagerMap[viewManagerName] = viewManager
            return viewManager
          }
        }
      }

      return null
    }

    // 省略部分代码......
  }

注册组件

JS工具

React Native新架构使用了很多代码生成工具,以致于成了黑箱操作,这对于Turbo Module和Fabric组件的注册流程造成了理解上的困难。为此,我们不得不研究这些CLI工具。首先研究@react-native-community/cli工具,源码 cli。这里我们使用的版本是v20.0.2

查看源码cli/packages/cli-config-android/src/config/index.ts

export function dependencyConfig(
  root: string,
  userConfig: AndroidDependencyParams | null = {},
): AndroidDependencyConfig | null {
  if (userConfig === null) {
    return null;
  }

  const src = userConfig.sourceDir || findAndroidDir(root);

  if (!src) {
    return null;
  }

  const sourceDir = path.join(root, src);
  const manifestPath = userConfig.manifestPath
    ? path.join(sourceDir, userConfig.manifestPath)
    : findManifest(sourceDir);
  const buildGradlePath = findBuildGradle(sourceDir, '');
  const isPureCxxDependency =
    userConfig.cxxModuleCMakeListsModuleName != null &&
    userConfig.cxxModuleCMakeListsPath != null &&
    userConfig.cxxModuleHeaderName != null &&
    !manifestPath &&
    !buildGradlePath;

  if (!manifestPath && !buildGradlePath && !isPureCxxDependency) {
    return null;
  }

  let packageImportPath = null,
    packageInstance = null;

  if (!isPureCxxDependency) {
    const packageName =
      userConfig.packageName || getPackageName(manifestPath, buildGradlePath);
    const packageClassName = findPackageClassName(sourceDir);

    /**
     * This module has no package to export
     */
    if (!packageClassName) {
      return null;
    }

    packageImportPath =
      userConfig.packageImportPath ||
      `import ${packageName}.${packageClassName};`;

    packageInstance = userConfig.packageInstance || `new ${packageClassName}()`;
  }

  const buildTypes = userConfig.buildTypes || [];
  const dependencyConfiguration = userConfig.dependencyConfiguration;
  const libraryName =
    userConfig.libraryName || findLibraryName(root, sourceDir);
  const componentDescriptors =
    userConfig.componentDescriptors || findComponentDescriptors(root);
  let cmakeListsPath = userConfig.cmakeListsPath
    ? path.join(sourceDir, userConfig.cmakeListsPath)
    : path.join(sourceDir, 'build/generated/source/codegen/jni/CMakeLists.txt');
  const cxxModuleCMakeListsModuleName =
    userConfig.cxxModuleCMakeListsModuleName || null;
  const cxxModuleHeaderName = userConfig.cxxModuleHeaderName || null;
  let cxxModuleCMakeListsPath = userConfig.cxxModuleCMakeListsPath
    ? path.join(sourceDir, userConfig.cxxModuleCMakeListsPath)
    : null;

  if (process.platform === 'win32') {
    cmakeListsPath = cmakeListsPath.replace(/\\/g, '/');
    if (cxxModuleCMakeListsPath) {
      cxxModuleCMakeListsPath = cxxModuleCMakeListsPath.replace(/\\/g, '/');
    }
  }

  return {
    sourceDir,
    packageImportPath,
    packageInstance,
    buildTypes,
    dependencyConfiguration,
    libraryName,
    componentDescriptors,
    cmakeListsPath,
    cxxModuleCMakeListsModuleName,
    cxxModuleCMakeListsPath,
    cxxModuleHeaderName,
    isPureCxxDependency,
  };
}

dependencyConfig函数非常重要,它主要用于生成example/android/build/generated/autolinking/autolinking.json文件。autolinking.json文件我们在前面的TurboModule的文章已经介绍过了,其中的信息是指导代码生成的关键。这里重点关注componentDescriptors字段的生成,因为它是Fabric组件代码生成的起始。

组件开发者通常并不会在react-native.config.js文件中主动配置componentDescriptors字段,一般都是默认值,那么就要查看一下findComponentDescriptors函数的实现逻辑了,源码cli/packages/cli-config-android/src/config/findComponentDescriptors.ts

export function findComponentDescriptors(packageRoot: string) {
  let jsSrcsDir = null;
  try {
    const packageJson = fs.readFileSync(
      path.join(packageRoot, 'package.json'),
      'utf8',
    );
    jsSrcsDir = JSON.parse(packageJson).codegenConfig.jsSrcsDir;
  } catch (error) {
    // no jsSrcsDir, continue with default glob pattern
  }
  const globPattern = jsSrcsDir
    ? `${jsSrcsDir}/**/*{.js,.jsx,.ts,.tsx}`
    : '**/*{.js,.jsx,.ts,.tsx}';
  const files = glob.sync(globPattern, {
    cwd: unixifyPaths(packageRoot),
    onlyFiles: true,
    ignore: ['**/node_modules/**'],
  });
  const codegenComponent = files
    .map((filePath) =>
      fs.readFileSync(path.join(packageRoot, filePath), 'utf8'),
    )
    .map(extractComponentDescriptors)
    .filter(Boolean);

  // Filter out duplicates as it happens that libraries contain multiple outputs due to package publishing.
  // TODO: consider using "codegenConfig" to avoid this.
  return Array.from(new Set(codegenComponent as string[]));
}

这里主要逻辑其实是从package.json中获取到jsSrcsDir配置,从而定位到源码所在目录。我们关注的是codegenComponent的生成,此处是通过读取每个源文件内容,然后调用 extractComponentDescriptors 提取组件描述符。继续查看cli/packages/cli-config-android/src/config/extractComponentDescriptors.ts

const CODEGEN_NATIVE_COMPONENT_REGEX =
  /codegenNativeComponent(<.*>)?\s*\(\s*["'`](\w+)["'`](,?[\s\S]+interfaceOnly:\s*(\w+))?/m;

export function extractComponentDescriptors(contents: string) {
  const match = contents.match(CODEGEN_NATIVE_COMPONENT_REGEX);
  if (!(match?.[4] === 'true') && match?.[2]) {
    return `${match[2]}ComponentDescriptor`;
  }
  return null;
}

到这里就很清楚了,扫描所有源码,使用正则找到我们在声明组件一节中提到的export default codegenNativeComponent<NativeProps>('CustomviewView');这行代码,这里的正则就是匹配关键字codegenNativeComponent。这里的match?.[2]就是提前出组件名,也就是我们示例中的CustomviewView。最终返回的字符串是拼接后的,这里就是CustomviewViewComponentDescriptor。也就是说componentDescriptors字段的默认值是CustomviewViewComponentDescriptor

接下来我们回顾前文《Android端TurboModule分析》,其中提到但略过的React Gradle脚本的configureCodegen方法:

  private fun configureCodegen(
      project: Project,
      localExtension: ReactExtension,
      rootExtension: PrivateReactExtension,
      isLibrary: Boolean,
  ) {
    // 首先,我们需要设置 Codegen 的输出目录
    val generatedSrcDir: Provider<Directory> =
        project.layout.buildDirectory.dir("generated/source/codegen")

    // 我们为 jsRootDir(JS根目录)指定默认值(约定)。
    // 对于 App 来说是根文件夹(即 Gradle 项目的 ../../)
    // 对于 Library 来说是包文件夹(即 Gradle 项目的 ../)
    if (isLibrary) {
      localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
    } else {
      localExtension.jsRootDir.convention(localExtension.root)
    }

    // 我们创建任务以从 JS 文件生成 Schema
    val generateCodegenSchemaTask =
        project.tasks.register(
            "generateCodegenSchemaFromJavaScript",
            GenerateCodegenSchemaTask::class.java,
        ) { it ->
          it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
          it.codegenDir.set(rootExtension.codegenDir)
          it.generatedSrcDir.set(generatedSrcDir)
          it.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)

          // 我们在配置阶段读取 package.json,以便正确地填充此任务的 `jsRootDir` @Input 属性
          // 以及 onlyIf 条件。因此,parsePackageJson 应该在这个 lambda 表达式内部被调用。
          val packageJson = findPackageJsonFile(project, rootExtension.root)
          val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }

          val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
          val includesGeneratedCode =
              parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
          if (jsSrcsDirInPackageJson != null) {
            it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
          } else {
            it.jsRootDir.set(localExtension.jsRootDir)
          }
          it.jsInputFiles.set(
              project.fileTree(it.jsRootDir) { tree ->
                tree.include("**/*.js")
                tree.include("**/*.jsx")
                tree.include("**/*.ts")
                tree.include("**/*.tsx")

                tree.exclude("node_modules/**/*")
                tree.exclude("**/*.d.ts")
                // 我们希望排除 build 目录,以避免在执行规避检查时选中它们
                tree.exclude("**/build/**/*")
              }
          )

          val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
          it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
        }

    // 我们创建任务以从 Schema 生成 Java 代码
    val generateCodegenArtifactsTask =
        project.tasks.register(
            "generateCodegenArtifactsFromSchema",
            GenerateCodegenArtifactsTask::class.java,
        ) { task ->
          task.dependsOn(generateCodegenSchemaTask)
          task.reactNativeDir.set(rootExtension.reactNativeDir)
          task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
          task.generatedSrcDir.set(generatedSrcDir)
          task.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
          task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
          task.libraryName.set(localExtension.libraryName)
          task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)

          // 请注意,appNeedsCodegen 会触发在配置阶段读取 package.json,
          // 因为我们需要填充此任务的 onlyIf 条件。
          // 因此,appNeedsCodegen 需要在这个 lambda 表达式内部被调用
          val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
          val packageJson = findPackageJsonFile(project, rootExtension.root)
          val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
          val includesGeneratedCode =
              parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
          task.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
        }

    // 我们更新 Android 配置以包含生成的源码
    // 这相当于以下的 DSL:
    //
    // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
    if (isLibrary) {
      project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext
        ->
        ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
      }
    } else {
      project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl {
          ext ->
        ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
      }
    }

    // `preBuild` 是 AGP 自动注册的基础任务之一
    // 这将在编译整个项目之前调用 Codegen
    project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
  }

configureCodegen 是 React Native Gradle 插件的核心方法,负责配置 Fabric/TurboModule 新架构的代码生成(Codegen)流程。该方法在 Gradle 配置阶段执行,设置两个关键任务和 Android 源码集集成。

其流程可以分为五个阶段:

  1. 设置输出目录和 JS 根目录

  2. 创建 Schema 生成任务

  3. 创建代码生成任务

  4. 集成到 Android 构建系统

  5. 挂载到构建生命周期

这里概括一下整个流程:

1. Gradle 配置阶段
   ├─> 设置 generatedSrcDir = "build/generated/source/codegen"
   ├─> 设置 jsRootDir 默认值(Library: "../", App: root)
   ├─> 读取 package.json(配置阶段)
   │   ├─> 检查 codegenConfig.jsSrcsDir
   │   └─> 检查 codegenConfig.includesGeneratedCode
   ├─> 注册 generateCodegenSchemaTask
   │   ├─> 配置 Node.js 环境
   │   ├─> 设置 JS 输入文件(**/*.{js,jsx,ts,tsx})
   │   └─> 设置 onlyIf 条件
   ├─> 注册 generateCodegenArtifactsTask
   │   ├─> 依赖 generateCodegenSchemaTask
   │   └─> 设置 onlyIf 条件
   ├─> 配置 Android SourceSets
   │   └─> 添加 generatedSrcDir/java 到 main SourceSet
   └─> 建立 preBuild 依赖关系

2. Gradle 执行阶段(运行 ./gradlew build)
   ├─> preBuild 任务执行
   │   └─> generateCodegenArtifactsTask 执行
   │         ├─> generateCodegenSchemaTask 执行
   │         │     ├─> 扫描 JS/TS 文件
   │         │     └─> 生成 schema.json
   │         └─> 根据 schema.json 生成 Java/C++ 代码
   └─> compileJava 编译生成的代码

这里的两个关键任务分别是generateCodegenSchemaFromJavaScriptgenerateCodegenArtifactsFromSchema。前者从 JS/TS 生成 Schema JSON文件,后者从 Schema 生成 Java/C++ 代码。

Schema JSON主要是JS/TS的接口描述,生成路径是Your Project/node_modules/Third-party Lib/android/build/generated/source/codegen/schema.json,可以自行查看,这里我们重点关注generateCodegenArtifactsFromSchema,源码react-native/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt

abstract class GenerateCodegenArtifactsTask : Exec() {
  @get:Internal abstract val reactNativeDir: DirectoryProperty
  @get:Internal abstract val generatedSrcDir: DirectoryProperty
  @get:InputFile abstract val packageJsonFile: RegularFileProperty   // package.json 文件路径
  @get:Input abstract val nodeWorkingDir: Property<String>
  @get:Input abstract val nodeExecutableAndArgs: ListProperty<String>
  @get:Input abstract val codegenJavaPackageName: Property<String>
  @get:Input abstract val libraryName: Property<String>

  @get:InputFile
  val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")

  @get:OutputDirectory val generatedJavaFiles: Provider<Directory> = generatedSrcDir.dir("java")
  @get:OutputDirectory val generatedJniFiles: Provider<Directory> = generatedSrcDir.dir("jni")

  override fun exec() {
    val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters()
    setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName)
    super.exec()
  }

  internal fun resolveTaskParameters(): Pair<String, String> {
    val parsedPackageJson =
        if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) {
          JsonUtils.fromPackageJson(packageJsonFile.get().asFile)
        } else {
          null
        }
    val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get()
    val resolvedCodegenJavaPackageName =
        parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get()
    return resolvedLibraryName to resolvedCodegenJavaPackageName
  }

  internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) {
    val workingDir = File(nodeWorkingDir.get())
    commandLine(
        windowsAwareCommandLine(
            *nodeExecutableAndArgs.get().toTypedArray(),
            reactNativeDir.file("scripts/generate-specs-cli.js").get().asFile.cliPath(workingDir),
            "--platform",
            "android",
            "--schemaPath",
            generatedSchemaFile.get().asFile.cliPath(workingDir),
            "--outputDir",
            generatedSrcDir.get().asFile.cliPath(workingDir),
            "--libraryName",
            libraryName,
            "--javaPackageName",
            codegenJavaPackageName,
        )
    )
  }
}

该类主要用于执行外部 Node.js 命令,依据 schema.json文件,生成 Java/JNI 代码。需要注意一下,这里libraryNamecodegenJavaPackageName的取值,代码中是通过resolveTaskParameters方法提取出来的。回顾一下package.json文件的配置,大概是以下结构:

{
  "codegenConfig": {
    "name": "MyLibrary",
    "android": {
      "javaPackageName": "com.example.mylibrary"
    }
  }
}

那么libraryName就是"MyLibrary"codegenJavaPackageName就是"com.example.mylibrary"

现在再还原一下命令,其实就是以下形式:

node <reactNativeDir>/scripts/generate-specs-cli.js \
  --platform android \
  --schemaPath <generatedSrcDir>/schema.json \
  --outputDir <generatedSrcDir> \
  --libraryName <resolvedLibraryName> \
  --javaPackageName <resolvedCodegenJavaPackageName>

我们定位到该js文件react-native/packages/react-native/scripts/generate-specs-cli.js

const executor = require('./codegen/generate-specs-cli-executor');
// 省略......

function main() {
  executor.execute(
    // $FlowFixMe[prop-missing]
    argv.platform,
    // $FlowFixMe[prop-missing]
    argv.schemaPath,
    // $FlowFixMe[prop-missing]
    argv.outputDir,
    // $FlowFixMe[prop-missing]
    argv.libraryName,
    // $FlowFixMe[prop-missing]
    argv.javaPackageName,
    // $FlowFixMe[prop-missing]
    argv.libraryType,
  );
}

main();

继续跟踪react-native/packages/react-native/scripts/codegen/generate-specs-cli-executor.js

const utils = require('./codegen-utils');

const GENERATORS /*: {[string]: {[string]: $ReadOnlyArray<string>}} */ = {
  all: {
    android: ['componentsAndroid', 'modulesAndroid', 'modulesCxx'],
    ios: ['componentsIOS', 'modulesIOS', 'modulesCxx'],
  },
  components: {
    android: ['componentsAndroid'],
    ios: ['componentsIOS'],
  },
  modules: {
    android: ['modulesAndroid', 'modulesCxx'],
    ios: ['modulesIOS', 'modulesCxx'],
  },
};

function generateSpecFromInMemorySchema(
  platform /*: string */,
  schema /*: string */,
  outputDirectory /*: string */,
  libraryName /*: string */,
  packageName /*: string */,
  libraryType /*: string */,
  useLocalIncludePaths /*: boolean */,
) {
  validateLibraryType(libraryType);
  createOutputDirectoryIfNeeded(outputDirectory, libraryName);
  const includeGetDebugPropsImplementation =
    libraryName.includes('FBReactNativeSpec'); //only generate getDebugString for React Native Core Components
  utils.getCodegen().generate(
    {
      libraryName,
      schema,
      outputDirectory,
      packageName,
      assumeNonnull: platform === 'ios',
      useLocalIncludePaths,
      includeGetDebugPropsImplementation,
    },
    {
      generators: GENERATORS[libraryType][platform],
    },
  );

  if (platform === 'android') {
    // Move all components C++ files to a structured jni folder for now.
    // Note: this should've been done by RNCodegen's generators, but:
    // * the generators don't support platform option yet
    // * this subdir structure is Android-only, not applicable to iOS
    const files = fs.readdirSync(outputDirectory);
    const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`;
    fs.mkdirSync(jniOutputDirectory, {recursive: true});
    files
      .filter(f => f.endsWith('.h') || f.endsWith('.cpp'))
      .forEach(f => {
        fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`);
      });
  }
}

这里核心是调用utils.getCodegen().generate来执行代码生成逻辑,但是这里有一个传参需要注意一下,generators: GENERATORS[libraryType][platform],我们观察GENERATORS的定义就会发现,这里的参数配置正好对应package.json中的codegen配置,由于我们现在研究的是Fabric组件注册,那么这里的参数应该是componentsAndroid

继续查找generate方法的实现,源码react-native/packages/react-native/scripts/codegen/codegen-utils.js

/**
* 用于抽象实际代码生成过程的包装器。 
* 之所以需要这个包装器,是因为在 Sandcastle 中运行测试时,并非所有环境都像通常那样设置。 
* 例如,`@react-native/codegen` 库就不存在。 
*
* 借助这个包装器,我们可以模拟代码生成器的 getter 方法,使其返回一个自定义对象,该对象模拟了 Codegen 接口。 
*
* @return 一个可以为新架构生成代码的对象。 
*/
function getCodegen() /*: $FlowFixMe */ {
  let RNCodegen;
  try {
    // $FlowFixMe[cannot-resolve-module]
    RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js');
  } catch (e) {
    // $FlowFixMe[cannot-resolve-module]
    RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen.js');
  }
  if (!RNCodegen) {
    throw new Error('RNCodegen not found.');
  }
  return RNCodegen;
}

继续跟踪react-native/packages/react-native-codegen/lib/generators/RNCodegen.js

const LIBRARY_GENERATORS = {
  descriptors: [
    generateComponentDescriptorCpp.generate,
    generateComponentDescriptorH.generate,
  ],
  events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
  states: [generateStateCpp.generate, generateStateH.generate],
  props: [
    generateComponentHObjCpp.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generatePropsJavaInterface.generate,
    generatePropsJavaDelegate.generate,
  ],
  // TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
  componentsAndroid: [
    // JNI/C++ files
    generateComponentDescriptorH.generate,
    generateComponentDescriptorCpp.generate,
    generateEventEmitterCpp.generate,
    generateEventEmitterH.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generateStateCpp.generate,
    generateStateH.generate,
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
    // Java files
    generatePropsJavaInterface.generate,
    generatePropsJavaDelegate.generate,
  ],
  componentsIOS: [
    generateComponentDescriptorH.generate,
    generateComponentDescriptorCpp.generate,
    generateEventEmitterCpp.generate,
    generateEventEmitterH.generate,
    generateComponentHObjCpp.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generateStateCpp.generate,
    generateStateH.generate,
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
  ],
  modulesAndroid: [
    generateModuleJniCpp.generate,
    generateModuleJniH.generate,
    generateModuleJavaSpec.generate,
  ],
  modulesCxx: [generateModuleH.generate],
  modulesIOS: [generateModuleObjCpp.generate],
  tests: [generateTests.generate],
  'shadow-nodes': [
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
  ],
};


module.exports = {
  allGenerators: ALL_GENERATORS,
  generate(
    {
      libraryName,
      schema,
      outputDirectory,
      packageName,
      assumeNonnull,
      useLocalIncludePaths,
      includeGetDebugPropsImplementation = false,
      libraryGenerators = LIBRARY_GENERATORS,
    },
    {generators, test},
  ) {
    schemaValidator.validate(schema);
    const defaultHeaderPrefix = 'react/renderer/components';
    const headerPrefix =
      useLocalIncludePaths === true
        ? ''
        : `${defaultHeaderPrefix}/${libraryName}/`;
    function composePath(intermediate) {
      return path.join(outputDirectory, intermediate, libraryName);
    }
    const componentIOSOutput = composePath(
      useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
    );
    const modulesIOSOutput = composePath('./');
    const outputFoldersForGenerators = {
      componentsIOS: componentIOSOutput,
      modulesIOS: modulesIOSOutput,
      descriptors: outputDirectory,
      events: outputDirectory,
      props: outputDirectory,
      states: outputDirectory,
      componentsAndroid: outputDirectory,
      modulesAndroid: outputDirectory,
      modulesCxx: outputDirectory,
      tests: outputDirectory,
      'shadow-nodes': outputDirectory,
    };
    const generatedFiles = [];
    for (const name of generators) {
      for (const generator of libraryGenerators[name]) {
        generator(
          libraryName,
          schema,
          packageName,
          assumeNonnull,
          headerPrefix,
          includeGetDebugPropsImplementation,
        ).forEach((contents, fileName) => {
          generatedFiles.push({
            name: fileName,
            content: contents,
            outputDir: outputFoldersForGenerators[name],
          });
        });
      }
    }
    return checkOrWriteFiles(generatedFiles, test);
  },
  // 省略......
};

可以看到,这里for (const name of generators)遍历的generators就是我们前面强调过的GENERATORS参数处理。那么此时name的值就是componentsAndroid

既然确定了参数,那么从libraryGenerators中遍历出来的generator就是以下这些生成器:

  componentsAndroid: [
    // JNI/C++ files
    generateComponentDescriptorH.generate,
    generateComponentDescriptorCpp.generate,
    generateEventEmitterCpp.generate,
    generateEventEmitterH.generate,
    generatePropsCpp.generate,
    generatePropsH.generate,
    generateStateCpp.generate,
    generateStateH.generate,
    generateShadowNodeCpp.generate,
    generateShadowNodeH.generate,
    // Java files
    generatePropsJavaInterface.generate,
    generatePropsJavaDelegate.generate,
  ],

这里先研究一下generateComponentDescriptorH.generategenerateComponentDescriptorCpp.generate

源码react-native/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js

const FileTemplate = ({
  libraryName,
  componentDefinitions,
  headerPrefix,
}: {
  libraryName: string,
  componentDefinitions: string,
  headerPrefix: string,
}) => `
/**
 * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
 *
 * Do not edit this file as changes may cause incorrect behavior and will be lost
 * once the code is regenerated.
 *
 * ${'@'}generated by codegen project: GenerateComponentDescriptorH.js
 */

#pragma once

${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

${componentDefinitions}

void ${libraryName}_registerComponentDescriptorsFromCodegen(
  std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);

} // namespace facebook::react
`;

const ComponentDefinitionTemplate = ({className}: {className: string}) =>
  `
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();

// 省略部分代码......

这里我们只简单看一下用于头文件生成的字符串模版。libraryName参数取值我们上面已经分析过了,来自package.json中的配置项。那么还有一个关键参数className的取值需要弄清楚。实际上这里的componentDefinitionsclassName都来自于schema.json。具体看一下className生成逻辑:

    const componentDefinitions = Object.keys(schema.modules)
      .map(moduleName => {
        const module = schema.modules[moduleName];
        if (module.type !== 'Component') {
          return;
        }

        const {components} = module;
        // No components in this module
        if (components == null) {
          return null;
        }

        return Object.keys(components)
          .map(componentName => {
            if (components[componentName].interfaceOnly === true) {
              return;
            }

            return ComponentDefinitionTemplate({className: componentName});
          })
          .join('\n');
      })
      .filter(Boolean)
      .join('\n');

    const replacedTemplate = FileTemplate({
      libraryName,
      componentDefinitions,
      headerPrefix: headerPrefix ?? '',
    });

可以看到,实际上是在遍历schema.json中的components字段。在声明组件一节已经创建了Demo工程,现在构建项目生成schema.json,查看相关内容:

{
  "libraryName": "",
  "modules": {
    "CustomWebView": {
      "type": "Component",
      "components": {
        "CustomWebView": {
          "extendsProps": [
            {
              "type": "ReactNativeBuiltInType",
              "knownTypeName": "ReactNativeCoreViewProps"
            }
          ],
          "events": [省略......],
          "props": [省略......],
          "commands": []
        }
      }
    }
  }
}

那么className就是componentName,也就是自定义的组件名CustomWebView

C++ 层

弄清楚代码生成的逻辑之后,接下来我们可以直接查看生成的文件内容,主要是ComponentDescriptors.h

#pragma once

#include <react/renderer/components/CustomviewViewSpec/ShadowNodes.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

using CustomWebViewComponentDescriptor = ConcreteComponentDescriptor<CustomWebViewShadowNode>;

void CustomviewViewSpec_registerComponentDescriptorsFromCodegen(
  std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
}

ComponentDescriptors.cpp

#include <react/renderer/components/CustomviewViewSpec/ComponentDescriptors.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

namespace facebook::react {

void CustomviewViewSpec_registerComponentDescriptorsFromCodegen(
  std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
registry->add(concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>());
}

}

头文件定义了一个类型别名CustomWebViewComponentDescriptor,然后在实现文件中注册了这个Provider。我们看一下concreteComponentDescriptorProvider函数的实现,源码react-native/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProvider.h:

/*
* 为给定的 `ComponentDescriptor` 类创建一个 `ComponentDescriptorProvider`
*/
template <typename ComponentDescriptorT>
ComponentDescriptorProvider concreteComponentDescriptorProvider()
{
  static_assert(
      std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
      "ComponentDescriptorT must be a descendant of ComponentDescriptor");

  return {
      ComponentDescriptorT::ConcreteShadowNode::Handle(),
      ComponentDescriptorT::ConcreteShadowNode::Name(),
      nullptr,
      &concreteComponentDescriptorConstructor<ComponentDescriptorT>};
}

返回值是一个ComponentDescriptorProvider类型实例,继续跟踪一下类定义:

/*
 * 提供了一种统一的方式来构造特定存储的 `ComponentDescriptor` 类的实例。 
 * C++ 不允许创建指向构造函数的指针,因此我们必须使用这样的数据结构来操作一组类。
 *
 * 注意:某些组件的 `handle` 和 `name` 的实际值取决于 `flavor`。 
 * 如果使用给定的 `flavor` 通过 `constructor` 对象实例化后,
 * Provider暴露的 `handle` 和 `name` 值与预期值相同,则该提供者有效。 
 */
class ComponentDescriptorProvider final {
 public:
  ComponentHandle handle;
  ComponentName name;
  ComponentDescriptor::Flavor flavor;
  ComponentDescriptorConstructor *constructor;
};

这里大量使用了C++模版,要想查看真正的handle和name值,需要当实际的模版类型中查找。这里先查看源码react-native/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h

namespace facebook::react {

/*
 * Default template-based implementation of ComponentDescriptor.
 * Use your `ShadowNode` type as a template argument and override any methods
 * if necessary.
 */
template <typename ShadowNodeT>
class ConcreteComponentDescriptor : public ComponentDescriptor {
  static_assert(std::is_base_of<ShadowNode, ShadowNodeT>::value, "ShadowNodeT must be a descendant of ShadowNode");

  using SharedShadowNodeT = std::shared_ptr<const ShadowNodeT>;

 public:
  using ConcreteShadowNode = ShadowNodeT;

  // 省略代码......
} // namespace facebook::react

可以看到,ConcreteShadowNode实际上只是一个类型别名,具体的要看模版的实际参数,那么ConcreteShadowNode::Handle就相当于CustomWebViewShadowNode::Handle。这里CustomWebViewShadowNode也是自动生成的代码,我们直接查看android/app/build/generated/source/codegen/jni/react/renderer/components/CustomviewViewSpec/ShadowNodes.h

/*
 * `ShadowNode` for <CustomWebView> component.
 */
using CustomWebViewShadowNode = ConcreteViewShadowNode<
    CustomWebViewComponentName,
    CustomWebViewProps,
    CustomWebViewEventEmitter,
    CustomWebViewState>;

继续查看android/app/build/generated/source/codegen/jni/react/renderer/components/CustomviewViewSpec/ShadowNodes.cpp

#include <react/renderer/components/CustomviewViewSpec/ShadowNodes.h>

namespace facebook::react {

extern const char CustomWebViewComponentName[] = "CustomWebView";

} 

现在跟踪一下react-native/packages/react-native/ReactCommon/react/renderer/components/view/ConcreteViewShadowNode.h

namespace facebook::react {

/*
 * Template for all <View>-like classes (classes which have all same props
 * as <View> and similar basic behaviour).
 * For example: <Paragraph>, <Image>, but not <Text>, <RawText>.
 */
template <
    const char *concreteComponentName,
    typename ViewPropsT = ViewProps,
    typename ViewEventEmitterT = ViewEventEmitter,
    typename StateDataT = StateData>
  requires(std::is_base_of_v<ViewProps, ViewPropsT>)
class ConcreteViewShadowNode : public ConcreteShadowNode<
                                   concreteComponentName,
                                   YogaLayoutableShadowNode,
                                   ViewPropsT,
                                   ViewEventEmitterT,
                                   StateDataT> {
  // 省略代码......
};

} // namespace facebook::react

ConcreteViewShadowNode类中并未实现HandleName方法,继续查看父类react-native/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h

namespace facebook::react {

/*
 * Base templace class for all `ShadowNode`s which connects exact `ShadowNode`
 * type with exact `Props` type.
 * `ConcreteShadowNode` is a default implementation of `ShadowNode` interface
 * with many handy features.
 */
template <
    ComponentName concreteComponentName,
    typename BaseShadowNodeT,
    typename PropsT,
    typename EventEmitterT = EventEmitter,
    typename StateDataT = StateData>
class ConcreteShadowNode : public BaseShadowNodeT {

 protected:
  using ShadowNode::props_;
  using ShadowNode::state_;

 public:
  using BaseShadowNodeT::BaseShadowNodeT;
  // 省略......

  static ComponentName Name()
  {
    return ComponentName(concreteComponentName);
  }

  static ComponentHandle Handle()
  {
    return ComponentHandle(concreteComponentName);
  }

  // 省略......
};

} // namespace facebook::react

到这里就很清晰了,Name和Handle方法返回值内部是持有的相同的concreteComponentName,而这个模版参数,根据前面的传参,实际是就是

CustomWebViewComponentName,也就是"CustomWebView"

扫描Fabric 组件库,生成代码的逻辑其实已经很清楚了,最后只剩下一个问题,真正调用注册的代码在哪里?事实上,安卓中并未真正通过CustomviewViewSpec_registerComponentDescriptorsFromCodegen函数去注册,而是使用了autolinking机制。这部分在《Android端TurboModule分析》一文有详细介绍了,可以去回顾一下GenerateAutolinkingNewArchitecturesFileTask脚本的分析,其中生成的信息源就来自我们前面费了半天劲分析的autolinking.json中的

现在来看一下Gradle脚本生成的autolinking.cpp

#include "autolinking.h"
#include <CustomviewViewSpec.h>
#include <react/renderer/components/CustomviewViewSpec/ComponentDescriptors.h>

namespace facebook {
namespace react {

std::shared_ptr<TurboModule> autolinking_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams &params) {
auto module_CustomviewViewSpec = CustomviewViewSpec_ModuleProvider(moduleName, params);
if (module_CustomviewViewSpec != nullptr) {
return module_CustomviewViewSpec;
}
  return nullptr;
}

std::shared_ptr<TurboModule> autolinking_cxxModuleProvider(const std::string moduleName, const std::shared_ptr<CallInvoker>& jsInvoker) {

  return nullptr;
}

void autolinking_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry) {
providerRegistry->add(concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>());
  return;
}

} // namespace react
} // namespace facebook

这里生成了autolinking_registerProviders方法,这才是真正注册组件的地方。而此处的代码是由Gradle脚本生成,其中的关键信息就来自autolinking.json中的componentDescriptors字段,也就是前面我们费了半天劲才分析出该字段的默认值的地方。整个React Native的代码生成其实是有些混乱的,会生成一些并不会使用的代码,对理解产生干扰。

关于autolinking_registerProviders函数的调用链,在前面的文章也分析涉及过,这里再回顾一下调用流程,源码react-native/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp

namespace facebook::react {

void registerComponents(
    std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
  // 自定义 Fabric 组件放在这里。您可以在此处注册来自您的应用程序或第三方库的自定义组件
  // providerRegistry->add(concreteComponentDescriptorProvider<
  //        MyComponentDescriptor>());

  // We link app local components if available
#ifdef REACT_NATIVE_APP_COMPONENT_REGISTRATION
  REACT_NATIVE_APP_COMPONENT_REGISTRATION(registry);
#endif

  // And we fallback to the components autolinked
  autolinking_registerProviders(registry);
}

// 省略部分代码......

} // namespace facebook::react

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
  return facebook::jni::initialize(vm, [] {
    facebook::react::DefaultTurboModuleManagerDelegate::cxxModuleProvider =
        &facebook::react::cxxModuleProvider;
    facebook::react::DefaultTurboModuleManagerDelegate::javaModuleProvider =
        &facebook::react::javaModuleProvider;
    facebook::react::DefaultComponentsRegistry::
        registerComponentDescriptorsFromEntryPoint =
            &facebook::react::registerComponents;
  });
}

可见,注册的起点也是JNI_OnLoad函数。

Kotlin层

使用npx create-react-native-library@latest工具创建一个Fabric组件库时,会生成一些模版代码,其中包括我们上面提到的CustomWebViewManager类,现在我们来看一下CustomWebViewPackage类:

class CustomWebViewPackage : ReactPackage {
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
    val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
    viewManagers.add(CustomWebViewManager())
    return viewManagers
  }

  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
    return emptyList()
  }
}

这里createViewManagers方法会返回一个ViewManager的实例列表。根据我们在《ReactNative新架构之Android端TurboModule机制完全解析》一文中分析GeneratePackageListTask任务的结果,我们知道最终会生成一个PackageList文件,其中会注入每个三方TurboModule或Fabric组件包中的ReactPackage实现类。

现在来查看一下我们示例工程生成的example/android/app/build/generated/autolinking/src/main/java/com/facebook/react/PackageList.java文件:

public class PackageList {
  private Application application;
  private ReactNativeHost reactNativeHost;
  private MainPackageConfig mConfig;

  // 省略部分代码......

  public ArrayList<ReactPackage> getPackages() {
    return new ArrayList<>(Arrays.<ReactPackage>asList(
      new MainReactPackage(mConfig),
      new CustomWebViewPackage()
    ));
  }
}

回顾一下本文开头的初始化部分,我们提到过以下代码

fabricUIManager =
    FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)

现在回顾一下《React Native新架构之Android端初始化源码分析》一文,在ReactInstance类构造时,有如下初始化逻辑:

    viewManagerResolver = BridgelessViewManagerResolver(reactPackages, context)

    ComponentNameResolverBinding.install(
        unbufferedRuntimeExecutor,
        object : ComponentNameResolver {
          override val componentNames: Array<String>
            get() {
              val viewManagerNames = viewManagerResolver.getViewManagerNames()
              if (viewManagerNames.isEmpty()) {
                FLog.e(TAG, "No ViewManager names found")
                return arrayOf()
              }
              return viewManagerNames.toTypedArray<String>()
            }
        },
    )

结合上一节查找组件 kotlin层实现的分析,整个流程都十分清晰了。

Win11 如何打开 IE11 浏览器

大家好,我是前端架构师,关注微信公众号【@程序员大卫】免费领取前端精品资料。

前言

有时候在调试老项目时,仍然需要使用 IE11 浏览器。但在 Windows 11 中,直接打开 IE 时,系统通常会自动跳转到 Microsoft Edge,导致无法真正进入 IE11。

下面教你一种简单、有效的方法,在 Win11 中直接打开 IE11。

1. 搜索并打开 cmd

在开始菜单中搜索 cmd,然后点击打开。

2. 粘贴并执行下面的脚本

将以下命令粘贴到 cmd 中并回车执行:

powershell -command "$pf86=[Environment]::GetFolderPath('ProgramFilesX86');$s=New-Object -ComObject WScript.Shell;$sc=$s.CreateShortcut(\"$env:USERPROFILE\Desktop\Internet Explorer.lnk\");$sc.TargetPath=\"$pf86\Internet Explorer\iexplore.exe\";$sc.Arguments='about:blank -Embedding';$sc.IconLocation=\"$pf86\Internet Explorer\iexplore.exe, 0\";$sc.Save()"

这段脚本会在桌面创建一个 IE 浏览器的快捷方式

3. 双击桌面的 IE 图标即可打开

执行完成后,桌面会出现一个 Internet Explorer 图标,双击它即可成功打开 IE11 浏览器。

React Native之Android端Fabric 架构源码分析(上)

React Native之Android端Fabric 架构源码分析(上)

前言

Fabric 是 React Native 新架构的 UI 渲染系统,现在我们就来深入分析其源码。

本文基于React Native 0.83版本源码进行分析。

初始化

《React Native新架构之Android端初始化源码分析》一文已经提过Fabric的初始化部分,现在回顾一下:

 // ReactInstance.kt   


    val eventBeatManager = EventBeatManager()
    fabricUIManager =
        FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)

    // 在 Fabric 初始化之前需要完成的其他初始化操作。
    DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)

    val binding = FabricUIManagerBinding()
    binding.register(
        getBufferedRuntimeExecutor(),
        getRuntimeScheduler(),
        fabricUIManager,
        eventBeatManager,
        componentFactory,
    )

    // 初始化 FabricUIManager
    fabricUIManager.initialize()

这里EventBeatManager 是 一个基于观察者模式的 Fabric 架构的 "节拍控制器",它利用 Android 原生的帧回调机制,协调并驱动 C++ 层的事件向 JS 层高效、有序地流动。

现在重点看一FabricUIManager的构造方法做了什么react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

  public FabricUIManager(
      ReactApplicationContext reactContext,
      ViewManagerRegistry viewManagerRegistry,
      BatchEventDispatchedListener batchEventDispatchedListener) {
    // 初始化帧回调 
    mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext);
    mReactApplicationContext = reactContext;
    // 初始化挂载管理器 
    mMountingManager = new MountingManager(viewManagerRegistry, mMountItemExecutor);
    // 初始化挂载指令调度器 
    mMountItemDispatcher =
        new MountItemDispatcher(mMountingManager, new MountItemDispatchListener());
    // 初始化事件分发器 
    mEventDispatcher = new FabricEventDispatcher(reactContext, new FabricEventEmitter(this));
    // 持有批处理事件监听器
    mBatchEventDispatchedListener = batchEventDispatchedListener;
    // 注册生命周期监听
    mReactApplicationContext.addLifecycleEventListener(this);
    // 注册组件回调 
    mViewManagerRegistry = viewManagerRegistry;
    mReactApplicationContext.registerComponentCallbacks(viewManagerRegistry);
  }

这段代码,首先创建一个派生自 Choreographer.FrameCallback的回调,这是 Fabric 渲染的“心脏”。它会注册到 Android 的 Choreographer,在每一帧垂直同步(VSync)信号到来时被调用。它负责驱动 MountItemDispatcher 来执行挂载操作(即实际的 View 更新)。

随后创建了一个挂载管理器MountingManager,它是实际操作 Android View 的管理者(执行 createView, updateProps 等)。接着创建了挂载指令调度器MountItemDispatcher,它负责管理挂载指令(MountItem)的队列,决定它们是在当前线程同步执行还是推入队列等待下一帧执行。当一批指令分发完成后,它会收到回调。这主要用于通知监听器,告诉它们“UI 已经更新了,你们可以进行下一帧动画计算了”。

接下来又创建了事件分发器FabricEventDispatcher,它负责将 Android 原生事件(如 Touch, Scroll)发送给 JavaScript。它的参数FabricEventEmitter是一个实现了 RCTEventEmitter 接口的类,它内部持有 C++ 层的引用(通过 JNI),是 Java 事件通往 C++ Fabric 核心的入口。

以上这写类基本上构成了一套UI系统的核心处理。如果大家需要更深入分析React Native UI系统,那么这些类就是研究的重点。在构造方法的最后,注册了生命周期监听,这是为了让 FabricUIManager 能够感知 Activity/Host 的 onResumeonPauseonDestroy。尤其是在 onHostResume 时恢复 UI 挂载循环,在 onHostPause 时暂停,以节省资源并避免在后台更新 UI。

最后注册组件回调,主要是用于当系统内存不足时,ViewManagerRegistry 可以收到通知并释放缓存。

现在继续分析初始化流程FabricUIManagerBinding的创建,源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt

@DoNotStrip
@SuppressLint("MissingNativeLoadLibrary")
internal class FabricUIManagerBinding : HybridClassBase() {
  init {
    initHybrid()
  }

  private external fun initHybrid()

  external fun setPixelDensity(pointScaleFactor: Float)

  private external fun installFabricUIManager(
      runtimeExecutor: RuntimeExecutor,
      runtimeScheduler: RuntimeScheduler,
      uiManager: FabricUIManager,
      eventBeatManager: EventBeatManager,
      componentsRegistry: ComponentFactory,
  )


  fun register(
      runtimeExecutor: RuntimeExecutor,
      runtimeScheduler: RuntimeScheduler,
      fabricUIManager: FabricUIManager,
      eventBeatManager: EventBeatManager,
      componentFactory: ComponentFactory,
  ) {
    fabricUIManager.setBinding(this)
    installFabricUIManager(
        runtimeExecutor,
        runtimeScheduler,
        fabricUIManager,
        eventBeatManager,
        componentFactory,
    )
    setPixelDensity(getDisplayMetricDensity())
  }

  // 省略部分代码......
}

该对象的构造,主要是调用initHybrid方法。关于initHybrid的机制,我们在前面的文章已经做了详细分析,这里就不再重复解释。

这里的FabricUIManagerBinding是 React Native Fabric 架构在 Android 端的 "核心启动器" 和 "跨语言胶水层"。它的主要作用是初始化 Fabric 的 C++ 核心组件,并建立 Java、C++ 和 JavaScript 三者之间的通信桥梁。当该对象被创建时,立即调用了其register方法。在这个方法中,主要是调用了installFabricUIManager,它将 C++ 层的 Fabric API 绑定到 JavaScript 运行时(Runtime)。这使得 JavaScript 可以直接通过 JSI 调用 C++ 接口(如 createNode, cloneNode, appendChild),实现同步且高效的 UI 操作。这里还有一个重要的操作,即setPixelDensity,将 Android 设备的屏幕像素密度(Density)传递给 React Native 的 C++ 核心层(Fabric/Yoga),用于统一布局单位。

最后,分析一下FabricUIManagerinitialize做了什么事:

  public void initialize() {
    // 注册事件批处理监听
    mEventDispatcher.addBatchEventDispatchedListener(mBatchEventDispatchedListener);

    // 启用 Fabric 日志与性能监控
    if (ReactNativeFeatureFlags.enableFabricLogs()) {
      mDevToolsReactPerfLogger = new DevToolsReactPerfLogger();
      mDevToolsReactPerfLogger.addDevToolsReactPerfLoggerListener(FABRIC_PERF_LOGGER);

      ReactMarker.addFabricListener(mDevToolsReactPerfLogger);
    }

    // 启用新旧架构互操作
    if (ReactNativeNewArchitectureFeatureFlags.useFabricInterop()) {
      InteropEventEmitter interopEventEmitter = new InteropEventEmitter(mReactApplicationContext);
      mReactApplicationContext.internal_registerInteropModule(
          RCTEventEmitter.class, interopEventEmitter);
    }
  }

这里首先是将 mBatchEventDispatchedListener(即 EventBeatManager)注册到事件分发器中。这是 “心跳” 连接的关键一步。当 Android 原生事件(如 Touch)被成批分发时,会通知 EventBeatManager,进而触发 C++ 层的 tick(),驱动 Fabric 渲染管线刷新。没有这一步,JavaScript 可能永远收不到事件更新。

接下来是性能监控相关的处理,开启需依赖enableFabricLogs的值,这是排查 Fabric 性能问题(如掉帧、白屏)和调试渲染流程的“开关”。

最后是启用新旧架构互操作的处理,这是 React Native 平滑迁移到新架构的重要兼容层,确保老代码在新架构下也能工作。

C++层

以上FabricUIManagerBinding提供了很多Native方法,我们在此重点分析一下installFabricUIManager。源码react-native/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp

void FabricUIManagerBinding::installFabricUIManager(
    jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutorHolder,
    jni::alias_ref<JRuntimeScheduler::javaobject> runtimeSchedulerHolder,
    jni::alias_ref<JFabricUIManager::javaobject> javaUIManager,
    EventBeatManager* eventBeatManager,
    ComponentFactory* componentsRegistry) {
  TraceSection s("FabricUIManagerBinding::installFabricUIManager");

  enableFabricLogs_ = ReactNativeFeatureFlags::enableFabricLogs();

  if (enableFabricLogs_) {
    LOG(WARNING)
        << "FabricUIManagerBinding::installFabricUIManager() was called (address: "
        << this << ").";
  }

  std::unique_lock lock(installMutex_);

  // 创建 C++ MountingManager (
  auto globalJavaUiManager = make_global(javaUIManager);
  mountingManager_ =
      std::make_shared<FabricMountingManager>(globalJavaUiManager);

  std::shared_ptr<const ContextContainer> contextContainer =
      std::make_shared<ContextContainer>();

  auto runtimeExecutor = runtimeExecutorHolder->cthis()->get();

  auto runtimeScheduler = runtimeSchedulerHolder->cthis()->get().lock();
  // 如果存在 RuntimeScheduler(通常都存在),则包装 runtimeExecutor。
  // 这意味着所有通过此 executor 提交的 JS 任务都会经过 RuntimeScheduler 调度,从而支持优先级和任务取消。
  if (runtimeScheduler) {
    runtimeExecutor =
        [runtimeScheduler](
            std::function<void(jsi::Runtime & runtime)>&& callback) {
          runtimeScheduler->scheduleWork(std::move(callback));
        };
    contextContainer->insert(
        RuntimeSchedulerKey, std::weak_ptr<RuntimeScheduler>(runtimeScheduler));
  }

  // 创建 EventBeat 工厂 
  EventBeat::Factory eventBeatFactory =
      [eventBeatManager, &runtimeScheduler, globalJavaUiManager](
          std::shared_ptr<EventBeat::OwnerBox> ownerBox)
      -> std::unique_ptr<EventBeat> {
    return std::make_unique<AndroidEventBeat>(
        std::move(ownerBox),
        eventBeatManager,
        *runtimeScheduler,
        globalJavaUiManager);
  };

  contextContainer->insert("FabricUIManager", globalJavaUiManager);

  // 组装 Scheduler 工具箱
  auto toolbox = SchedulerToolbox{};
  toolbox.contextContainer = contextContainer;
  toolbox.componentRegistryFactory = componentsRegistry->buildRegistryFunction;

  // TODO: (T132338609) runtimeExecutor 应该在主 bundle eval 之后执行 lambda 表达式,
  // 而 bindingsInstallExecutor 应该在之前执行。
  toolbox.bridgelessBindingsExecutor = std::nullopt;
  toolbox.runtimeExecutor = runtimeExecutor;

  toolbox.eventBeatFactory = eventBeatFactory;

  // 启动 Fabric 核心
  animationDriver_ = std::make_shared<LayoutAnimationDriver>(
      runtimeExecutor, contextContainer, this);
  scheduler_ =
      std::make_shared<Scheduler>(toolbox, animationDriver_.get(), this);
}

这个方法是一个 "组装车间":

  1. 它接收来自 Android (Java) 的原材料(UIManager, EventBeatManager)。

  2. 它接收来自 JS Runtime 的驱动器(RuntimeExecutor)。

  3. 它将这些零件组装成 C++ 的核心部件(MountingManager, AndroidEventBeat)。

  4. 最后,它启动了 Fabric 的引擎 —— Scheduler

总结

下面是对整个初始化流程的概述

ReactInstance 初始化
           │
           │
           └─► Fabric 初始化
                    │
                    ├─► ViewManagerResolver 创建 (收集 ReactPackage 中的 ViewManager)
                    │
                    ├─► ViewManagerRegistry 创建
                    │
                    ├─► FabricUIManager 创建
                    │
                    ├─► FabricUIManagerBinding 创建
                    │
                    └─► binding.register() ──► 触发 C++ 层初始化

启动渲染

回顾一下Android 的初始化流程一文:

// ReactInstance.kt


 /**
   * 渲染一个 React Native surface.
   *
   * @param surface 要渲染的 [com.facebook.react.interfaces.fabric.ReactSurface] 对象
   */
  @ThreadConfined("ReactHost")
  fun startSurface(surface: ReactSurfaceImpl) {
    // 省略部分代码......
    val view = surface.view

    if (surface.isRunning) {
      // Surface 已经在运行(预渲染过),只需附加 View
      fabricUIManager.attachRootView(surface.surfaceHandler, view)
    } else {
      fabricUIManager.startSurface(surface.surfaceHandler, surface.context, view)
    }
  }

之前并未深入fabricUIManager.startSurface方法的调用,现在来分析一下:

// FabricUIManager.java

  public void startSurface(
      final SurfaceHandlerBinding surfaceHandler,
      final Context context,
      final @Nullable View rootView) {
    final int rootTag =
        rootView instanceof ReactRoot
            ? ((ReactRoot) rootView).getRootViewTag()
            : ReactRootViewTagGenerator.getNextRootViewTag();

    ThemedReactContext reactContext =
        new ThemedReactContext(
            mReactApplicationContext, context, surfaceHandler.getModuleName(), rootTag);
    mMountingManager.startSurface(rootTag, reactContext, rootView);
    Assertions.assertNotNull(mBinding, "Binding in FabricUIManager is null");
    mBinding.startSurfaceWithSurfaceHandler(rootTag, surfaceHandler, rootView != null);
  }

此方法主要做了两件事,首先是调用MountingManagerstartSurface启动 Surface,接着调用了FabricUIManagerBinding的Native方法startSurfaceWithSurfaceHandler在C++层启动 Surface。先查看react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt

  /**
    * 启动 Surface 但不附加视图。对该 Surface 执行的所有视图操作都将被排队,直到视图被附加为止。
    */
  @AnyThread
  fun startSurface(
      surfaceId: Int,
      reactContext: ThemedReactContext?,
      rootView: View?,
  ): SurfaceMountingManager {
    // 创建一个新的 SurfaceMountingManager 实例,负责管理特定 Surface 的视图挂载操作
    val surfaceMountingManager =
        SurfaceMountingManager(
            surfaceId,
            jsResponderHandler,  // JS 响应处理器(处理触摸事件响应)
            viewManagerRegistry,
            rootViewManager,
            mountItemExecutor,   // 挂载项执行器
            checkNotNull(reactContext),
        )

    // 理论上这里可能存在竞态条件,如果 addRootView 从不同线程被调用两次,
    // 虽然这种情况(可能)极不可能发生,而且很可能是一个错误。
    // 这个防止竞态条件的逻辑是从旧代码继承而来的,我们不知道在实际中是否真的会发生
    // 所以,我们现在记录软异常。这在调试模式下会崩溃,但在生产环境中不会。
    surfaceIdToManager.putIfAbsent(surfaceId, surfaceMountingManager)
    if (surfaceIdToManager[surfaceId] !== surfaceMountingManager) {
      logSoftException(
          TAG,
          IllegalStateException(
              "Called startSurface more than once for the SurfaceId [$surfaceId]"
          ),
      )
    }

    mostRecentSurfaceMountingManager = surfaceIdToManager[surfaceId]

    if (rootView != null) {
      surfaceMountingManager.attachRootView(rootView, reactContext)
    }

    return surfaceMountingManager
  }

此方法主要内容是创建SurfaceMountingManager,然后调用attachRootView方法。现在继续跟踪attachRootView方法,源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java

  public void attachRootView(View rootView, ThemedReactContext themedReactContext) {
    mThemedReactContext = themedReactContext;
    addRootView(rootView);
  }

  private void addRootView(@NonNull final View rootView) {
    if (isStopped()) {
      return;  // 检查 Surface 是否已停止
    }

    mTagToViewState.put(mSurfaceId, new ViewState(mSurfaceId, rootView, mRootViewManager, true));

    // 在 UI 线程上执行根视图设置
    Runnable runnable =
        new GuardedRunnable(Assertions.assertNotNull(mThemedReactContext)) {
          @Override
          public void runGuarded() {
            // 自从调用`addRootView`以来,CPU 一直在运行,因此从理论上讲,界面可能已经在此处停止渲染了。
            if (isStopped()) {
              return;
            }
            // 省略部分日志打印......

            // 设置根视图 ID 
            rootView.setId(mSurfaceId);

            if (rootView instanceof ReactRoot) {
              ((ReactRoot) rootView).setRootViewTag(mSurfaceId);
            }

            executeMountItemsOnViewAttach();

            // 通过在调用 `executeMountItemsOnViewAttach` 之后执行此操作,
            // 我们可以确保在处理此队列时安排的任何操作也会被添加到队列中,
            // 而不是通过 `MountItemDispatcher` 中的队列立即进行处理。
            mRootViewAttached = true;
          }
        };

    // 确保在 UI 线程执行
    if (UiThreadUtil.isOnUiThread()) {
      runnable.run();
    } else {
      UiThreadUtil.runOnUiThread(runnable);
    }
  }

这里的实现核心是封装了一个Runnable,即一个任务,且这个任务必须在安卓的UI线程执行。继续跟踪executeMountItemsOnViewAttach方法,查看任务的内容:

  private final Queue<MountItem> mOnViewAttachMountItems = new ArrayDeque<>();


  @UiThread
  @ThreadConfined(UI)
  private void executeMountItemsOnViewAttach() {
    mMountItemExecutor.executeItems(mOnViewAttachMountItems);
  }

可以看到,该方法就是在调用挂载项执行器不断的执行挂载项队列。这里的挂载项执行器是在创建MountingManager时传入的,回到FabricUIManager源码查看实现:

  private final MountingManager.MountItemExecutor mMountItemExecutor =
      new MountingManager.MountItemExecutor() {
        @Override
        public void executeItems(Queue<MountItem> items) {
          // 从技术上讲,在调度程序创建之前就可以访问这个执行器,但如果真的发生这种情况,那就说明出了非常严重的问题。
          mMountItemDispatcher.dispatchMountItems(items);
        }
      };

继续跟踪react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt中的实现:

  @UiThread
  @ThreadConfined(UI)
  fun dispatchMountItems(mountItems: Queue<MountItem?>) {
    while (!mountItems.isEmpty()) {
      val item = requireNotNull(mountItems.poll()) { "MountItem should not be null" }
      try {
        item.execute(mountingManager)
      } catch (e: RetryableMountingLayerException) {
        // 省略已弃用的逻辑......
      }
    }
  }

此处核心逻辑是从队列取出一个 MountItem并执行它的execute方法。至于MountItem接口,它有许多实现类,其中最核心的是IntBufferBatchMountItem实现,我们可以大致浏览一下它的execute方法主要做些什么。源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.kt

  override fun execute(mountingManager: MountingManager) {
    val surfaceMountingManager = mountingManager.getSurfaceManager(surfaceId)
    if (surfaceMountingManager == null) {
      return
    }
    if (surfaceMountingManager.isStopped) {
      return
    }
    if (ReactNativeFeatureFlags.enableFabricLogs()) {
      FLog.d(TAG, "Executing IntBufferBatchMountItem on surface [%d]", surfaceId)
    }

    beginMarkers("mountViews")
    var i = 0
    var j = 0
    while (i < intBufferLen) {
      val rawType = intBuffer[i++]
      val type = rawType and INSTRUCTION_FLAG_MULTIPLE.inv()
      val numInstructions =
          (if ((rawType and INSTRUCTION_FLAG_MULTIPLE) != 0) intBuffer[i++] else 1)

      val args = arrayOf("numInstructions", numInstructions.toString())

      for (k in 0 until numInstructions) {
        when (type) {
          INSTRUCTION_CREATE -> {    
            val componentName = (objBuffer[j++] as String?).orEmpty()
            val fabricComponentName =
                FabricNameComponentMapping.getFabricComponentName(componentName)
            // 创建视图
            surfaceMountingManager.createView(
                fabricComponentName,
                intBuffer[i++],
                objBuffer[j++] as ReadableMap?,
                objBuffer[j++] as StateWrapper?,
                objBuffer[j++] as EventEmitterWrapper?,
                intBuffer[i++] == 1,
            )
          }
          // 删除视图
          INSTRUCTION_DELETE -> surfaceMountingManager.deleteView(intBuffer[i++]) 
          INSTRUCTION_INSERT -> {
            val tag = intBuffer[i++]
            val parentTag = intBuffer[i++]
            // 插入视图到父视图
            surfaceMountingManager.addViewAt(parentTag, tag, intBuffer[i++])
          }
          // 从父视图移除
          INSTRUCTION_REMOVE ->
              surfaceMountingManager.removeViewAt(intBuffer[i++], intBuffer[i++], intBuffer[i++])
           // 更新属性
          INSTRUCTION_UPDATE_PROPS ->
              surfaceMountingManager.updateProps(intBuffer[i++], objBuffer[j++] as ReadableMap?)
          // 更新状态
          INSTRUCTION_UPDATE_STATE ->
              surfaceMountingManager.updateState(intBuffer[i++], objBuffer[j++] as StateWrapper?)

          // 更新布局
          INSTRUCTION_UPDATE_LAYOUT -> {
            val reactTag = intBuffer[i++]
            val parentTag = intBuffer[i++]
            val x = intBuffer[i++]
            val y = intBuffer[i++]
            val width = intBuffer[i++]
            val height = intBuffer[i++]
            val displayType = intBuffer[i++]
            val layoutDirection = intBuffer[i++]
            surfaceMountingManager.updateLayout(
                reactTag,
                parentTag,
                x,
                y,
                width,
                height,
                displayType,
                layoutDirection,
            )
          }
          // 省略部分代码......
          else -> {
            throw IllegalArgumentException(
                "Invalid type argument to IntBufferBatchMountItem: $type at index: $i"
            )
          }
        }
      }
    }
    endMarkers()
  }

可以看到,IntBufferBatchMountItem包含批量视图操作指令,它使用 int 数组和 object 数组优化性能,减少 JNI 调用,并且支持多种视图操作:创建、删除、插入、移除、更新属性/状态/布局等。

接下来我们跟踪一下surfaceMountingManager.createView的实现,源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java

 @UiThread
  public void createView(
      @NonNull String componentName,
      int reactTag,
      @Nullable ReadableMap props,
      @Nullable StateWrapper stateWrapper,
      @Nullable EventEmitterWrapper eventEmitterWrapper,
      boolean isLayoutable) {
    if (isStopped()) {
      return;
    }
    ViewState viewState = getNullableViewState(reactTag);
    if (viewState != null && viewState.mView != null) {
      return;
    }

    createViewUnsafe(
        componentName, reactTag, props, stateWrapper, eventEmitterWrapper, isLayoutable);
  }

  /**
   * 执行视图创建操作,但不进行任何安全检查。您必须在调用此方法之前确保安全性(参见现有调用位置)
   */
  @UiThread
public void createViewUnsafe(
      @NonNull String componentName,
      int reactTag,
      @Nullable ReadableMap props,
      @Nullable StateWrapper stateWrapper,
      @Nullable EventEmitterWrapper eventEmitterWrapper,
      boolean isLayoutable) {
    Systrace.beginSection(
        Systrace.TRACE_TAG_REACT,
        "SurfaceMountingManager::createViewUnsafe(" + componentName + ")");
    try {
      ReactStylesDiffMap propMap = new ReactStylesDiffMap(props);

      ViewState viewState = new ViewState(reactTag);
      viewState.mCurrentProps = propMap;
      viewState.mStateWrapper = stateWrapper;
      viewState.mEventEmitter = eventEmitterWrapper;
      mTagToViewState.put(reactTag, viewState);

      if (isLayoutable) {
        ViewManager viewManager = mViewManagerRegistry.get(componentName);
        // View Managers are responsible for dealing with inital state and props.
        viewState.mView =
            viewManager.createView(
                reactTag, mThemedReactContext, propMap, stateWrapper, mJSResponderHandler);
        viewState.mViewManager = viewManager;
      }
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT);
    }
  }

这里是根据组件名通过mViewManagerRegistry来查找ViewManager。具体注册逻辑,我们在后面的注册组件一节分析。

继续跟踪一下viewManager.createView方法的实现,源码react-native/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java

  /** 创建一个了解 props 和 state 的视图。 */
  public @NonNull T createView(
      int reactTag,
      @NonNull ThemedReactContext reactContext,
      @Nullable ReactStylesDiffMap props,
      @Nullable StateWrapper stateWrapper,
      JSResponderHandler jsResponderHandler) {
    T view = createViewInstance(reactTag, reactContext, props, stateWrapper);
    if (view instanceof ReactInterceptingViewGroup) {
      ((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
    }
    return view;
  }

  /**
   * 子类应该返回一个适当类型的新 View 实例。这是一个可选方法,它会为您调用 createViewInstance。
   * 如果您需要在创建视图时使用 props 或 state,请重写它。
   *
   * 如果您重写此方法,您*必须*确保正确处理 updateProperties、view.setId、
   * addEventEmitters 和 updateState/updateExtraData!
   *
   * @param reactTag 应该设置为视图实例 ID 的 reactTag
   * @param reactContext 用于初始化视图实例的 ReactContext
   * @param initialProps 视图实例的初始 props
   * @param stateWrapper 视图实例的初始 state
   */
  protected @NonNull T createViewInstance(
      int reactTag,
      @NonNull ThemedReactContext reactContext,
      @Nullable ReactStylesDiffMap initialProps,
      @Nullable StateWrapper stateWrapper) {
    T view = null;
    @Nullable Stack<T> recyclableViews = getRecyclableViewStack(reactContext.getSurfaceId(), true);
    if (recyclableViews != null && !recyclableViews.empty()) {
      T recyclableView = recyclableViews.pop();

      // 当视图回收未对所有组件启用时,可回收视图仍可能附加到不可回收视图。
      // 这保证了回收视图已从其父视图中移除。
      if (ReactNativeFeatureFlags.enableViewRecycling() && recyclableView.getParent() != null) {
        ((ViewGroup) recyclableView.getParent()).removeView(recyclableView);
      }

      view = recycleView(reactContext, recyclableView);
    } else {
      view = createViewInstance(reactContext);
    }
    view.setId(reactTag);
    addEventEmitters(reactContext, view);
    if (initialProps != null) {
      updateProperties(view, initialProps);
    }
    // 仅在 Fabric 架构中存在;但在 Fabric 中始终存在。
    if (stateWrapper != null) {
      Object extraData = updateState(view, initialProps, stateWrapper);
      if (extraData != null) {
        updateExtraData(view, extraData);
      }
    }
    return view;
  }

 /**
   * 子类应该返回一个适当类型的新 View 实例。
   */
  protected abstract @NonNull T createViewInstance(@NonNull ThemedReactContext reactContext);

   /**
   * 子类可以重写此方法以在给定 View 上安装自定义事件发射器。
   * 如果您的视图需要向 JS 发送除基本触摸事件之外的事件(例如滚动事件),
   * 您可能想要重写此方法。
   */
  protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull T view) {}

 /**
   * 子类可以实现此方法以接收从 {@link ReactShadowNode#onCollectExtraUpdates} 中
   * 相应 {@link ReactShadowNode} 实例排队的可选额外数据。
   *
   * 由于 CSS 布局步骤和 UI 更新可以在设置 x/y/width/height 之外的单独线程中执行,
   * 这是从 CSS 节点向原生视图对应部分传递额外数据的推荐且线程安全的方式。
   *
   * <p>TODO T7247021: Replace updateExtraData with generic update props mechanism after D2086999
   */
  public abstract void updateExtraData(@NonNull T view, Object extraData);

 /**
   * 子类可以实现此方法以接收在此组件类型的所有实例之间共享的状态更新。
   */
  public @Nullable Object updateState(
      @NonNull T view, ReactStylesDiffMap props, StateWrapper stateWrapper) {
    return null;
  }

这里比较重要的是createViewInstance方法,子类必须重写它,用于创建自定义View。现在查看一下我们创建的自定义Fabric 组件包中自动生成的模版代码:

@ReactModule(name = CustomWebViewManager.NAME)
class CustomWebViewManager : SimpleViewManager<CustomWebView>(),
  CustomWebViewManagerInterface<CustomWebView> {
  private val mDelegate: ViewManagerDelegate<CustomWebView>

  init {
    mDelegate = CustomWebViewManagerDelegate(this)
  }

  override fun getDelegate(): ViewManagerDelegate<CustomWebView>? {
    return mDelegate
  }

  override fun getName(): String {
    return NAME
  }

  public override fun createViewInstance(context: ThemedReactContext): CustomWebView {
    return CustomWebView(context)
  }

  @ReactProp(name = "sourceURL")
  override fun setSourceURL(view: CustomWebView?, sourceURL: String?) {
    // Implement the logic to handle the sourceURL property
  }

  companion object {
    const val NAME = "CustomWebView"
  }
}

可以看到,CustomWebViewManager实际上就是ViewManager的子孙类,其实现了createViewInstance方法,返回自定义的View实例。

这条线的跟踪已经足够深入了,关于原生UI组件的具体布局渲染就不继续了,先到此为止。

C++ 层

FabricUIManagerstartSurface方法中还有一个Native方法mBinding.startSurfaceWithSurfaceHandler未分析,源码react-native/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp

void FabricUIManagerBinding::startSurfaceWithSurfaceHandler(
    jint surfaceId,
    jni::alias_ref<SurfaceHandlerBinding::jhybridobject> surfaceHandlerBinding,
    jboolean isMountable) {

  // SurfaceHandler配置 
  // 从JNI包装中提取C++ SurfaceHandler对象
  const auto& surfaceHandler =
      surfaceHandlerBinding->cthis()->getSurfaceHandler();
  surfaceHandler.setSurfaceId(surfaceId);
  // 根据是否有View设置显示模式
  surfaceHandler.setDisplayMode(
      isMountable != 0 ? DisplayMode::Visible : DisplayMode::Suspended);

  // 获取Fabric调度器
  auto scheduler = getScheduler();
  if (!scheduler) {
    LOG(ERROR)
        << "FabricUIManagerBinding::startSurfaceWithSurfaceHandler: scheduler disappeared";
    return;
  }

  // 将SurfaceHandler注册到调度器中。调度器负责管理渲染和布局
  scheduler->registerSurface(surfaceHandler);

  auto mountingManager = getMountingManager("startSurfaceWithSurfaceHandler");
  if (mountingManager != nullptr) {
    // 通知MountingManager, Surface开始启动
    // MountingManager负责将C++的UI操作转换为Android原生View操作
    mountingManager->onSurfaceStart(surfaceId);
  }

  // 启动SurfaceHandler 
  surfaceHandler.start();

  // 如果启用布局动画,设置动画驱动器
  if (ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid()) {
    surfaceHandler.getMountingCoordinator()->setMountingOverrideDelegate(
        animationDriver_);
  }

  {
    std::unique_lock lock(surfaceHandlerRegistryMutex_);
    // 将SurfaceHandler添加到注册表中,便于后续管理(停止、更新等)
    surfaceHandlerRegistry_.emplace(
        surfaceId, jni::make_weak(surfaceHandlerBinding));
  }
}

这个方法是新架构Surface启动的关键桥梁,它体现了新架构的核心特点:通过JSI直接从C++调用JS。

  • 向上:接收Java层的启动请求

  • 向下:触发C++层的Surface启动流程

  • 横向:协调Scheduler、MountingManager等各个子系统

这里重点关注一下surfaceHandler.start()的实现,源码react-native/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp:

void SurfaceHandler::start() const noexcept {
  std::unique_lock lock(linkMutex_);
  // 省略断言......

  auto parameters = Parameters{};
  {
    std::shared_lock parametersLock(parametersMutex_);
    parameters = parameters_;
  }

  // 创建ShadowTree 
  auto shadowTree = std::make_unique<ShadowTree>(
      parameters.surfaceId,
      parameters.layoutConstraints,
      parameters.layoutContext,
      *link_.uiManager,
      *parameters.contextContainer);

  // 将ShadowTree指针保存到link_中,供后续操作使用
  link_.shadowTree = shadowTree.get();

  if (!parameters.moduleName.empty()) {
    // 启动Surface
    link_.uiManager->startSurface(
        std::move(shadowTree),
        parameters.moduleName,
        parameters.props,
        parameters_.displayMode);
  } else {
    // 创建空Surface,仅用于预渲染
    link_.uiManager->startEmptySurface(std::move(shadowTree));
  }
  // 将状态从Registered更新为Running
  link_.status = Status::Running;
  // 应用显示模式
  applyDisplayMode(parameters.displayMode);
}

此方法是Fabric架构中渲染流程的起点,负责创建ShadowTree并启动UI渲染流程。这里的ShadowTree就是Fabric中的虚拟DOM树,用于布局计算和渲染。

继续跟踪uiManager->startSurface的实现,源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp:

void UIManager::startSurface(
    ShadowTree::Unique&& shadowTree,
    const std::string& moduleName,
    const folly::dynamic& props,
    DisplayMode displayMode) const noexcept {
  TraceSection s("UIManager::startSurface");

  // ShadowTree注册 
  auto surfaceId = shadowTree->getSurfaceId();
  shadowTreeRegistry_.add(std::move(shadowTree));

  // 委托通知
  // 使用访问者模式安全访问已注册的ShadowTree
  shadowTreeRegistry_.visit(
      surfaceId, [delegate = delegate_](const ShadowTree& shadowTree) {
        if (delegate != nullptr) {
          // 通知UIManager的委托对象Surface已启动
          delegate->uiManagerDidStartSurface(shadowTree);
        }
      });

  // 异步调用JS层
  runtimeExecutor_([=](jsi::Runtime& runtime) {
    TraceSection s("UIManager::startSurface::onRuntime");
    // 在JS线程上异步执行
    AppRegistryBinding::startSurface(
        runtime, surfaceId, moduleName, props, displayMode);
  });
}

此方法是Fabric架构中连接C++渲染系统和JS应用层的关键桥梁方法,负责注册ShadowTree并触发JS应用启动。

继续跟踪AppRegistryBinding::startSurface。源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/AppRegistryBinding.cpp:

/* static */ void AppRegistryBinding::startSurface(
    jsi::Runtime& runtime,
    SurfaceId surfaceId,
    const std::string& moduleName,
    const folly::dynamic& initialProps,
    DisplayMode displayMode) {
  TraceSection s("AppRegistryBinding::startSurface");

  // 构建参数对象
  jsi::Object parameters(runtime);
  parameters.setProperty(runtime, "rootTag", surfaceId);
  parameters.setProperty(
      runtime, "initialProps", jsi::valueFromDynamic(runtime, initialProps));
  parameters.setProperty(runtime, "fabric", true);

  // 获取全局AppRegistry 
  // 访问JS全局对象
  auto global = runtime.global();
  // 查找RN$AppRegistry(在AppRegistry.js中设置)
  auto registry = global.getProperty(runtime, "RN$AppRegistry");
  if (!registry.isObject()) {
    throw std::runtime_error(
        "AppRegistryBinding::startSurface failed. Global was not installed.");
  }

  // 获取runApplication方法对象
  auto method = std::move(registry).asObject(runtime).getPropertyAsFunction(
      runtime, "runApplication");

  // 调用JS方法:runApplication
  method.call(
      runtime,
      {jsi::String::createFromUtf8(runtime, moduleName),
       std::move(parameters),
       jsi::Value(runtime, displayModeToInt(displayMode))});
}

这里的重点是就是JS层的runApplication方法调用,可以说这就是JS层的入口方法。此处的JSI C++调用等价于以下JS代码:

AppRegistry.runApplication(
  "RNTesterApp",           // moduleName
  {
    rootTag: 1,            // surfaceId
    initialProps: {...},   // 初始属性
    fabric: true           // 新架构标记
  },
  0                        // displayMode
);

AppRegistryBinding::startSurface方法是新架构调用链的终点,负责通过JSI直接调用JS层的AppRegistry.runApplication,启动React Native应用。

渲染调度

接下来我们探索一下前面提到的mOnViewAttachMountItems队列:

// SurfaceMountingManager.java

  @UiThread
  @ThreadConfined(UI)
  public void scheduleMountItemOnViewAttach(MountItem item) {
    mOnViewAttachMountItems.add(item);
  }

经过搜索,可以定位到scheduleMountItemOnViewAttach调用处:

// MountItemDispatcher.kt

  private fun getAndResetViewCommandMountItems(): List<DispatchCommandMountItem>? =
      drainConcurrentItemQueue(viewCommandMountItems)

  private fun getAndResetMountItems(): List<MountItem>? = drainConcurrentItemQueue(mountItems)

  private fun getAndResetPreMountItems(): List<MountItem>? = drainConcurrentItemQueue(preMountItems)

  private fun executeOrEnqueue(item: MountItem) {
    if (mountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
      // Surface 还未准备好,将任务加入等待队列
      val surfaceMountingManager: SurfaceMountingManager =
          mountingManager.getSurfaceManagerEnforced(
              item.getSurfaceId(),
              "MountItemDispatcher::executeOrEnqueue",
          )
      surfaceMountingManager.scheduleMountItemOnViewAttach(item)
    } else {
      // Surface 已准备好,直接执行
      item.execute(mountingManager)
    }
  }


  /*
   * 按以下顺序执行视图命令、预挂载项和挂载项:
   * 1. 视图命令
   * 2. 预挂载项  
   * 3. 常规挂载项
   *
   * 如果 `viewCommandMountItemsToDispatch` 和 `mountItemsToDispatch` 都为空,则不执行任何操作。
   * 除了 `tryDispatchMountItems` 之外,任何地方都不应直接调用此方法。
   */
  @UiThread
  @ThreadConfined(UI)
  private fun dispatchMountItems() {
    batchedExecutionTime = 0
    runStartTime = SystemClock.uptimeMillis()
    // 初始化和获取待分发项
    val viewCommandMountItemsToDispatch = getAndResetViewCommandMountItems()
    val mountItemsToDispatch = getAndResetMountItems()

    if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
      return
    }

    itemDispatchListener.willMountItems(mountItemsToDispatch)

    // 定义视图命令分发函数
    val dispatchViewCommand: (command: DispatchCommandMountItem) -> Unit = { command ->
      if (ReactNativeFeatureFlags.enableFabricLogs()) {
        printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem")
      }
      try {
        executeOrEnqueue(command)
      } catch (e: RetryableMountingLayerException) {
        // 省略......
      } catch (e: Throwable) {
        // 省略......
      }
    }

    // 执行ViewCommand

    // 作为优化,首先执行所有 ViewCommand
    // 这应该是:
    // 1) 高性能的:ViewCommand 通常是 SetNativeProps 的替代品,我们一直希望它尽可能"同步"。
    // 2) 更安全的:ViewCommand 本质上与树提交/差异/挂载过程断开连接。
    // JS 令式地排队这些命令。
    //    如果 JS 已经排队了一个命令,可以合理地假设时间过得越久, 视图消失的可能性就越大。
    //    因此,通过提前执行 ViewCommand,我们实际上应该避免一类错误/故障。
    viewCommandMountItemsToDispatch?.let { commands ->
      for (command in commands) {
        dispatchViewCommand(command)
      }

      Systrace.endSection(Systrace.TRACE_TAG_REACT)
    }

    // 执行PreMountItem
    // 如果有 MountItem 要分发,我们确保所有"预挂载项"首先执行
    getAndResetPreMountItems()?.let { preMountItems ->
      for (preMountItem in preMountItems) {
        if (ReactNativeFeatureFlags.enableFabricLogs()) {
          printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem")
        }
        executeOrEnqueue(preMountItem)
      }
      Systrace.endSection(Systrace.TRACE_TAG_REACT)
    }

    // 执行常规 MountItem 
    mountItemsToDispatch?.let { items ->
      val batchedExecutionStartTime = SystemClock.uptimeMillis()

      for (mountItem in items) {
        if (ReactNativeFeatureFlags.enableFabricLogs()) {
          printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
        }

        val command = mountItem as? DispatchCommandMountItem
        if (command != null) {
          dispatchViewCommand(command)
          continue
        }

        try {
          executeOrEnqueue(mountItem)
        } catch (e: Throwable) {
          // 省略......
        }
      }
      batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
    }

    itemDispatchListener.didMountItems(mountItemsToDispatch)
  }

 /**
  * 尝试分发 MountItems。如果出现异常,我们将重试 10 次后放弃。
  */
  fun tryDispatchMountItems() {
    // 如果我们已经在分发中,不要重入。在 Android 的 Fabric 中可能经常发生重入,
    // 因为来自挂载层的 `updateState` 会导致挂载项被同步分发。我们想要 
    //    1)确保在这些情况下不重入,但 2)仍然同步执行这些排队的指令。
    // 这是一个相当粗暴的工具,但我们可能没有更好的选择,因为我们真的不想执行任何乱序的操作。
    if (inDispatch) {
      return
    }

    inDispatch = true

    try {
      dispatchMountItems()
    } finally {
      // 即使抛出异常也要在运行 dispatchMountItems 后清理
      inDispatch = false
    }

    // 无论我们是否实际分发了任何内容,我们都会调用 didDispatchMountItems,
    // 因为 NativeAnimatedModule 依赖于此来执行可能已调度的任何动画
    itemDispatchListener.didDispatchMountItems()
  }

可以看到,scheduleMountItemOnViewAttach仅被executeOrEnqueue方法调用。只有在Surface 还未准备好时,才将任务加入等待队列。否则,直接在executeOrEnqueue中执行挂载项。

这里executeOrEnqueue方法在dispatchMountItems方法中有三次调用,分别对应着三个队列。这三个队列存在执行顺序:

  1. ViewCommand 优先执行:提高性能和安全性(存储视图命令,如 scrollTo、focus等,这些是来自 JS 的命令式调用,与视图树的提交/差异/挂载过程分离)

  2. PreMountItem 次之:为后续挂载做准备(存储预挂载项,主要是视图预分配操作。这是性能优化,尽可能提前完成工作)

  3. 常规 MountItem 最后:执行实际的视图操作(存储常规挂载项,包含主要的视图操作。这是视图树更新的核心操作)

有个地方需要注意,就是drainConcurrentItemQueue方法。此方法是一次性清空队列,将结果转为一个List。之所以这样做,是为了将累积的所有挂载项一次性取出进行批处理,避免在执行过程中队列继续增长导致的不一致性。这里的三个队列都调用了此方法返回列表。

接下来,根据注释可知,dispatchMountItems方法只应该被tryDispatchMountItems调用。而tryDispatchMountItems方法主要做了一个防止重入的处理。继续搜索该方法,发现只有两处调用,且都在FabricUIManager.java中:

private class DispatchUIFrameCallback extends GuardedFrameCallback {
    private volatile boolean mIsMountingEnabled = true;
    @ThreadConfined(UI)
    private boolean mShouldSchedule = false;
    @ThreadConfined(UI)
    private boolean mIsScheduled = false;

    private DispatchUIFrameCallback(ReactContext reactContext) {
      super(reactContext);
    }

    @UiThread
    @ThreadConfined(UI)
    private void schedule() {
      if (!mIsScheduled && mShouldSchedule) {
        mIsScheduled = true;
        ReactChoreographer.getInstance()
            .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, this);
      }
    }
    // 省略部分代码......

    @Override
    @UiThread
    @ThreadConfined(UI)
    public void doFrameGuarded(long frameTimeNanos) {
      mIsScheduled = false;
      if (!mIsMountingEnabled) {
        return;
      }

      if (mDestroyed) {
        return;
      }

      // 通过 C++ 驱动所有动画。
      // 这里存在获取/设置 `mDriveCxxAnimations` 的竞态条件,但这应该无关紧要;
      // 调用 mBinding 方法是安全的,除非 mBinding 对象已被销毁。
      if ((mDriveCxxAnimations || ReactNativeFeatureFlags.cxxNativeAnimatedEnabled())
          && mBinding != null) {
        mBinding.driveCxxAnimations();
      }

      if (mBinding != null) {
        mBinding.drainPreallocateViewsQueue();
      }

      try {
        // 首先,在 frameTimeNanos 时间内执行尽可能多的预挂载项。
        // 如果不是所有预挂载项都被执行,可能发生以下情况:
        //   1. 如果 MountItemDispatcher 中有视图命令或挂载项:执行剩余的预挂载项。
        //   2. 如果没有视图命令或挂载项,等待下一帧。
        mMountItemDispatcher.dispatchPreMountItems(frameTimeNanos);
        mMountItemDispatcher.tryDispatchMountItems();
      } catch (Exception ex) {
        FLog.e(TAG, "Exception thrown when executing UIFrameGuarded", ex);
        mIsMountingEnabled = false;
        throw ex;
      } finally {
        schedule();
      }

      mSynchronousEvents.clear();
    }
  }

先看doFrameGuarded这处调用,这里正是一开始分析初始化流程时提到的基于 Android Choreographer帧回调处理。

  • 监听 VSync 信号

  • 在每帧开始时触发回调

  • 按优先级顺序执行回调队列

    1. mBinding.driveCxxAnimations()- C++ 动画

    2. mBinding.drainPreallocateViewsQueue()- C++ 预分配队列

    3. dispatchPreMountItems(frameTimeNanos)- Java 预挂载项

    4. tryDispatchMountItems() - 常规挂载项

doFrameGuarded这处调用就是正常的帧驱动渲染流程,确保 UI 更新与屏幕刷新同步。

我们再看另一处调用:

  /**
   * 此方法将 UI 操作直接添加到 UI 线程队列中。以使用 {@link ReactChoreographer.CallbackType} 来强制执行顺序。
   * 此方法应仅在提交新渲染树后调用。
   */
  @SuppressWarnings("unused")
  @AnyThread
  @ThreadConfined(ANY)
  private void scheduleMountItem(
      @Nullable final MountItem mountItem,
      int commitNumber,
      long commitStartTime,
      long diffStartTime,
      long diffEndTime,
      long layoutStartTime,
      long layoutEndTime,
      long finishTransactionStartTime,
      long finishTransactionEndTime,
      int affectedLayoutNodesCount) {
    // 当 Binding.cpp 在提交阶段调用 scheduleMountItems 时,它总是使用 BatchMountItem 调用。
    // 没有其他地方使用 BatchMountItem 调用此方法,并且 Binding.cpp 只使用 BatchMountItem 调用 scheduleMountItems。
    long scheduleMountItemStartTime = SystemClock.uptimeMillis();
    boolean isBatchMountItem = mountItem instanceof BatchMountItem;
    boolean shouldSchedule = false;

    // 判断是否为批量挂载项
    if (isBatchMountItem) {
      BatchMountItem batchMountItem = (BatchMountItem) mountItem;
      Assertions.assertNotNull(batchMountItem, "BatchMountItem is null");
      shouldSchedule = !batchMountItem.isBatchEmpty();
    } else {
      shouldSchedule = mountItem != null;
    }
    // 省略性能记录......

    // 通知监听器
    // 在同步渲染的情况下,这可能在 UI 线程上被调用。否则,
    // 它应该几乎总是在 JS 线程上被调用。
    for (UIManagerListener listener : mListeners) {
      listener.didScheduleMountItems(this);
    }

    // 调度执行
    if (shouldSchedule) {
      Assertions.assertNotNull(mountItem, "MountItem is null");
      // 将 MountItem 添加到分发器队列
      mMountItemDispatcher.addMountItem(mountItem);
      // 判断是否是UI线程,如果是UI线程则立即执行,实现低延迟渲染
      if (UiThreadUtil.isOnUiThread()) {
        Runnable runnable =
            new GuardedRunnable(mReactApplicationContext) {
              @Override
              public void runGuarded() {
                mMountItemDispatcher.tryDispatchMountItems();
              }
            };
        runnable.run();
      }
    }

    // 在锁外和同步挂载完成执行后发布标记
    if (isBatchMountItem) {
      // 省略部分代码......
    }
  }

可以看到,此方法是由C++层调用,在其中实现了双路径执行渲染策略。当前如果在UI线程调用,则会立即执行渲染。反之,只是通过mMountItemDispatcher.addMountItem(mountItem)将 MountItem 添加到队列,等待帧回调处理:

  // MountItemDispatcher.kt

  fun addMountItem(mountItem: MountItem) {
    mountItems.add(mountItem)
  }

C++ 层

现在来研究一下C++层触发scheduleMountItem的流程,源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

jsi::Value UIManagerBinding::get(
    jsi::Runtime& runtime,
    const jsi::PropNameID& name) {
  auto methodName = name.utf8(runtime);

  UIManager* uiManager = uiManager_.get();
  // 省略部分代码......

  if (methodName == "completeRoot") {
    auto paramCount = 2;
    return jsi::Function::createFromHostFunction(
        runtime,
        name,
        paramCount,
        [uiManager, methodName, paramCount](
            jsi::Runtime& runtime,
            const jsi::Value& /*thisValue*/,
            const jsi::Value* arguments,
            size_t count) -> jsi::Value {
          validateArgumentCount(runtime, methodName, paramCount, count);

          auto runtimeSchedulerBinding =
              RuntimeSchedulerBinding::getBinding(runtime);
          auto surfaceId = surfaceIdFromValue(runtime, arguments[0]);

          auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[1]);
          uiManager->completeSurface(
              surfaceId,
              shadowNodeList,
              {.enableStateReconciliation = true,
               .mountSynchronously = false,
               .source = ShadowTree::CommitSource::React});

          return jsi::Value::undefined();
        });
  }
  // 省略部分代码......

  return jsi::Value::undefined();
}

继续跟踪completeSurface方法实现,源码react-native/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp

void UIManager::completeSurface(
    SurfaceId surfaceId,
    const ShadowNode::UnsharedListOfShared& rootChildren,
    ShadowTree::CommitOptions commitOptions) {
  TraceSection s("UIManager::completeSurface", "surfaceId", surfaceId);

  shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
    auto result = shadowTree.commit(
        [&](const RootShadowNode& oldRootShadowNode) {
          return std::make_shared<RootShadowNode>(
              oldRootShadowNode,
              ShadowNodeFragment{
                  .props = ShadowNodeFragment::propsPlaceholder(),
                  .children = rootChildren,
              });
        },
        commitOptions);

    if (result == ShadowTree::CommitStatus::Succeeded) {
      // It's safe to update the visible revision of the shadow tree immediately
      // after we commit a specific one.
      lazyShadowTreeRevisionConsistencyManager_->updateCurrentRevision(
          surfaceId, shadowTree.getCurrentRevision().rootShadowNode);
    }
  });
}

这里的核心是ShadowTree::commit方法的调用,实参是个闭包。它主要用于计算 Diff,生成 MountingTransaction。继续跟踪源码react-native/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp

CommitStatus ShadowTree::commit(
    const ShadowTreeCommitTransaction& transaction,
    const CommitOptions& commitOptions) const {
  [[maybe_unused]] int attempts = 0;

  if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) {
    while (attempts < MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING) {
      auto status = tryCommit(transaction, commitOptions);
      if (status != CommitStatus::Failed) {
        return status;
      }
      attempts++;
    }

    {
      std::unique_lock lock(commitMutexRecursive_);
      return tryCommit(transaction, commitOptions);
    }
  } else {
    // 循环尝试提交,直到成功或达到最大尝试次数
    while (true) {
      attempts++;

      auto status = tryCommit(transaction, commitOptions);
      if (status != CommitStatus::Failed) {
        return status;
      }

      // After multiple attempts, we failed to commit the transaction.
      // Something internally went terribly wrong.
      react_native_assert(attempts < 1024);
    }
  }
}

CommitStatus ShadowTree::tryCommit(
    const ShadowTreeCommitTransaction& transaction,
    const CommitOptions& commitOptions) const {
  TraceSection s("ShadowTree::commit");

  auto telemetry = TransactionTelemetry{};
  telemetry.willCommit();

  CommitMode commitMode;
  auto oldRevision = ShadowTreeRevision{};
  auto newRevision = ShadowTreeRevision{};

  {
    // Reading `currentRevision_` in shared manner.
    SharedLock lock = sharedCommitLock();
    commitMode = commitMode_;
    oldRevision = currentRevision_;
  }

  const auto& oldRootShadowNode = oldRevision.rootShadowNode;

  // 1. 执行 transaction,生成新的 RootShadowNode
  auto newRootShadowNode = transaction(*oldRevision.rootShadowNode);

  if (!newRootShadowNode) {
    return CommitStatus::Cancelled;
  }

  // 2. 状态协调(State Reconciliation)
  if (commitOptions.enableStateReconciliation) {
    auto updatedNewRootShadowNode =
        progressState(*newRootShadowNode, *oldRootShadowNode);
    if (updatedNewRootShadowNode) {
      newRootShadowNode =
          std::static_pointer_cast<RootShadowNode>(updatedNewRootShadowNode);
    }
  }

  // 3. 调用 delegate 的 shadowTreeWillCommit hook
  newRootShadowNode = delegate_.shadowTreeWillCommit(
      *this, oldRootShadowNode, newRootShadowNode, commitOptions);

  if (!newRootShadowNode) {
    return CommitStatus::Cancelled;
  }

  // Layout nodes.
  std::vector<const LayoutableShadowNode*> affectedLayoutableNodes{};
  affectedLayoutableNodes.reserve(1024);

  telemetry.willLayout();
  telemetry.setAsThreadLocal();

  // 4. 布局计算
  newRootShadowNode->layoutIfNeeded(&affectedLayoutableNodes);
  telemetry.unsetAsThreadLocal();
  telemetry.didLayout(static_cast<int>(affectedLayoutableNodes.size()));

  {
    // Updating `currentRevision_` in unique manner if it hasn't changed.
    UniqueLock lock = uniqueCommitLock();

    if (currentRevision_.number != oldRevision.number) {
      return CommitStatus::Failed;
    }

    auto newRevisionNumber = currentRevision_.number + 1;

    {
      std::scoped_lock dispatchLock(EventEmitter::DispatchMutex());
      updateMountedFlag(
          currentRevision_.rootShadowNode->getChildren(),
          newRootShadowNode->getChildren(),
          commitOptions.source);
    }

    telemetry.didCommit();
    telemetry.setRevisionNumber(static_cast<int>(newRevisionNumber));

    // Seal the shadow node so it can no longer be mutated
    // Does nothing in release.
    newRootShadowNode->sealRecursive();

    newRevision = ShadowTreeRevision{
        .rootShadowNode = std::move(newRootShadowNode),
        .number = newRevisionNumber,
        .telemetry = telemetry};
    // 5. 更新 currentRevision_
    currentRevision_ = newRevision;
  }

  // 6. 发送布局事件
  emitLayoutEvents(affectedLayoutableNodes);

  // 7. 关键:如果 commitMode 是 Normal,调用 mount
  if (commitMode == CommitMode::Normal) {
    mount(std::move(newRevision), commitOptions.mountSynchronously);
  }

  return CommitStatus::Succeeded;
}


void ShadowTree::mount(ShadowTreeRevision revision, bool mountSynchronously)
    const {
  // 1. 将新的 revision 推送到 MountingCoordinator
  mountingCoordinator_->push(std::move(revision));
  // 2. 调用 delegate 的 shadowTreeDidFinishTransaction
  delegate_.shadowTreeDidFinishTransaction(
      mountingCoordinator_, mountSynchronously);
}

注意,ShadowTreeRevision 表示 Shadow Tree 的一个已提交快照(版本)。由三个组成部分:

  • rootShadowNode:整个 Shadow Tree 的根节点,包含完整的树结构和所有子节点

  • number:版本号,从 0(INITIAL_REVISION)开始递增,每次 commit 创建新 revision 时递增。主要用于检测并发冲突和版本追踪

  • telemetry:性能数据(commit 时间、layout 时间、影响的节点数等

MountingCoordinator 使用 revision 来计算新旧树之间的差异,生成需要执行的视图操作。

继续跟踪shadowTreeDidFinishTransaction的实现:

// UIManager.cpp

void UIManager::shadowTreeDidFinishTransaction(
    std::shared_ptr<const MountingCoordinator> mountingCoordinator,
    bool mountSynchronously) const {
  TraceSection s("UIManager::shadowTreeDidFinishTransaction");

  if (delegate_ != nullptr) {
    delegate_->uiManagerDidFinishTransaction(
        std::move(mountingCoordinator), mountSynchronously);
  }
}

继续跟踪uiManagerDidFinishTransaction实现,源码react-native/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp

void Scheduler::uiManagerDidFinishTransaction(
    std::shared_ptr<const MountingCoordinator> mountingCoordinator,
    bool mountSynchronously) {
  TraceSection s("Scheduler::uiManagerDidFinishTransaction");

  if (delegate_ != nullptr) {
    // 除了 Android 平台之外,此操作在所有平台上均无效,
    // 因为在 Android 平台上我们需要观察每个事务才能正确地进行挂载。
    delegate_->schedulerDidFinishTransaction(mountingCoordinator);

    if (!mountSynchronously) {
      auto surfaceId = mountingCoordinator->getSurfaceId();

      runtimeScheduler_->scheduleRenderingUpdate(
          surfaceId,
          [delegate = delegate_,
           mountingCoordinator = std::move(mountingCoordinator)]() {
            delegate->schedulerShouldRenderTransactions(mountingCoordinator);
          });
    } else {
      delegate_->schedulerShouldRenderTransactions(mountingCoordinator);
    }
  }
}

继续跟踪schedulerShouldRenderTransactions实现:

// FabricUIManagerBinding.cpp


void FabricUIManagerBinding::schedulerShouldRenderTransactions(
    const std::shared_ptr<const MountingCoordinator>& mountingCoordinator) {
  auto mountingManager =
      getMountingManager("schedulerShouldRenderTransactions");
  if (!mountingManager) {
    return;
  }
  if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
    auto mountingTransaction = mountingCoordinator->pullTransaction(
        /* willPerformAsynchronously = */ true);
    if (mountingTransaction.has_value()) {
      auto transaction = std::move(*mountingTransaction);
      mountingManager->executeMount(transaction);
    }
  } else {
    std::vector<MountingTransaction> pendingTransactions;

    {
      // 保留锁以访问待处理事务,但不要执行挂载操作,因为该方法可能会再次调用此方法。
      //
      // 当挂载管理器同步触发状态更新时(例如从 UI 线程提交时),此方法可能会被重入调用。
      // 这是安全的,因为我们已经将同一surface ID 的所有事务合并到待处理事务列表中的单个事务中,
      // 因此操作不会乱序执行。
      std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
      pendingTransactions_.swap(pendingTransactions);
    }

    for (auto& transaction : pendingTransactions) {
      mountingManager->executeMount(transaction);
    }
  }
}

到这里,最重要的方法是executeMount。该方法负责将 C++ 层的 Shadow Tree 变更(MountingTransaction)转换为 Java 层的视图操作指令,并调度到 UI 线程执行。

具体说,就是将 C++ 的 ShadowViewMutation 转换为 Java 的 MountItem,并序列化传递给 Java 层。该方法的实现非常长,这里仅摘要一部分。源码react-native/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp

void FabricMountingManager::executeMount(
    const MountingTransaction& transaction) {
   // 省略......

    // 遍历 mutations,将 ShadowViewMutation(Create/Delete/Insert/Remove/Update)转换为 CppMountItem
    for (const auto& mutation : mutations) {
      auto parentTag = mutation.parentTag;
      const auto& oldChildShadowView = mutation.oldChildShadowView;
      const auto& newChildShadowView = mutation.newChildShadowView;
      auto& mutationType = mutation.type;
      auto& index = mutation.index;

      bool isVirtual = mutation.mutatedViewIsVirtual();
      switch (mutationType) {
        case ShadowViewMutation::Create: {
          bool shouldCreateView =
              !allocatedViewTags.contains(newChildShadowView.tag);

          if (shouldCreateView) {
            cppCommonMountItems.push_back(
                CppMountItem::CreateMountItem(newChildShadowView));
            allocatedViewTags.insert(newChildShadowView.tag);
          }
          break;
        }
        case ShadowViewMutation::Remove: {
          if (!isVirtual) {
            cppCommonMountItems.push_back(
                CppMountItem::RemoveMountItem(
                    parentTag, oldChildShadowView, index));
          }
          break;
        }
        case ShadowViewMutation::Delete: {
          (maintainMutationOrder ? cppCommonMountItems : cppDeleteMountItems)
              .push_back(CppMountItem::DeleteMountItem(oldChildShadowView));
          if (allocatedViewTags.erase(oldChildShadowView.tag) != 1) {
            LOG(ERROR) << "Emitting delete for unallocated view "
                       << oldChildShadowView.tag;
          }
          break;
        }
        // 省略代码......

        default: {
          break;
        }
      }
    }
  }

  // 现在我们已经掌握了所有必要的信息,包括挂载项的顺序,因此可以准确地知道需要分配多少空间。
  auto [batchMountItemIntsSize, batchMountItemObjectsSize] = computeBufferSizes(
      cppCommonMountItems,
      cppDeleteMountItems,
      cppUpdatePropsMountItems,
      cppUpdateStateMountItems,
      cppUpdatePaddingMountItems,
      cppUpdateLayoutMountItems,
      cppUpdateOverflowInsetMountItems,
      cppUpdateEventEmitterMountItems);

  static auto scheduleMountItem = JFabricUIManager::javaClassStatic()
                                      ->getMethod<void(
                                          JMountItem::javaobject,
                                          jint,
                                          jlong,
                                          jlong,
                                          jlong,
                                          jlong,
                                          jlong,
                                          jlong,
                                          jlong,
                                          jint)>("scheduleMountItem");

  // 省略

  // 调用 JFabricUIManager.createIntBufferBatchMountItem() 创建 Java 对象
  // 将序列化数据传递给 Java 层
  static auto createMountItemsIntBufferBatchContainer =
      JFabricUIManager::javaClassStatic()
          ->getMethod<jni::alias_ref<JMountItem>(
              jint, jintArray, jni::jtypeArray<jobject>, jint)>(
              "createIntBufferBatchMountItem");
  auto batch = createMountItemsIntBufferBatchContainer(
      javaUIManager_,
      surfaceId,
      // If there are no items, we pass a nullptr instead of passing the
      // object through the JNI
      batchMountItemIntsSize > 0 ? buffer.ints : nullptr,
      batchMountItemObjectsSize > 0 ? buffer.objects.get() : nullptr,
      revisionNumber);

  auto finishTransactionEndTime = telemetryTimePointNow();

  // 调度到 UI 线程,将 BatchMountItem 发送到 Java 层
  scheduleMountItem(
      javaUIManager_,
      batch.get(),
      telemetry.getRevisionNumber(),
      telemetryTimePointToMilliseconds(telemetry.getCommitStartTime()),
      telemetryTimePointToMilliseconds(telemetry.getDiffStartTime()),
      telemetryTimePointToMilliseconds(telemetry.getDiffEndTime()),
      telemetryTimePointToMilliseconds(telemetry.getLayoutStartTime()),
      telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()),
      telemetryTimePointToMilliseconds(finishTransactionStartTime),
      telemetryTimePointToMilliseconds(finishTransactionEndTime),
      telemetry.getAffectedLayoutNodesCount());

  env->DeleteLocalRef(buffer.ints);
}

可以看到,在此方法中反射调用了上层的scheduleMountItem,与我们前面分析的结果对接上了。

总的来说,该方法充当 C++ Shadow Tree 与 Java 原生视图之间的桥梁,将 React 的声明式变更转换为 Android 视图操作指令,并调度到 UI 线程执行,实现从虚拟树到真实视图的映射。

由于单章字数限制,这里将本文拆成上下两篇,下篇《React Native之Android端Fabric 架构源码分析(下)》

《实时渲染》第2章-图形渲染管线-2.2应用程序阶段

实时渲染

2. 图形渲染管线

2.2 应用程序阶段

开发人员可以完全控制应用程序阶段发生的事情,因为它通常在CPU上执行。因此,开发人员可以完全决定其实现,然后对其进行修改以提高性能。此处的更改也会影响后续阶段的性能。例如,应用程序阶段算法或设置可以减少要渲染的三角形数量。

综上所述,一些应用程序工作可以由GPU执行,使用称为计算着色器的单独模式。此模式将GPU视为高度并行的通用处理器,忽略其专门用于渲染图形的特殊功能。

在应用程序阶段结束时,要渲染的几何图形被馈送到几何图形处理阶段。这些是渲染图元,即点、线和三角形,它们最终可能会出现在屏幕上(或正在使用的任何输出设备)。这是应用阶段最重要的任务。

该阶段基于软件实现的结果是它没有被划分为子阶段,几何处理、光栅化和像素处理阶段也是如此1。但是,为了提高性能,此阶段通常在多个处理器内核上并行执行。在CPU设计中,这被称为超标量构造,因为它能够在同一阶段同时执行多个进程。第18.5节介绍了使用多个处理器内核的各种方法。

在这个阶段通常实施的一个过程是碰撞检测。在检测到两个物体之间发生碰撞后,可以生成响应并将其发送回碰撞物体以及力反馈设备。应用程序阶段也是处理来自其他来源的输入的地方,例如键盘、鼠标或头戴式显示器。根据这个输入,可以采取几种不同的动作。加速算法,例如特定的剔除算法(第19章),也在这里实现,以及管道的其余部分无法处理的任何其他内容。

Footnotes

  1. 由于CPU本身的流水线规模要小得多,您可以说应用程序阶段被进一步细分为几个流水线阶段,但这在这里无关紧要。

《实时渲染》第2章-图形渲染管线-2.1架构

实时渲染

2. 图形渲染管线

链条的坚固程度取决于它最薄弱的环节。 --匿名者

本章介绍实时图形渲染的核心组件,即图形渲染管线,也简称为“管线”。管线的主要功能是通过给定虚拟相机、三维对象、光源等,生成或渲染二维图像。因此,渲染管线是实时渲染的基础工具。使用管线的过程如图2.1所示。图像中对象的位置和形状由它们的几何形状、环境特征以及相机在该环境中的位置决定。对象的外观受材料属性、光源、纹理(应用于表面的图像)和着色方程的影响。

转存失败,建议直接上传图片文件

图2.1. 在左图中,一个虚拟相机位于金字塔的顶端(四条线会聚的地方)。仅渲染视图体积内的图元。对于透视渲染的图像(如这里的情况),视图体积是一个平截头体(frustum,复数为frusta),即具有矩形底部的截棱锥。右图显示了相机“看到”的内容。请注意,左侧图像中的红色甜甜圈形状不在右侧的渲染中,因为它位于视锥体之外。此外,左图中扭曲的蓝色棱镜被剪裁在平截头体的顶平面上。

我们将解释渲染管线的不同阶段,重点是功能而不是实现。应用这些阶段的相关细节将在后面的章节中介绍。

2.1 架构

在现实世界中,管线的概念以许多不同的形式表现出来,从工厂装配线到快餐厨房。它也适用于图形渲染。管线由几个阶段组成[715],每个阶段执行一个更大任务的一部分。

每一个流水线阶段都是并行执行,但是其都依赖于前一阶段的结果。理想情况下,一个非流水线系统然后被分成n个流水线级,可以提供n倍的加速。这种性能的提高是使用流水线的主要原因。例如,可以由一系列人快速准备大量三明治——一个准备面包,另一个添加肉,另一个添加浇头。每个人都将结果传递给排队的下一个人,然后立即开始制作下一个三明治。如果每个人需要20秒来完成他们的任务,那么每20秒一个三明治的最大速度是可能的,每分钟三个。流水线阶段并行执行,但它们会暂停,直到最慢的阶段完成其任务。例如,假设肉类添加阶段变得更加复杂,需要30秒。现在可以达到的最佳速度是每分钟两个三明治。对于这个特定的管线,肉阶段是瓶颈,因为它决定了整个生产的速度。据说浇头阶段在等待肉类阶段完成期间饿死了(顾客也是如此)。

这种管线结构也可以在实时计算机图形的上下文中找到。实时渲染管线粗略划分为四个主要阶段——应用程序、几何处理、光栅化和像素处理——如图2.2所示。渲染管线引擎用于实时计算机图形应用程序,其核心正是这种结构;因此这种管线结构是后续章节讨论的重要基础。这些阶段中的每一个通常本身就是一个管道,这意味着它由几个子阶段组成。我们区分此处显示的功能阶段及其实现结构。一个功能阶段有一个特定的任务要执行,但没有指定任务在管道中的执行方式。一个给定的实现可以将两个功能阶段合并为一个单元或使用可编程内核执行,同时它将另一个更耗时的功能阶段划分为几个硬件单元。

转存失败,建议直接上传图片文件

图2.2. 渲染管线的基本结构,包括四个阶段:应用程序、几何处理、光栅化和像素处理。这些阶段中的每一个本身都可以是一个流水线,如几何处理阶段下方所示,或者一个阶段可以(部分)并行化,如像素处理阶段下方所示。在这个例子中,应用程序阶段是一个单一的进程,但这个阶段也可以是流水线或并行的。请注意,光栅化阶段会查找图元内部的像素,例如三角形。

渲染速度可以用每秒帧数(FPS)来表示,即每秒渲染的图像数量。也可以用赫兹(Hz)来表示,它只是1/秒的表示法,即更新频率。仅说明渲染图像所需的时间(以毫秒(ms)为单位)也很常见。生成图像的时间通常会有所不同,具体取决于每帧期间执行的计算的复杂性。每秒帧数用于表示特定帧的速率或一段时间内的平均性能。赫兹用于硬件,例如设置为固定速率的显示器。

顾名思义,应用程序阶段由应用程序驱动,因此通常在通用CPU上运行的软件中实现。这些CPU通常包括能够并行处理多个执行线程的多个内核。这使CPU能够有效地运行由应用程序阶段负责的各种任务。一些传统上在CPU上执行的任务包括碰撞检测、全局加速算法、动画、物理模拟等,具体取决于应用程序的类型。下一个主要阶段是几何处理,它处理变换、投影和所有其他类型的几何处理。此阶段计算要绘制的内容、应如何绘制以及应在何处绘制。几何阶段通常在包含许多可编程内核和固定操作硬件的图形处理单元(GPU)上执行。光栅化阶段通常将三个顶点作为输入,形成一个三角形,并找到该三角形内的所有像素,然后将这些像素转发到下一个阶段。最后,像素处理阶段对每个像素执行一个程序以确定其颜色,并可能执行深度测试以查看它是否可见。它还可以执行逐像素操作,例如将新计算的颜色与先前的颜色混合。光栅化和像素处理阶段也完全在GPU上处理。所有这些阶段及其内部管道将在接下来的四节中讨论。有关GPU如何处理这些阶段的更多详细信息,请参见第3章

荣耀Magic8 RSR体验:7999元起,除了保时捷设计,还能买到什么?

在 Magic8 Pro 发布三个月之后,荣耀终于补全了这一代旗舰家族的最后一块拼图——荣耀Magic8 RSR保时捷设计。

这是荣耀和保时捷设计合作的第三代 Magic 机型,它不仅承担着「顶配版本」的角色,也被赋予了更明确的设计使命:在旗舰手机规格之上,进一步在外观形态中,去还原一台保时捷跑车所代表的速度感、比例美学与精密工程感。

事实上,相比前两代,荣耀Magic8 RSR保时捷设计的视觉、气韵,都要更接近一台现代化的保时捷 911 跑车。

「流光飞线」设计,复刻 911 的灵魂线条

拿起荣耀Magic8 RSR保时捷设计(下文简称为荣耀Magic8 RSR)的第一刻,我最直观的感受是「温润」与「锋利」的共存。

荣耀Magic8 RSR 机身背部最醒目的元素,是那条贯穿上下的流光飞线。

它并非单纯的装饰线条,更是源自保时捷空气动力学的核心设计语言,从车头延伸至车尾,用一条连续而克制的曲线,勾勒出高速运动中的力量与秩序。在 911 的设计草图中,抑或风洞测试中可视化的气流路径里,这条飞线都反复出现,成为保时捷极具辨识度的设计符号。

甚至在荣耀与保时捷设计达成合作时的最初草图上,我们都能看到「流光飞线」的存在。

只不过落到荣耀Magic8 RSR 的机身上,这条飞线并非完全平面化处理,而是结合了微微隆起的气动曲面。

它一方面强化了背部的光影层次和视觉张力,宛如 911 车型硕大的后轮宽体曲线,让它天然自带凌厉的速度感;另一方面也让机身在握持时更贴合掌心,带来更符合人机工学的舒适手感。

当然,这种温润的感受,很大一部分是来自于机身背部所采用的超微晶纳米陶瓷材质。

有别于玻璃常见的冷硬触感,荣耀Magic8 RSR 的陶瓷背板会令人感到堪比玉石般的细腻与平滑,经过高温烧结与纳米抛光之后,背板的背面触感会更加致密,在光影之下也会有更加柔和的光感。

更重要的是,陶瓷本身具备更高的硬度,在日常使用时也不易产生细小刮痕,也更不容易留下指纹或油渍,能够让机身长时间保持如一的状态。

也正是在这里,荣耀Magic8 RSR 完成了一次很有意思的平衡:在复刻保时捷「速度」这一具象符号的同时,又通过材质与曲面的处理,把设计重新拉回到与手掌持续接触的那份温润之中。

在机身配色方面,我们手中的荣耀Magic8 RSR 为「岩板灰」配色,它源自斯图加特的保时捷经典车漆体系,也是备受保时捷车主群体青睐的代表性配色。

灰色本就自带克制与沉稳的气质,而岩板灰并未停留在单一的色调表达上,而是通过低饱和度与细腻的明暗层次,呈现出一种介于金属与石材之间的独特质感。

光线掠过陶瓷背板时,颜色会随角度产生微妙变化,不张扬,却始终保有存在感,这也正契合了保时捷设计一贯强调的内敛与精准。

除了岩板灰,荣耀Magic8 RSR 还提供了灵感源自保时捷稀有定制车漆的月光色版本,主打随光流转的层次变化:时而洁白如霜,时而深紫似夜,时而粉红若霞——这完全取决于天气和环境光线的变化。

在「流光飞线」之上,是荣耀Magic8 RSR那极具辨识度的镜头模组。

不同于荣耀 Magic8 Pro 采用的圆形模组设计,荣耀Magic8 RSR 的镜头布局明显取法于保时捷 911 的矩阵大灯。

三颗摄像头与 AiMAGE 标识被有序地分布在矩阵四角,而保时捷 911 标志性的日行灯光学轮廓与线条,也被精密复刻于镜头镜片之上,重新演绎了这一经典符号。

堪比 Ultra 级的影像模组,还有 One more thing

正如我们在荣耀 Magic8 Pro 的体验中所提到的那样,「影像」是这部手机被低估的一大亮点。

放到荣耀Magic8 RSR 上,这一判断依然成立,并且在相同影像硬件的基础上,经过三个月的软件与算法打磨,整体体验明显更加成熟。

在巨大的后置影像模组之中,荣耀Magic8 RSR 配备了一套「后置三摄」的影像系统,分别由 5000 万像素 1/1.3 英寸的大底主摄、5000 万像素超广角镜头、以及这颗 1/1.4 英寸底的 2 亿像素潜望长焦组成。

不难看出,这套影像系统的「主角」,是这枚被称之为「2 亿超夜神」的潜望式长焦镜头。

这颗长焦镜头配备了 1/1.4 英寸大底,2 亿像素,以及最大 f/2.6 的光圈。默认提供 3.7× 等效 85mm 的焦段,可二次点击焦段按钮切换至 7.4× 等效 170mm 的长焦;也可以直接点选 10× 等效 230mm 超长焦,进而二次点击 10× 按钮切换至 14.8 × 等效 340mm 超长焦。

得益于高像素大底传感器的加持,在白天光线充足的情况下,这枚长焦镜头的解析力与画面层次表现都较为稳定,长焦带来的空间压缩效果也让构图更具秩序感。

用一句话来说,就是非常容易出片。

随着相机界面中的焦段切换变得更加顺滑,再加上大底长焦所带来的充足成像信心,我逐渐打破了「默认焦段才是最优光学」的惯性思维,开始更大胆地优先使用变焦完成构图,去捕捉那些在超长焦下往往容易被忽略的生活瞬间。

即便将焦段直接拉到 340mm,荣耀Magic8 RSR 依然能够在小鸟振翅飞走前,捕捉到那一瞬间的回眸。而在 342mm 的焦段下,它同样可以记录下远处的一抹夕阳,以及通过镜面反射才得以同框的老人笑容。

既然号称「夜神」,事实上这颗长焦在夜间暗光环境下,也有着较为出众的表现。

在 85mm 与 170mm 这两个焦段下,荣耀Magic8 RSR 的夜拍表现相当稳定。画面在还原肉眼所见光影层次的同时,并未刻意拉高亮度,避免了整体「傻亮」的情况;暗部细节保留得较为充分,画面纯净度也维持在一个不错的水平。

为了提升长焦成片率,荣耀引入了 AiMAGE 防抖引擎,实现了 CIPA 6.5 级防抖。在实拍体验中,荣耀Magic8 RSR 在手持拍摄时,成片率依然可控,这也让长焦夜拍真正具备了实用价值。

同样地,得益于高性能 Flicker 传感器的加持,荣耀Magic8 RSR 可以避免大多数灯光的频闪现象,所以在各种光线环境下拍摄,都能免受灯箱频闪的影响。

除了可以看得足够远,荣耀Magic8 RSR 也能看得细。这枚长焦模组依然具备出色的长焦微距能力,不仅缩短了最近对焦距离,而且也能清晰地还原人眼都未必能看得清晰的细微视角。

回看最近的相册,我会发现用荣耀Magic8 RSR 拍下的照片中,有相当一部分都来自这枚长焦镜头。这并非因为主摄表现不够出色,而是这颗长焦的可用性实在太高——无论远近,都能带来一种带着距离感的「陌生视角」,让日常场景变得更有新鲜感。

更重要的是,在相同影像硬件的基础上,经过这三个月的持续打磨,如今的荣耀Magic8 RSR 在拍摄体验上,明显要比当初上手的荣耀Magic8 Pro 来得更加成熟、也更加顺畅。

对于硬核摄影玩家,荣耀还提供了可玩性极高的「专业影像套装」——通过加装 2.35 倍长焦增距镜,手机可物理解锁 200mm 的超长焦视角,配合专属摄影手柄,秒变「演唱会神器」,补全了 Ultra 影像旗舰们都有的那个「One More Thing」。

旗舰手机当中的「手感」异类

或许你已经注意到,近几年旗舰手机的屏幕尺寸正在不断增大;而在屏幕变大的同时,机身宽度也往往被一并拉宽。久而久之,我们似乎已经默认:大屏旗舰,就意味着更宽的机身。

正是在这种习以为常的认知下,荣耀Magic8 RSR 带来了一种略显反直觉的体验——这台手机拿在手里,反而显得「窄了一些」。

换句话说,它并不是一台需要刻意迁就双手操作的设备,而是一台在顶级旗舰定位下,依然对单手使用保持友好的手机。

以日常的键盘输入情景为例,比起 iPhone 17 Pro Max、华为Mate80 Pro Max 来说,荣耀Magic8 RSR 是当中我唯一无需打开微信输入法单手模式的手机。

这种手感上的「窄」,并非错觉。

荣耀Magic8 RSR 将机身宽度控制在 75mm,在同级别大屏旗舰中显得相当克制。配合 6.71 英寸的等深四微曲屏,机身侧边在转折处有着连续而柔和的曲面过渡,边缘并不会硌手,也不会带来明显的横向撑掌感。

在实际握持时,屏幕看起来依然足够大,但手指在左右滑动时却不需要刻意调整握姿,拇指覆盖范围也更自然。这种「看着是大屏、拿着却不显宽」的反差,正是荣耀Magic8 RSR 在手感上最讨喜的地方。

对我而言,荣耀Magic8 RSR 似乎像是一台有着 Mate 80 RS 非凡大师背盖触感,又有堪比 Pura 80 Ultra 的握持手感的手机,在长时间使用手机的时候,也减轻了手上的压力,比较难能可贵。

谁会为「设计」买单?

荣耀Magic8 RSR 作为系列当中最顶级的型号,但在核心配置上并没有和荣耀Magic8 Pro 拉开明显差距。

同样搭载第五代骁龙 8 至尊版移动平台,并辅以荣耀自研的能效芯片 E2,荣耀Magic8 RSR 在面对高强度多任务处理和大型游戏场景时,都能保持从容应对。

电池续航方面则是搭载了 7200mAh 的新一代青海湖电池,配合120W有线快充与80W无线快充。搭配官方标配的 120W 充电器,实测峰值功率 61W,15 分钟可冲进电量 42%,48 分钟充满。

唯一存在等级划分的,就是内存和存储配置了,起步配置是 16GB RAM+512GB ROM,最高还有 24GB LPDDR5X 内存和 1TB 存储空间可选。

价格方面,荣耀Magic8 RSR 有 2 个版本:
16+512GB 7999 元;24GB+1TB 8999 元。

其实不难看出,荣耀Magic8 RSR 保时捷设计的定位比较像是荣耀体系里的「非凡大师」。

只不过在核心配置层面,荣耀Magic8 RSR 与 Magic8 Pro 并没有刻意拉开决定性的性能差距;相同的旗舰平台、相近的影像硬件,让它们在「能做什么」这件事上高度一致。也正因如此,它们之间的差异,并不来自参数表,而是来自取向的不同。

说到底,荣耀 Magic8 RSR 保时捷设计并不是为「想要更高参数」的人准备的。

在影像、性能与基础体验已经高度成熟的今天,它选择不再继续向上堆叠规格,而是把重心放在设计与气质的差异化上——让手机从一件高效工具,变成一件带有审美与身份表达的长期物件。

因此,是否选择荣耀 Magic8 RSR,并不取决于它「比 Magic8 Pro 强多少」,而在于你是否愿意为这套源自保时捷的设计语言、材料质感与整体气韵,接受那一份「1800 元」的溢价。

如果把视角再拉远一些,会发现这一选择背后,其实也映射着国产品牌冲击高端的一条熟悉路径。曾经帮助华为立住高端形象的两大联名符号:徕卡与保时捷设计,如今已经走向岔路。

徕卡的红色可乐标,首次出现在国产手机上,带着小米 17 Ultra 徕卡版一起走向了一机难求的盛况;而保时捷设计,也与荣耀并肩前行多年。

对无数人而言,保时捷 911 就是心中的 Dream Car,而承载着同一套设计气韵的荣耀Magic8 RSR,自然也希望成为这些人心里的 Dream Phone。但现实往往更复杂——就像很多人年轻时梦想的是 911,真正有能力走进保时捷展厅之后,却最终选择了帕美:它依然足够保时捷,却更兼顾日常、审美与长期相处的舒适度。

回看前两代 RSR,它们的设计足够凌厉,也足够有骨相和个性,但市场给予的回应,却并没有完全匹配这份锋芒。而这一代 Magic8 RSR,显然做出了一次更现实的调整:在保留保时捷设计辨识度的同时,于比例、线条与整体气质上,更靠近大众审美。

从这个角度看,荣耀Magic8 RSR 不只是一次产品更新,更像是荣耀在高端路线上的一次阶段性修正:当硬件已经拉满之后,如何用设计和审美来扩大受众、建立共识,或许才是荣耀冲击高端真正要回答的问题。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


TCL科技:董事长提名王成担任公司首席执行官(CEO)

36氪获悉,TCL科技公告,因经营管理需要,公司董事长李东生提名王成担任公司首席执行官(CEO),全面负责日常经营管理工作,李东生不再兼任。2026年1月19日,第八届董事会第十九次会议审议通过该议案,王成任期至公司第八届董事会任期截止日。王成现任公司首席运营官,持有公司股票268,220股,符合任职条件。

*ST天微:公司股票可能被终止上市

36氪获悉,*ST天微公告,公司于2025年4月30日披露了《关于实施退市风险警示暨停牌的公告》,公司因2024年度实现归属于上市公司股东的扣除非经常性损益前后的净利润孰低为负值,且2024年度实现扣除与主营业务无关的业务收入后营业收入将低于人民币1亿元,根据有关规定,公司股票于2025年5月6日起被实施退市风险警示。若公司出现《股票上市规则》第12.4.10条规定的情形,公司股票可能被上海证券交易所终止上市。请广大投资者注意投资风险。

荣耀Magic 8 Pro Air发布

36氪获悉,荣耀正式发布荣耀Magic8 Pro Air,4999元起售。据介绍,该产品搭载天玑9500第三代3nm全大核旗舰芯片。

盈方微:股票将于1月20日复牌

36氪获悉,盈方微公告,公司股票将于2026年1月20日开市时起复牌。此前,公司因筹划发行股份及支付现金购买资产并募集配套资金的事项存在不确定性,自2026年1月6日起停牌。公司决定不再推进对时擎智能科技有限公司的股权收购,但将继续推进对上海肖克利信息科技股份有限公司及富士德中国有限公司的收购,并募集配套资金。相关审计和评估工作尚未完成,公司将在完成后再次召开董事会审议并依法定程序召集股东会。

上交所就瑞茂通相关事项下发监管工作函

36氪获悉,上交所晚间发布关于瑞茂通供应链管理股份有限公司相关事项的监管工作函,涉及对象为上市公司、董事、高级管理人员、控股股东及实际控制人,处理事由为就公司信息披露事项明确监管要求。

报道:腾讯、富达国际和淡马锡据悉计划投资零食零售商鸣鸣很忙的香港IPO

据知情人士透露,腾讯控股、富达国际和淡马锡计划投资中国零食零售商鸣鸣很忙集团的香港IPO。由于相关信息未公开而要求匿名的知情人士称,上述三家公司势将在此次IPO的基石投资者之列。IPO募资规模最高或达5亿美元。知情人士还称,鸣鸣很忙最快将于周二开始接受投资者认购。知情人士补充道,相关磋商仍在进行,细节仍可能有变。(新浪财经)

江波龙:询价转让价格为212.09元/股

36氪获悉,江波龙公告,根据2026年1月19日询价申购情况,初步确定的本次询价转让价格为212.09元/股。参与报价的机构投资者家数为59家,涵盖了基金管理公司、保险公司等。本次询价转让拟转让股份已获全额认购,初步确定受让方为54名机构投资者,拟受让股份总数为12,574,358股。本次询价转让不涉及公司控制权变更,不会影响公司治理结构和持续经营。
❌