@flun/node-mobile-app 4.0.8 → 4.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,478 +1,478 @@
1
- # 自定义 libnode.so 指南(Android)
2
-
3
- 本文档讲两件事:
4
-
5
- 1. **用预编译产物**(推荐,90% 用户)-- 从仓库下载 `android-libnode.zip`,替换,跑 CLI,完事
6
- 2. **自己编译**(进阶)-- 用 WSL + NDK 从源码编译带 full-icu 的 v22+
7
-
8
- **使用 `@flun/node-mobile-app` 时不需要看这份文档就能打包**--CLI 会自动处理 ABI 配置、缓存清理、原生模块编译;
9
- 本文档只在**要换 libnode.so** 时才需要;
10
-
11
- ---
12
-
13
- ## 一、自定义 libnode.so
14
-
15
- `@flun/node-mobile-app` 的 Node.js 运行时由 `@flun/nodejs-mobile-react-native` 提供的 `libnode.so` 决定;下载版本以该包
16
- `package.json` 的 `prebuiltAssets.version` 字段为准,运行时版本与之一致;不同版本的产物在 ICU 支持上有差异:
17
-
18
- | 限制 | 影响 |
19
- | ------------------------------ | ------------------------------------------------------- |
20
- | `--with-intl=none`(不带 ICU) | 不能用 `\p{...}` / `\P{...}` Unicode 正则 |
21
- | 无法用 Express 5 | `path-to-regexp@8` 内部用 `\p{ID_Start}`,v18 加载时崩溃 |
22
- | 停更 | 官方不再更新 |
23
-
24
- **要跑 Express 5 / 用新特性 / 用完整 Unicode 正则**,需替换为 **v22+ full-icu**;
25
-
26
- ---
27
-
28
- ## 二、用预编译产物(推荐)
29
-
30
- > **注意**:从 `@flun/nodejs-mobile-react-native` 1.0.1 起,Android 的 `libnode.so` 由 postinstall 脚本从 Release 自动下载;
31
- 版本以该包 `package.json` 的 `prebuiltAssets.version` 字段为准;
32
- > **本节适用于**:自动下载的产物为 v22 以下(不带 full-icu),且需跑 Express 5 或使用 Unicode 正则,要换成 v22+ 自编译版本;
33
- > 若产物为 v22 及以上(带 full-icu),你**不需要**看本节--装包后 `libnode.so` 已就位;
34
-
35
-
36
- ### 2.1 下载地址
37
-
38
- **下载版本**:见 Releases 页面,按需选择;版本号以页面中的 tag 为准,不在文档中固定;
39
-
40
- - GitHub
41
- - 源码仓库:[OpenFlun/nodejs-mobile](https://github.com/OpenFlun/nodejs-mobile)
42
- - Release 页面:[Releases](https://github.com/OpenFlun/nodejs-mobile/releases)
43
- - Gitee(国内)
44
- - 源码仓库:[OpenFlun/nodejs-mobile](https://gitee.com/OpenFlun/nodejs-mobile)
45
- - Release 页面:[Releases](https://gitee.com/OpenFlun/nodejs-mobile/releases)
46
-
47
- **文件名说明**:发布名为 `android-libnode.zip`(带 `android-` 前缀),为未来 iOS 等平台扩展预留接口(如
48
- `ios-libnode.zip`);**内容仍是标准的 `libnode/` 目录**(`bin/` + `include/`);
49
-
50
- ### 2.2 支持矩阵
51
-
52
- | 架构 | v18 | v22 |
53
- | ---------------------------- | ------ | -------- |
54
- | **arm64-v8a**(真机) | ✓ 官方 | ✓ 预编译 |
55
- | **x86_64**(模拟器) | ✓ 官方 | ✓ 预编译 |
56
- | **armeabi-v7a**(32 位 ARM) | ✓ 官方 | ✗ |
57
-
58
- **下载入口**:
59
- - 双架构(推荐):`android-libnode.zip`(约 88 MB,具体以实际产物为准)
60
- - 单架构 zip 已废弃,不再发布
61
-
62
- **armeabi-v7a 为什么放弃**:V8 v22 在 x64 主机上无法交叉编译 32 位 ARM 目标(详见 5.1);
63
-
64
- ### 2.3 下载与替换
65
-
66
- **解压后的结构**:
67
-
68
- ```
69
- libnode/
70
- ├── bin/
71
- │ ├── arm64-v8a/libnode.so # 真机(约 113 MB)
72
- │ └── x86_64/libnode.so # 模拟器(约 118 MB)
73
- └── include/
74
- └── node/ # 官方 v22 headers(含 ABI 适配)
75
- ```
76
-
77
- **替换到你的项目**:
78
-
79
- ```bash
80
- # 进入插件的 android 目录
81
- cd <你的项目>/node_modules/@flun/nodejs-mobile-react-native/android
82
-
83
- # 备份原产物(可选,方便回退)
84
- cp -r libnode libnode.bak
85
-
86
- # 整体替换
87
- rm -rf libnode
88
- unzip /path/to/android-libnode.zip -d /tmp
89
- mv /tmp/libnode ./libnode
90
- ```
91
-
92
- **Windows 下用 PowerShell**:
93
-
94
- ```powershell
95
- $plugin = '<你的项目>\node_modules\@flun\nodejs-mobile-react-native\android'
96
-
97
- # 备份
98
- Copy-Item -Recurse "$plugin\libnode" "$plugin\libnode.bak"
99
-
100
- # 替换
101
- Remove-Item -Recurse -Force "$plugin\libnode"
102
- Expand-Archive -Path 'D:\path\to\android-libnode.zip' -DestinationPath "$env:TEMP\libnode-new" -Force
103
- Move-Item "$env:TEMP\libnode-new\libnode" "$plugin\libnode"
104
- ```
105
-
106
- ### 2.4 调整 ABI 配置
107
-
108
- **如果下载的 `android-libnode.zip` 只含一个架构**(比如只编了 arm64-v8a),在 `mobileAppConfig.js` 里同步:
109
-
110
- ```js
111
- android: {
112
- abiFilters: ['arm64-v8a'], // 只放实际存在的架构
113
- }
114
- ```
115
-
116
- **如果双架构**(arm64-v8a + x86_64,当前预编译产物即为双架构):
117
-
118
- ```js
119
- android: {
120
- abiFilters: ['arm64-v8a', 'x86_64'],
121
- }
122
- ```
123
-
124
- ### 2.5 跑 CLI(自动完成所有适配)
125
-
126
- ```bash
127
- npx node-mobile-app test # 开发调试
128
- # 或
129
- npx node-mobile-app build # 打包 APK
130
- ```
131
-
132
- **CLI 会自动**:
133
-
134
- | CLI 动作 | 说明 |
135
- | --------------------------------------------------------------- | ---------------------------- |
136
- | 检测 `libnode.so` 变化 | 指纹比对 size / mtime |
137
- | 清 `.cxx` / `android/build` / `app/build` | 避免旧架构的 CMake 中间产物 |
138
- | Patch `app/build.gradle` 的 `ndk.abiFilters` | 用 `mobileAppConfig.js` 的值 |
139
- | Patch `android/gradle.properties` 的 `reactNativeArchitectures` | 同步 ABI 列表 |
140
- | Patch 插件 `build.gradle` 的 `abiFilters` | 让插件只编指定架构 |
141
- | 重编原生模块(自动检测 `binding.gyp`) | 见 2.6 |
142
-
143
- **你不需要手动清任何缓存;**
144
-
145
- ### 2.6 重编原生模块(仅当项目含原生模块)
146
-
147
- 不同 Node 主版本的 `NODE_MODULE_VERSION` 不同(如 v18 为 108,v22 为 127);**如果项目里装了带 C++ 代码的 npm 包**(如
148
- `bcrypt`、`sqlite3`、`sharp`),替换为不同主版本的 `libnode.so` 后,原生模块需重编才能兼容;
149
-
150
- **默认自动处理**:CLI 会扫描 `nodejs-project/node_modules` 下的 `binding.gyp` 判断是否有原生模块,
151
- 自动决定是否重编,**无需配置**;
152
-
153
- **手动强制重编**(特殊场景:换过 `libnode.so` 但依赖没变,检测机制未触发):
154
-
155
- ```bash
156
- # Linux / macOS
157
- NODE_MOBILE_FORCE_REBUILD=1 npx node-mobile-app test
158
- ```
159
-
160
- ```powershell
161
- # Windows
162
- $env:NODE_MOBILE_FORCE_REBUILD=1; npx node-mobile-app test
163
- ```
164
-
165
- **没有原生模块(纯 JS 依赖)**:无需关心,检测会自动跳过;
166
-
167
- **切换架构(arm64 ↔ x86_64)**:不需要重编;JS 依赖跨架构,只有少数原生模块分架构;
168
-
169
- **Windows 限制**:Windows 上 nodejs-mobile 无法编译原生模块(上游限制);此功能仅 Linux / macOS 可用;
170
-
171
- ### 2.7 验证
172
-
173
- **看 Node 版本**:
174
-
175
- ```powershell
176
- $adb = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe"
177
- & $adb logcat -c
178
- & $adb shell am force-stop <你的 appId>
179
- & $adb shell am start -n <你的 appId>/.MainActivity
180
- Start-Sleep -Seconds 5
181
- & $adb logcat -d | Select-String 'NODEJS-MOBILE'
182
- ```
183
-
184
- **预期**:
185
-
186
- ```
187
- D nativeloader: Load ... libnodejs-mobile-react-native-native-lib.so ... : ok
188
- <I NODEJS-MOBILE: Node.js vX.Y.Z>
189
- ```
190
-
191
- **验证 full-icu**:临时在 `server.js` 顶部加:
192
-
193
- ```js
194
- try {
195
- /^[$_\p{ID_Start}]$/u;
196
- console.log('=== ICU check: \\p{ID_Start} OK');
197
- } catch (e) {
198
- console.log('=== ICU check failed:', e.message);
199
- }
200
- ```
201
-
202
- 抓日志:
203
-
204
- ```powershell
205
- & $adb logcat -c
206
- & $adb shell am start -n <你的 appId>/.MainActivity
207
- Start-Sleep -Seconds 5
208
- & $adb logcat -d | Select-String 'ICU check'
209
- ```
210
-
211
- 预期:`I NODEJS-MOBILE: === ICU check: \p{ID_Start} OK`
212
-
213
- 若显示 `ICU check failed` → `libnode.so` 不带 full-icu → 见第三章自己编,或回退到 Express 4;
214
-
215
- ---
216
-
217
- ## 三、自己编译 libnode.so
218
-
219
- **环境要求**:WSL(或 Linux),因为构建脚本依赖 `make` / `sed` / NDK;
220
-
221
- ### 3.1 准备源码与 NDK
222
-
223
- ```bash
224
- # 克隆源码(国内推荐 Gitee)
225
- git clone https://gitee.com/OpenFlun/nodejs-mobile.git
226
- cd nodejs-mobile
227
-
228
- # 下载 Android NDK r27
229
- cd /mnt/d/nodejs-mobile-build
230
- wget https://dl.google.com/android/repository/android-ndk-r27-linux.zip
231
- unzip android-ndk-r27-linux.zip
232
- ```
233
-
234
- ### 3.2 关键:启用 full-icu
235
-
236
- **默认 `--with-intl=none`,必须改成 `full-icu`**,否则 Express 5 跑不了;
237
-
238
- ```bash
239
- cd /mnt/d/nodejs-mobile-build/nodejs-mobile
240
-
241
- # 改前确认
242
- grep with-intl android_configure.py
243
-
244
- # 改成 full-icu
245
- sed -i 's/--with-intl=none/--with-intl=full-icu/' android_configure.py
246
-
247
- # 改后确认
248
- grep with-intl android_configure.py
249
- ```
250
-
251
- ### 3.3 编译单架构
252
-
253
- ```bash
254
- NDK=/mnt/d/nodejs-mobile-build/android-ndk-r27
255
- SRC=/mnt/d/nodejs-mobile-build/nodejs-mobile
256
- STRIP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
257
-
258
- cd $SRC
259
- rm -rf out config.gypi config.mk config.status
260
-
261
- # 目标: arm64 或 x86_64
262
- ./android-configure $NDK 30 arm64
263
- make -j4
264
-
265
- # 剥离符号(必须,否则 .so 会大很多)
266
- cp out/Release/libnode.so out/Release/libnode.so.bak
267
- $STRIP out/Release/libnode.so
268
-
269
- # 验证符号表未变
270
- diff <(nm -D out/Release/libnode.so.bak | awk '{print $2, $3}' | sort) \
271
- <(nm -D out/Release/libnode.so | awk '{print $2, $3}' | sort) \
272
- && echo "符号表一致"
273
-
274
- # 验证 full-icu
275
- strings out/Release/libnode.so | grep -c "icudt78l"
276
- # 应输出几千(约 4300),否则没编进 ICU
277
- ```
278
-
279
- ### 3.4 多架构编译(推荐用脚本)
280
-
281
- **一键脚本** `/mnt/d/nodejs-mobile-build/build-all-archs.sh`:
282
-
283
- ```bash
284
- #!/bin/bash
285
- set -e
286
- NDK=/mnt/d/nodejs-mobile-build/android-ndk-r27
287
- SRC=/mnt/d/nodejs-mobile-build/nodejs-mobile
288
- TMP=/mnt/d/nodejs-mobile-build/libnode-tmp
289
- OUT=$SRC/out_android
290
- STRIP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
291
- HEADERS=/mnt/d/nodejs-mobile-build/node-v<实际版本>-headers/include/node
292
-
293
- # 编译前检查 full-icu
294
- if ! grep -q "with-intl=full-icu" $SRC/android_configure.py; then
295
- echo "❌ android_configure.py 未启用 full-icu"
296
- exit 1
297
- fi
298
- echo "✅ full-icu 已启用"
299
-
300
- mkdir -p $TMP $OUT
301
-
302
- # 逐架构编译,暂存 .so
303
- for ARCH in arm64 x86_64; do
304
- echo "=== 编译 $ARCH ==="
305
- cd $SRC
306
- rm -rf out config.gypi config.mk config.status
307
- ./android-configure $NDK 30 $ARCH
308
- make -j4
309
- $STRIP out/Release/libnode.so
310
- DIR_NAME=$([ "$ARCH" = "arm64" ] && echo "arm64-v8a" || echo "$ARCH")
311
- mkdir -p $TMP/$DIR_NAME
312
- cp out/Release/libnode.so $TMP/$DIR_NAME/libnode.so
313
- echo "--- ICU 符号数: $(strings $TMP/$DIR_NAME/libnode.so | grep -c 'icudt78l') ---"
314
- done
315
-
316
- # 打包 android-libnode.zip
317
- cd /mnt/d/nodejs-mobile-build
318
- rm -rf libnode-package
319
- mkdir -p libnode-package/libnode/bin/arm64-v8a
320
- mkdir -p libnode-package/libnode/bin/x86_64
321
- mkdir -p libnode-package/libnode/include
322
- cp -r $HEADERS libnode-package/libnode/include/node
323
- cp $TMP/arm64-v8a/libnode.so libnode-package/libnode/bin/arm64-v8a/
324
- cp $TMP/x86_64/libnode.so libnode-package/libnode/bin/x86_64/
325
- cd libnode-package && zip -r android-libnode.zip libnode
326
- mv android-libnode.zip $OUT/android-libnode.zip
327
-
328
- echo "=== 产物:$OUT/android-libnode.zip ==="
329
- ls -lh $OUT
330
- ```
331
-
332
- 执行:
333
-
334
- ```bash
335
- chmod +x /mnt/d/nodejs-mobile-build/build-all-archs.sh
336
- /mnt/d/nodejs-mobile-build/build-all-archs.sh
337
- ```
338
-
339
- ### 3.5 多架构编译注意事项
340
-
341
- 1. **每次切换架构必须清 `out`**:中间文件不能混用,否则奇怪的编译错误;脚本里已经 `rm -rf out`;
342
- 2. **切换架构会清空 `out`**:所以脚本编完一个架构立即把 `.so` 复制到 `libnode-tmp/<arch>/` 独立保存;
343
- 3. **每次切架构前确认 `--with-intl=full-icu` 还在**:`android_configure.py` 是源码,不会因切换架构被重置--但如果你从 Git
344
- 重新 clone 或覆盖过源码,修改会丢,务必再确认;
345
- 4. **验证两个架构都带 ICU**:`strings libnode.so | grep -c "icudt78l"` 应输出几千(约 4300),否则该架构没编进 ICU;
346
-
347
- ---
348
-
349
- ## 四、产物体积与 Express 版本
350
-
351
- ### 4.1 体积对照
352
-
353
- **官方数据(strip 后)**:
354
-
355
- | 版本 | arm64-v8a | x86_64 |
356
- | ---------------------- | ------------- | ------------- |
357
- | v18(官方,无 ICU) | 59.6 MB | 62.3 MB |
358
- | v22(自编译,full-icu) | **约 113 MB** | **约 118 MB** |
359
-
360
- **zip 后**:`android-libnode.zip`(含两架构 `.so` + headers)约 88 MB,具体以实际产物为准;
361
-
362
- 体积差异约 50 MB,来自三部分:Node v22 核心引擎 + full-icu 数据(约 25–30 MB)+ V8 版本升级;**不是编译出错**;
363
-
364
- ### 4.2 full-icu 与 Express 版本
365
-
366
- | libnode.so | Node.js | ICU | Express |
367
- | --------------------- | ------- | --- | --------------------------- |
368
- | 官方 v18 | v18 | 无 | **必须 4.x**(如 `4.22.2`) |
369
- | 自编译 v22 + full-icu | v22 | 有 | 可用 5.x(如 `5.2.1`) |
370
-
371
- **Express 版本选错的典型崩溃**:
372
-
373
- - `number 116 is not a function` → v18 无 ICU 加载 `path-to-regexp@8`
374
- - `Invalid regular expression: /^[$_\p{ID_Start}]$/u` → 同上
375
-
376
- **Express 5 路由语法迁移**:
377
-
378
- | Express 4 | Express 5 |
379
- | --------------------------- | --------------------------------- |
380
- | `app.get('*', handler)` | `app.get('/{*splat}', handler)` |
381
- | `app.get('/:id?', handler)` | `app.get('/user{/:id}', handler)` |
382
- | `app.del('/x', handler)` | `app.delete('/x', handler)` |
383
- | `res.json(obj, 200)` | `res.status(200).json(obj)` |
384
- | `res.send(200, body)` | `res.status(200).send(body)` |
385
-
386
- **不要用 `@alloc/path-to-regexp` 替换原版**:它的 `exports` 只有 `import` 条件,CJS 下 `require()` 报
387
- `No "exports" main defined`;用原版 `path-to-regexp@8.4.2`;
388
-
389
- ---
390
-
391
- ## 五、常见问题
392
-
393
- ### 5.1 armeabi-v7a 为什么放弃
394
-
395
- **结论**:v22+ **放弃 armeabi-v7a(32 位 ARM)**;
396
-
397
- **原因**:
398
-
399
- 1. **V8 官方限制**:x64 主机上交叉编译 32 位 arm 目标时,`deps/v8/include/v8config.h` 第 914 行硬性拒绝:
400
-
401
- ```
402
- #error Target architecture arm is only supported on arm and ia32 host
403
- ```
404
-
405
- 即使强行绕过,后续 Torque 工具仍报大量 8 字节对齐错误;
406
-
407
- 2. **实际意义有限**:Android 从 2019 年起要求新应用提供 64 位版本,现代设备几乎全部 arm64-v8a;
408
-
409
- **替代**:需 32 位 ARM 支持时,使用提供该架构的 `libnode.so`(如 v18 / v20 产物);`abiFilters` 需与之对应;
410
-
411
- ### 5.2 x86_64 的说明
412
-
413
- x86_64 在 x64 主机上**无交叉编译问题**,与 arm64 流程一致;`android-libnode.zip` 已含 x86_64 产物;
414
-
415
- **用途**:Android Studio / 命令行的 x86_64 架构 AVD(模拟器);
416
-
417
- **用模拟器时**:
418
- - `mobileAppConfig.js` 的 `abiFilters` 含 `x86_64`
419
- - `libnode/bin/x86_64/libnode.so` 存在
420
- - 模拟器镜像必须是 x86_64 架构(不是 arm64)
421
-
422
- ### 5.3 自编译相关坑
423
-
424
- - **`number 116 is not a function`**
425
- - 原因:`libnode.so` 无 full-icu
426
- - 解决:重新用 `--with-intl=full-icu` 编译,或回退 Express 4
427
- - **`\p{...}` 正则报错**
428
- - 原因:同上
429
- - 解决:同上
430
- - **`No "exports" main defined`**
431
- - 原因:用了 `@alloc/path-to-regexp`
432
- - 解决:撤销 `overrides`,清 `node_modules` + `package-lock.json` 重装
433
- - **`Cannot find module 'rn-bridge'`**
434
- - 原因:Node 20+ 不再把链接绑定暴露给 `require()`
435
- - 解决:需修改 Node.js `lib/internal/modules/cjs/loader.js`,从 `NODE_PATH` 按目录加载 `rn-bridge`(入口由该目录
436
- `package.json` 的 `main` 决定)
437
- - **启动即崩**
438
- - 现象:`pthread_mutex_lock called on a destroyed mutex` + `Fatal signal 6 (SIGABRT)`;
439
- `require('rn-bridge')` 返回的对象无 `channel` 属性
440
- - 原因:v22 loader 补丁按 `rn-bridge/index.js` 硬编码定位,而插件入口因 `type: module` 已改名(如 `index.cjs`),匹配失败 →
441
- Node 回退到 `process._linkedBinding('rn_bridge')` 拿裸 binding
442
- - 解决:使用按**目录** `rn-bridge` 定位的 loader 补丁(入口由该目录 `package.json` 的 `main` 字段解析)
443
- - **JNI 编译报 `undefined symbol: v8::Exception::Error`**
444
- - 原因:插件旧头文件(v18 时代)与 v22 `libnode.so` ABI 不匹配
445
- - 解决:**用 `android-libnode.zip` 整目录替换**,其 `include/node/` 是 v22 官方 headers
446
- - **`NODE_MODULE_VERSION` 不匹配**
447
- - 原因:原生模块未重编
448
- - 解决:`NODE_MOBILE_FORCE_REBUILD=1` 后重跑 CLI
449
- - **Windows 下 `Unsupported operating system for nodejs-mobile native builds`**
450
- - 原因:插件硬编码只支持 macOS/Linux
451
- - 解决:官方 v22 产物已含 Windows 支持(`windows-x86_64` + `host_os=win32`)
452
- - **Gradle 9 报 `Could not find method exec()`**
453
- - 原因:Gradle 9 移除 `exec()`
454
- - 解决:官方 v22 产物已修复(改用 `providers.exec`)
455
-
456
- ---
457
-
458
- ## 六、注意事项
459
-
460
- 1. **插件本身可能不兼容**:`nodejs-mobile-react-native` 的 C++ 胶水代码为特定 Node.js 版本编写;升级到 v22+
461
- 时可能需自行修改插件源码并重编;
462
- 2. **依赖库版本**:部分 npm 包限制 Node.js 版本;检查 `package.json` 的 `engines`;
463
- 3. **非官方产物**:自编译的 `libnode.so` 未经充分测试,生产环境前充分测试;
464
- 4. **多架构建议**:优先保证 `arm64-v8a`,按需补 `x86_64`;`armeabi-v7a` v22+ 已放弃;
465
- 5. **full-icu 是 Express 5 的硬前提**:不要图省事用 `--with-intl=none` 编译;
466
- 6. **`android-libnode.zip` 是唯一发布物**:与官方 nodejs-mobile 结构一致(`bin/` + `include/`),整目录替换,无需手动改头文件;
467
- 7. **CLI 会自动处理缓存**:替换 `libnode.so` 后跑 `test` / `build`,指纹机制检测到变化时清 `.cxx` / `android/build` /
468
- `app/build`,并清 `nodejs-assets` 与依赖快照,触发重装、重编、重优化;**无需手动清任何缓存**;
469
-
470
- ---
471
-
472
- ## 相关文档
473
-
474
- - [README.md](./README.md) -- 快速开始、配置字段、命令
475
- - [Android 构建指南.md](./Android 构建指南.md) -- 环境准备、libnode.so 对照、Express 版本、调试
476
- - **预编译产物下载**:[GitHub Releases](https://github.com/OpenFlun/nodejs-mobile/releases)
477
- [Gitee Releases](https://gitee.com/OpenFlun/nodejs-mobile/releases)
1
+ # 自定义 libnode.so 指南(Android)
2
+
3
+ 本文档讲两件事:
4
+
5
+ 1. **用预编译产物**(推荐,90% 用户)-- 从仓库下载 `android-libnode.zip`,替换,跑 CLI,完事
6
+ 2. **自己编译**(进阶)-- 用 WSL + NDK 从源码编译带 full-icu 的 v22+
7
+
8
+ **使用 `@flun/node-mobile-app` 时不需要看这份文档就能打包**--CLI 会自动处理 ABI 配置、缓存清理、原生模块编译;
9
+ 本文档只在**要换 libnode.so** 时才需要;
10
+
11
+ ---
12
+
13
+ ## 一、自定义 libnode.so
14
+
15
+ `@flun/node-mobile-app` 的 Node.js 运行时由 `@flun/nodejs-mobile-react-native` 提供的 `libnode.so` 决定;下载版本以该包
16
+ `package.json` 的 `prebuiltAssets.version` 字段为准,运行时版本与之一致;不同版本的产物在 ICU 支持上有差异:
17
+
18
+ | 限制 | 影响 |
19
+ | ------------------------------ | ------------------------------------------------------- |
20
+ | `--with-intl=none`(不带 ICU) | 不能用 `\p{...}` / `\P{...}` Unicode 正则 |
21
+ | 无法用 Express 5 | `path-to-regexp@8` 内部用 `\p{ID_Start}`,v18 加载时崩溃 |
22
+ | 停更 | 官方不再更新 |
23
+
24
+ **要跑 Express 5 / 用新特性 / 用完整 Unicode 正则**,需替换为 **v22+ full-icu**;
25
+
26
+ ---
27
+
28
+ ## 二、用预编译产物(推荐)
29
+
30
+ > **注意**:从 `@flun/nodejs-mobile-react-native` 1.0.1 起,Android 的 `libnode.so` 由 postinstall 脚本从 Release 自动下载;
31
+ 版本以该包 `package.json` 的 `prebuiltAssets.version` 字段为准;
32
+ > **本节适用于**:自动下载的产物为 v22 以下(不带 full-icu),且需跑 Express 5 或使用 Unicode 正则,要换成 v22+ 自编译版本;
33
+ > 若产物为 v22 及以上(带 full-icu),你**不需要**看本节--装包后 `libnode.so` 已就位;
34
+
35
+
36
+ ### 2.1 下载地址
37
+
38
+ **下载版本**:见 Releases 页面,按需选择;版本号以页面中的 tag 为准,不在文档中固定;
39
+
40
+ - GitHub
41
+ - 源码仓库:[OpenFlun/nodejs-mobile](https://github.com/OpenFlun/nodejs-mobile)
42
+ - Release 页面:[Releases](https://github.com/OpenFlun/nodejs-mobile/releases)
43
+ - Gitee(国内)
44
+ - 源码仓库:[OpenFlun/nodejs-mobile](https://gitee.com/OpenFlun/nodejs-mobile)
45
+ - Release 页面:[Releases](https://gitee.com/OpenFlun/nodejs-mobile/releases)
46
+
47
+ **文件名说明**:发布名为 `android-libnode.zip`(带 `android-` 前缀),为未来 iOS 等平台扩展预留接口(如
48
+ `ios-libnode.zip`);**内容仍是标准的 `libnode/` 目录**(`bin/` + `include/`);
49
+
50
+ ### 2.2 支持矩阵
51
+
52
+ | 架构 | v18 | v22 |
53
+ | ---------------------------- | ------ | -------- |
54
+ | **arm64-v8a**(真机) | ✓ 官方 | ✓ 预编译 |
55
+ | **x86_64**(模拟器) | ✓ 官方 | ✓ 预编译 |
56
+ | **armeabi-v7a**(32 位 ARM) | ✓ 官方 | ✗ |
57
+
58
+ **下载入口**:
59
+ - 双架构(推荐):`android-libnode.zip`(约 88 MB,具体以实际产物为准)
60
+ - 单架构 zip 已废弃,不再发布
61
+
62
+ **armeabi-v7a 为什么放弃**:V8 v22 在 x64 主机上无法交叉编译 32 位 ARM 目标(详见 5.1);
63
+
64
+ ### 2.3 下载与替换
65
+
66
+ **解压后的结构**:
67
+
68
+ ```
69
+ libnode/
70
+ ├── bin/
71
+ │ ├── arm64-v8a/libnode.so # 真机(约 113 MB)
72
+ │ └── x86_64/libnode.so # 模拟器(约 118 MB)
73
+ └── include/
74
+ └── node/ # 官方 v22 headers(含 ABI 适配)
75
+ ```
76
+
77
+ **替换到你的项目**:
78
+
79
+ ```bash
80
+ # 进入插件的 android 目录
81
+ cd <你的项目>/node_modules/@flun/nodejs-mobile-react-native/android
82
+
83
+ # 备份原产物(可选,方便回退)
84
+ cp -r libnode libnode.bak
85
+
86
+ # 整体替换
87
+ rm -rf libnode
88
+ unzip /path/to/android-libnode.zip -d /tmp
89
+ mv /tmp/libnode ./libnode
90
+ ```
91
+
92
+ **Windows 下用 PowerShell**:
93
+
94
+ ```powershell
95
+ $plugin = '<你的项目>\node_modules\@flun\nodejs-mobile-react-native\android'
96
+
97
+ # 备份
98
+ Copy-Item -Recurse "$plugin\libnode" "$plugin\libnode.bak"
99
+
100
+ # 替换
101
+ Remove-Item -Recurse -Force "$plugin\libnode"
102
+ Expand-Archive -Path 'D:\path\to\android-libnode.zip' -DestinationPath "$env:TEMP\libnode-new" -Force
103
+ Move-Item "$env:TEMP\libnode-new\libnode" "$plugin\libnode"
104
+ ```
105
+
106
+ ### 2.4 调整 ABI 配置
107
+
108
+ **如果下载的 `android-libnode.zip` 只含一个架构**(比如只编了 arm64-v8a),在 `mobileAppConfig.js` 里同步:
109
+
110
+ ```js
111
+ android: {
112
+ abiFilters: ['arm64-v8a'], // 只放实际存在的架构
113
+ }
114
+ ```
115
+
116
+ **如果双架构**(arm64-v8a + x86_64,当前预编译产物即为双架构):
117
+
118
+ ```js
119
+ android: {
120
+ abiFilters: ['arm64-v8a', 'x86_64'],
121
+ }
122
+ ```
123
+
124
+ ### 2.5 跑 CLI(自动完成所有适配)
125
+
126
+ ```bash
127
+ npx node-mobile-app test # 开发调试
128
+ # 或
129
+ npx node-mobile-app build # 打包 APK
130
+ ```
131
+
132
+ **CLI 会自动**:
133
+
134
+ | CLI 动作 | 说明 |
135
+ | --------------------------------------------------------------- | ---------------------------- |
136
+ | 检测 `libnode.so` 变化 | 指纹比对 size / mtime |
137
+ | 清 `.cxx` / `android/build` / `app/build` | 避免旧架构的 CMake 中间产物 |
138
+ | Patch `app/build.gradle` 的 `ndk.abiFilters` | 用 `mobileAppConfig.js` 的值 |
139
+ | Patch `android/gradle.properties` 的 `reactNativeArchitectures` | 同步 ABI 列表 |
140
+ | Patch 插件 `build.gradle` 的 `abiFilters` | 让插件只编指定架构 |
141
+ | 重编原生模块(自动检测 `binding.gyp`) | 见 2.6 |
142
+
143
+ **你不需要手动清任何缓存;**
144
+
145
+ ### 2.6 重编原生模块(仅当项目含原生模块)
146
+
147
+ 不同 Node 主版本的 `NODE_MODULE_VERSION` 不同(如 v18 为 108,v22 为 127);**如果项目里装了带 C++ 代码的 npm 包**(如
148
+ `bcrypt`、`sqlite3`、`sharp`),替换为不同主版本的 `libnode.so` 后,原生模块需重编才能兼容;
149
+
150
+ **默认自动处理**:CLI 会扫描 `nodejs-project/node_modules` 下的 `binding.gyp` 判断是否有原生模块,
151
+ 自动决定是否重编,**无需配置**;
152
+
153
+ **手动强制重编**(特殊场景:换过 `libnode.so` 但依赖没变,检测机制未触发):
154
+
155
+ ```bash
156
+ # Linux / macOS
157
+ NODE_MOBILE_FORCE_REBUILD=1 npx node-mobile-app test
158
+ ```
159
+
160
+ ```powershell
161
+ # Windows
162
+ $env:NODE_MOBILE_FORCE_REBUILD=1; npx node-mobile-app test
163
+ ```
164
+
165
+ **没有原生模块(纯 JS 依赖)**:无需关心,检测会自动跳过;
166
+
167
+ **切换架构(arm64 ↔ x86_64)**:不需要重编;JS 依赖跨架构,只有少数原生模块分架构;
168
+
169
+ **Windows 限制**:Windows 上 nodejs-mobile 无法编译原生模块(上游限制);此功能仅 Linux / macOS 可用;
170
+
171
+ ### 2.7 验证
172
+
173
+ **看 Node 版本**:
174
+
175
+ ```powershell
176
+ $adb = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe"
177
+ & $adb logcat -c
178
+ & $adb shell am force-stop <你的 appId>
179
+ & $adb shell am start -n <你的 appId>/.MainActivity
180
+ Start-Sleep -Seconds 5
181
+ & $adb logcat -d | Select-String 'NODEJS-MOBILE'
182
+ ```
183
+
184
+ **预期**:
185
+
186
+ ```
187
+ D nativeloader: Load ... libnodejs-mobile-react-native-native-lib.so ... : ok
188
+ <I NODEJS-MOBILE: Node.js vX.Y.Z>
189
+ ```
190
+
191
+ **验证 full-icu**:临时在 `server.js` 顶部加:
192
+
193
+ ```js
194
+ try {
195
+ /^[$_\p{ID_Start}]$/u;
196
+ console.log('=== ICU check: \\p{ID_Start} OK');
197
+ } catch (e) {
198
+ console.log('=== ICU check failed:', e.message);
199
+ }
200
+ ```
201
+
202
+ 抓日志:
203
+
204
+ ```powershell
205
+ & $adb logcat -c
206
+ & $adb shell am start -n <你的 appId>/.MainActivity
207
+ Start-Sleep -Seconds 5
208
+ & $adb logcat -d | Select-String 'ICU check'
209
+ ```
210
+
211
+ 预期:`I NODEJS-MOBILE: === ICU check: \p{ID_Start} OK`
212
+
213
+ 若显示 `ICU check failed` → `libnode.so` 不带 full-icu → 见第三章自己编,或回退到 Express 4;
214
+
215
+ ---
216
+
217
+ ## 三、自己编译 libnode.so
218
+
219
+ **环境要求**:WSL(或 Linux),因为构建脚本依赖 `make` / `sed` / NDK;
220
+
221
+ ### 3.1 准备源码与 NDK
222
+
223
+ ```bash
224
+ # 克隆源码(国内推荐 Gitee)
225
+ git clone https://gitee.com/OpenFlun/nodejs-mobile.git
226
+ cd nodejs-mobile
227
+
228
+ # 下载 Android NDK r27
229
+ cd /mnt/d/nodejs-mobile-build
230
+ wget https://dl.google.com/android/repository/android-ndk-r27-linux.zip
231
+ unzip android-ndk-r27-linux.zip
232
+ ```
233
+
234
+ ### 3.2 关键:启用 full-icu
235
+
236
+ **默认 `--with-intl=none`,必须改成 `full-icu`**,否则 Express 5 跑不了;
237
+
238
+ ```bash
239
+ cd /mnt/d/nodejs-mobile-build/nodejs-mobile
240
+
241
+ # 改前确认
242
+ grep with-intl android_configure.py
243
+
244
+ # 改成 full-icu
245
+ sed -i 's/--with-intl=none/--with-intl=full-icu/' android_configure.py
246
+
247
+ # 改后确认
248
+ grep with-intl android_configure.py
249
+ ```
250
+
251
+ ### 3.3 编译单架构
252
+
253
+ ```bash
254
+ NDK=/mnt/d/nodejs-mobile-build/android-ndk-r27
255
+ SRC=/mnt/d/nodejs-mobile-build/nodejs-mobile
256
+ STRIP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
257
+
258
+ cd $SRC
259
+ rm -rf out config.gypi config.mk config.status
260
+
261
+ # 目标: arm64 或 x86_64
262
+ ./android-configure $NDK 30 arm64
263
+ make -j4
264
+
265
+ # 剥离符号(必须,否则 .so 会大很多)
266
+ cp out/Release/libnode.so out/Release/libnode.so.bak
267
+ $STRIP out/Release/libnode.so
268
+
269
+ # 验证符号表未变
270
+ diff <(nm -D out/Release/libnode.so.bak | awk '{print $2, $3}' | sort) \
271
+ <(nm -D out/Release/libnode.so | awk '{print $2, $3}' | sort) \
272
+ && echo "符号表一致"
273
+
274
+ # 验证 full-icu
275
+ strings out/Release/libnode.so | grep -c "icudt78l"
276
+ # 应输出几千(约 4300),否则没编进 ICU
277
+ ```
278
+
279
+ ### 3.4 多架构编译(推荐用脚本)
280
+
281
+ **一键脚本** `/mnt/d/nodejs-mobile-build/build-all-archs.sh`:
282
+
283
+ ```bash
284
+ #!/bin/bash
285
+ set -e
286
+ NDK=/mnt/d/nodejs-mobile-build/android-ndk-r27
287
+ SRC=/mnt/d/nodejs-mobile-build/nodejs-mobile
288
+ TMP=/mnt/d/nodejs-mobile-build/libnode-tmp
289
+ OUT=$SRC/out_android
290
+ STRIP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
291
+ HEADERS=/mnt/d/nodejs-mobile-build/node-v<实际版本>-headers/include/node
292
+
293
+ # 编译前检查 full-icu
294
+ if ! grep -q "with-intl=full-icu" $SRC/android_configure.py; then
295
+ echo "❌ android_configure.py 未启用 full-icu"
296
+ exit 1
297
+ fi
298
+ echo "✅ full-icu 已启用"
299
+
300
+ mkdir -p $TMP $OUT
301
+
302
+ # 逐架构编译,暂存 .so
303
+ for ARCH in arm64 x86_64; do
304
+ echo "=== 编译 $ARCH ==="
305
+ cd $SRC
306
+ rm -rf out config.gypi config.mk config.status
307
+ ./android-configure $NDK 30 $ARCH
308
+ make -j4
309
+ $STRIP out/Release/libnode.so
310
+ DIR_NAME=$([ "$ARCH" = "arm64" ] && echo "arm64-v8a" || echo "$ARCH")
311
+ mkdir -p $TMP/$DIR_NAME
312
+ cp out/Release/libnode.so $TMP/$DIR_NAME/libnode.so
313
+ echo "--- ICU 符号数: $(strings $TMP/$DIR_NAME/libnode.so | grep -c 'icudt78l') ---"
314
+ done
315
+
316
+ # 打包 android-libnode.zip
317
+ cd /mnt/d/nodejs-mobile-build
318
+ rm -rf libnode-package
319
+ mkdir -p libnode-package/libnode/bin/arm64-v8a
320
+ mkdir -p libnode-package/libnode/bin/x86_64
321
+ mkdir -p libnode-package/libnode/include
322
+ cp -r $HEADERS libnode-package/libnode/include/node
323
+ cp $TMP/arm64-v8a/libnode.so libnode-package/libnode/bin/arm64-v8a/
324
+ cp $TMP/x86_64/libnode.so libnode-package/libnode/bin/x86_64/
325
+ cd libnode-package && zip -r android-libnode.zip libnode
326
+ mv android-libnode.zip $OUT/android-libnode.zip
327
+
328
+ echo "=== 产物:$OUT/android-libnode.zip ==="
329
+ ls -lh $OUT
330
+ ```
331
+
332
+ 执行:
333
+
334
+ ```bash
335
+ chmod +x /mnt/d/nodejs-mobile-build/build-all-archs.sh
336
+ /mnt/d/nodejs-mobile-build/build-all-archs.sh
337
+ ```
338
+
339
+ ### 3.5 多架构编译注意事项
340
+
341
+ 1. **每次切换架构必须清 `out`**:中间文件不能混用,否则奇怪的编译错误;脚本里已经 `rm -rf out`;
342
+ 2. **切换架构会清空 `out`**:所以脚本编完一个架构立即把 `.so` 复制到 `libnode-tmp/<arch>/` 独立保存;
343
+ 3. **每次切架构前确认 `--with-intl=full-icu` 还在**:`android_configure.py` 是源码,不会因切换架构被重置--但如果你从 Git
344
+ 重新 clone 或覆盖过源码,修改会丢,务必再确认;
345
+ 4. **验证两个架构都带 ICU**:`strings libnode.so | grep -c "icudt78l"` 应输出几千(约 4300),否则该架构没编进 ICU;
346
+
347
+ ---
348
+
349
+ ## 四、产物体积与 Express 版本
350
+
351
+ ### 4.1 体积对照
352
+
353
+ **官方数据(strip 后)**:
354
+
355
+ | 版本 | arm64-v8a | x86_64 |
356
+ | ---------------------- | ------------- | ------------- |
357
+ | v18(官方,无 ICU) | 59.6 MB | 62.3 MB |
358
+ | v22(自编译,full-icu) | **约 113 MB** | **约 118 MB** |
359
+
360
+ **zip 后**:`android-libnode.zip`(含两架构 `.so` + headers)约 88 MB,具体以实际产物为准;
361
+
362
+ 体积差异约 50 MB,来自三部分:Node v22 核心引擎 + full-icu 数据(约 25–30 MB)+ V8 版本升级;**不是编译出错**;
363
+
364
+ ### 4.2 full-icu 与 Express 版本
365
+
366
+ | libnode.so | Node.js | ICU | Express |
367
+ | --------------------- | ------- | --- | --------------------------- |
368
+ | 官方 v18 | v18 | 无 | **必须 4.x**(如 `4.22.2`) |
369
+ | 自编译 v22 + full-icu | v22 | 有 | 可用 5.x(如 `5.2.1`) |
370
+
371
+ **Express 版本选错的典型崩溃**:
372
+
373
+ - `number 116 is not a function` → v18 无 ICU 加载 `path-to-regexp@8`
374
+ - `Invalid regular expression: /^[$_\p{ID_Start}]$/u` → 同上
375
+
376
+ **Express 5 路由语法迁移**:
377
+
378
+ | Express 4 | Express 5 |
379
+ | --------------------------- | --------------------------------- |
380
+ | `app.get('*', handler)` | `app.get('/{*splat}', handler)` |
381
+ | `app.get('/:id?', handler)` | `app.get('/user{/:id}', handler)` |
382
+ | `app.del('/x', handler)` | `app.delete('/x', handler)` |
383
+ | `res.json(obj, 200)` | `res.status(200).json(obj)` |
384
+ | `res.send(200, body)` | `res.status(200).send(body)` |
385
+
386
+ **不要用 `@alloc/path-to-regexp` 替换原版**:它的 `exports` 只有 `import` 条件,CJS 下 `require()` 报
387
+ `No "exports" main defined`;用原版 `path-to-regexp@8.4.2`;
388
+
389
+ ---
390
+
391
+ ## 五、常见问题
392
+
393
+ ### 5.1 armeabi-v7a 为什么放弃
394
+
395
+ **结论**:v22+ **放弃 armeabi-v7a(32 位 ARM)**;
396
+
397
+ **原因**:
398
+
399
+ 1. **V8 官方限制**:x64 主机上交叉编译 32 位 arm 目标时,`deps/v8/include/v8config.h` 第 914 行硬性拒绝:
400
+
401
+ ```
402
+ #error Target architecture arm is only supported on arm and ia32 host
403
+ ```
404
+
405
+ 即使强行绕过,后续 Torque 工具仍报大量 8 字节对齐错误;
406
+
407
+ 2. **实际意义有限**:Android 从 2019 年起要求新应用提供 64 位版本,现代设备几乎全部 arm64-v8a;
408
+
409
+ **替代**:需 32 位 ARM 支持时,使用提供该架构的 `libnode.so`(如 v18 / v20 产物);`abiFilters` 需与之对应;
410
+
411
+ ### 5.2 x86_64 的说明
412
+
413
+ x86_64 在 x64 主机上**无交叉编译问题**,与 arm64 流程一致;`android-libnode.zip` 已含 x86_64 产物;
414
+
415
+ **用途**:Android Studio / 命令行的 x86_64 架构 AVD(模拟器);
416
+
417
+ **用模拟器时**:
418
+ - `mobileAppConfig.js` 的 `abiFilters` 含 `x86_64`
419
+ - `libnode/bin/x86_64/libnode.so` 存在
420
+ - 模拟器镜像必须是 x86_64 架构(不是 arm64)
421
+
422
+ ### 5.3 自编译相关坑
423
+
424
+ - **`number 116 is not a function`**
425
+ - 原因:`libnode.so` 无 full-icu
426
+ - 解决:重新用 `--with-intl=full-icu` 编译,或回退 Express 4
427
+ - **`\p{...}` 正则报错**
428
+ - 原因:同上
429
+ - 解决:同上
430
+ - **`No "exports" main defined`**
431
+ - 原因:用了 `@alloc/path-to-regexp`
432
+ - 解决:撤销 `overrides`,清 `node_modules` + `package-lock.json` 重装
433
+ - **`Cannot find module 'rn-bridge'`**
434
+ - 原因:Node 20+ 不再把链接绑定暴露给 `require()`
435
+ - 解决:需修改 Node.js `lib/internal/modules/cjs/loader.js`,从 `NODE_PATH` 按目录加载 `rn-bridge`(入口由该目录
436
+ `package.json` 的 `main` 决定)
437
+ - **启动即崩**
438
+ - 现象:`pthread_mutex_lock called on a destroyed mutex` + `Fatal signal 6 (SIGABRT)`;
439
+ `require('rn-bridge')` 返回的对象无 `channel` 属性
440
+ - 原因:v22 loader 补丁按 `rn-bridge/index.js` 硬编码定位,而插件入口因 `type: module` 已改名(如 `index.cjs`),匹配失败 →
441
+ Node 回退到 `process._linkedBinding('rn_bridge')` 拿裸 binding
442
+ - 解决:使用按**目录** `rn-bridge` 定位的 loader 补丁(入口由该目录 `package.json` 的 `main` 字段解析)
443
+ - **JNI 编译报 `undefined symbol: v8::Exception::Error`**
444
+ - 原因:插件旧头文件(v18 时代)与 v22 `libnode.so` ABI 不匹配
445
+ - 解决:**用 `android-libnode.zip` 整目录替换**,其 `include/node/` 是 v22 官方 headers
446
+ - **`NODE_MODULE_VERSION` 不匹配**
447
+ - 原因:原生模块未重编
448
+ - 解决:`NODE_MOBILE_FORCE_REBUILD=1` 后重跑 CLI
449
+ - **Windows 下 `Unsupported operating system for nodejs-mobile native builds`**
450
+ - 原因:插件硬编码只支持 macOS/Linux
451
+ - 解决:官方 v22 产物已含 Windows 支持(`windows-x86_64` + `host_os=win32`)
452
+ - **Gradle 9 报 `Could not find method exec()`**
453
+ - 原因:Gradle 9 移除 `exec()`
454
+ - 解决:官方 v22 产物已修复(改用 `providers.exec`)
455
+
456
+ ---
457
+
458
+ ## 六、注意事项
459
+
460
+ 1. **插件本身可能不兼容**:`nodejs-mobile-react-native` 的 C++ 胶水代码为特定 Node.js 版本编写;升级到 v22+
461
+ 时可能需自行修改插件源码并重编;
462
+ 2. **依赖库版本**:部分 npm 包限制 Node.js 版本;检查 `package.json` 的 `engines`;
463
+ 3. **非官方产物**:自编译的 `libnode.so` 未经充分测试,生产环境前充分测试;
464
+ 4. **多架构建议**:优先保证 `arm64-v8a`,按需补 `x86_64`;`armeabi-v7a` v22+ 已放弃;
465
+ 5. **full-icu 是 Express 5 的硬前提**:不要图省事用 `--with-intl=none` 编译;
466
+ 6. **`android-libnode.zip` 是唯一发布物**:与官方 nodejs-mobile 结构一致(`bin/` + `include/`),整目录替换,无需手动改头文件;
467
+ 7. **CLI 会自动处理缓存**:替换 `libnode.so` 后跑 `test` / `build`,指纹机制检测到变化时清 `.cxx` / `android/build` /
468
+ `app/build`,并清 `nodejs-assets` 与依赖快照,触发重装、重编、重优化;**无需手动清任何缓存**;
469
+
470
+ ---
471
+
472
+ ## 相关文档
473
+
474
+ - [README.md](./README.md) -- 快速开始、配置字段、命令
475
+ - [Android 构建指南.md](./Android 构建指南.md) -- 环境准备、libnode.so 对照、Express 版本、调试
476
+ - **预编译产物下载**:[GitHub Releases](https://github.com/OpenFlun/nodejs-mobile/releases)
477
+ [Gitee Releases](https://gitee.com/OpenFlun/nodejs-mobile/releases)
478
478
  - **源码仓库**:[GitHub](https://github.com/OpenFlun/nodejs-mobile) / [Gitee](https://gitee.com/OpenFlun/nodejs-mobile)