逆向工程 --- ReactNative 之 helloworld
React Native 与生成的 Android 目录的关系分析
一、整体架构关系
text
ReactNative 项目根目录
├── /android/ ← 完整的 Android 原生工程
├── /ios/ ← 完整的 iOS 原生工程
├── /src/ 或 /App.js ← JavaScript/React 代码
├── package.json
├── metro.config.js ← JS 打包器配置
└── node_modules/
核心关系: android/ 目录是一个完整的、可独立运行的 Android 原生项目,React Native 的 JS 代码最终会被嵌入到这个原生项目中运行。
二、Android 目录详细结构
text
android/
├── app/
│ ├── build.gradle ← App 模块构建配置
│ ├── proguard-rules.pro ← 混淆规则
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml ← 应用清单
│ ├── java/com/yourapp/
│ │ ├── MainActivity.kt ← 主 Activity
│ │ └── MainApplication.kt ← Application 入口
│ └── res/ ← 原生资源文件
│ ├── drawable/
│ ├── layout/
│ ├── mipmap/ ← 应用图标
│ └── values/
│ ├── strings.xml
│ └── styles.xml
├── build.gradle ← 项目级构建配置
├── gradle.properties ← Gradle 属性
├── gradlew ← Gradle 包装器
├── gradlew.bat
└── settings.gradle ← 项目设置
三、关键文件的对应关系
1. MainApplication — JS 引擎的启动入口
Kotlin
// android/app/src/main/java/com/yourapp/MainApplication.kt
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
// 👇 注册所有原生模块(Native Modules)
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// 手动添加的原生模块
}
// 👇 指定 JS 入口文件名
override fun getJSMainModuleName(): String = "index"
// 👇 是否启用新架构
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
// 👇 是否使用 Hermes 引擎
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
}
关系说明:
text
MainApplication
↓ 初始化
ReactNativeHost
↓ 创建
ReactInstanceManager
↓ 加载
JS Bundle (来自 index.js)
↓ 执行
React 组件树渲染
2. MainActivity — JS UI 的承载容器
Kotlin
// android/app/src/main/java/com/yourapp/MainActivity.kt
class MainActivity : ReactActivity() {
// 👇 对应 AppRegistry.registerComponent() 中注册的名字
override fun getMainComponentName(): String = "YourAppName"
// 👇 创建 React 根视图的委托
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
对应 JS 端:
JavaScript
// index.js
import { AppRegistry } from 'react-native';
import App from './App';
// 👇 这里的 'YourAppName' 必须与 MainActivity 中的名字一致
AppRegistry.registerComponent('YourAppName', () => App);
3. build.gradle — 依赖和构建桥梁
groovy
// android/app/build.gradle
apply plugin: "com.android.application"
apply plugin: "com.facebook.react" // 👈 RN 的 Gradle 插件
android {
compileSdk rootProject.ext.compileSdkVersion
namespace "com.yourapp"
defaultConfig {
applicationId "com.yourapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
buildTypes {
release {
// 👇 Release 模式下启用 JS Bundle 打包到 APK
signingConfig signingConfigs.release
}
debug {
// 👇 Debug 模式下从 Metro Dev Server 加载 JS
}
}
}
dependencies {
// 👇 React Native 核心依赖
implementation("com.facebook.react:react-android")
implementation("com.facebook.react:hermes-android") // Hermes 引擎
}
四、运行时的连接机制
Debug 模式
text
┌─────────────────┐ HTTP ┌─────────────────────┐
│ Android App │ ◄──────────────────► │ Metro Dev Server │
│ (模拟器/真机) │ localhost:8081 │ (电脑上运行) │
│ │ │ │
│ ReactRootView │ ◄── JS Bundle ────── │ index.js → 打包 │
│ (容器) │ (实时加载) │ App.js │
│ │ │ 所有 JS/JSX 文件 │
└─────────────────┘ └─────────────────────┘
Release 模式
text
┌──────────────────────────────────────┐
│ APK 文件 │
│ │
│ assets/ │
│ └── index.android.bundle ← JS代码编译后的产物│
│ │
│ classes.dex ← Java/Kotlin 编译产物 │
│ lib/ │
│ ├── libhermes.so ← Hermes 引擎│
│ ├── libjsi.so ← JSI 接口 │
│ ├── libreactnative.so ← RN C++核心│
│ └── libturbomodule.so │
│ │
│ res/ ← 原生资源 │
│ AndroidManifest.xml │
└──────────────────────────────────────┘
五、JS 组件与 Android 原生组件的映射
text
React Native (JS) Android (Native)
───────────────── ─────────────────
<View> → android.view.ViewGroup
<Text> → android.widget.TextView
<Image> → android.widget.ImageView
<ScrollView> → android.widget.ScrollView
<TextInput> → android.widget.EditText
<FlatList> → RecyclerView (优化的)
<TouchableOpacity> → View + OnClickListener
<Modal> → android.app.Dialog
<Switch> → android.widget.Switch
<StatusBar> → Window.setStatusBarColor()
映射调用链
text
JS: <View style={{backgroundColor: 'red'}}>
│
▼
React Reconciler (Diff算法)
│
▼
UIManager.createView() / updateView()
│
▼ (通过 Bridge / JSI / Fabric)
│
ViewManager.createViewInstance()
│
▼
new ReactViewGroup(context) ← Android 原生 View
│
▼
setBackgroundColor(Color.RED)
六、Native Modules 的对应关系
当你需要调用 Android 原生能力时:
Android 端 (Java/Kotlin)
Kotlin
// android/app/src/main/java/com/yourapp/ToastModule.kt
class ToastModule(reactContext: ReactApplicationContext)
: ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = "ToastModule" // 👈 JS 端引用的名字
@ReactMethod // 👈 暴露给 JS 调用
fun show(message: String, duration: Int) {
Toast.makeText(reactApplicationContext, message, duration).show()
}
}
// 注册 Package
class ToastPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext) =
listOf(ToastModule(reactContext))
override fun createViewManagers(reactContext: ReactApplicationContext) =
emptyList<ViewManager<*, *>>()
}
JS 端
JavaScript
import { NativeModules } from 'react-native';
const { ToastModule } = NativeModules;
// 调用原生方法
ToastModule.show('Hello from JS!', ToastModule.SHORT);
调用流程
text
JS 代码调用
│
▼
NativeModules.ToastModule.show()
│
▼ (Bridge/JSI 传递)
│
ToastModule.show() ← Android 原生方法执行
│
▼
Toast.makeText().show() ← 原生 Android Toast
七、构建流程的关系
text
npx react-native run-android
│
├──→ 1. 启动 Metro Bundler (JS 打包服务器)
│ index.js → 转译 → bundle
│
└──→ 2. 执行 Gradle 构建 (原生编译)
│
├── 编译 Java/Kotlin → .dex
├── 打包原生资源 → res/
├── 链接 .so 库 (Hermes, JSI, RN核心)
│
├── Debug: APK 不包含 JS Bundle
│ (运行时从 Metro 服务器拉取)
│
└── Release: 执行 bundleReleaseJsAndAssets
│
├── metro bundle → index.android.bundle
└── 打入 APK 的 assets/ 目录
八、node_modules 与 Android 的关系
text
node_modules/
├── react-native/
│ ├── ReactAndroid/ ← RN Android 核心源码
│ │ ├── src/main/java/ ← Java 源码 (UIManager, Bridge等)
│ │ └── src/main/jni/ ← C++ 源码 (JSI, Fabric, TurboModules)
│ └── react.gradle ← 构建脚本
│
├── @react-native-community/xxx/
│ └── android/ ← 该库的 Android 原生代码
│ ├── build.gradle
│ └── src/main/java/
│
└── react-native-xxx/ ← 第三方库
└── android/ ← 自动链接到 android 项目
自动链接 (Autolinking):
groovy
// android/settings.gradle (自动生成)
includeBuild('../node_modules/@react-native/gradle-plugin')
// 自动扫描 node_modules 中包含 android/ 的包
// 并将它们作为 Gradle 子模块链接
九、新架构 vs 旧架构
text
┌──────────── 旧架构 (Bridge) ────────────┐
│ │
│ JS Thread ←──── JSON Bridge ────→ UI Thread │
│ (Hermes) (异步、序列化) (Android) │
│ │
└─────────────────────────────────────────┘
┌──────────── 新架构 (JSI/Fabric) ────────┐
│ │
│ JS Thread ←──── JSI (C++) ────→ UI Thread │
│ (Hermes) (同步、直接引用) (Android) │
│ │
│ + Fabric Renderer (C++ 渲染) │
│ + TurboModules (按需加载原生模块) │
│ + Codegen (类型安全的接口生成) │
│ │
└─────────────────────────────────────────┘
十、总结关系图
text
React Native 项目
│
┌────────────┼────────────┐
▼ ▼ ▼
JavaScript android/ ios/
(业务逻辑) (原生壳) (原生壳)
│ │
│ ┌───────┴────────┐
│ │ │
│ ▼ ▼
│ MainActivity MainApplication
│ (UI容器) (初始化RN引擎)
│ │ │
│ ▼ ▼
│ ReactRootView ReactNativeHost
│ │ │
└────┴───────┬────────┘
▼
ReactInstanceManager
(管理 JS 运行时)
│
┌────┴────┐
▼ ▼
Hermes Bridge/JSI
(JS引擎) (通信层)
│ │
▼ ▼
执行 Bundle 调用 Native
渲染组件 访问设备API
简单来说: android/ 目录就是一个 Android 原生"壳"应用,它负责:
- 初始化 JS 引擎(Hermes)
- 加载 JS Bundle
- 提供 原生 View 容器(ReactRootView)
- 桥接 JS 调用与原生 API
- 渲染 JS 描述的 UI 为原生 Android 组件
从 index.android.bundle 提取业务代码的 Python 工具
一、如何区分业务代码与框架代码
区分原理
text
Bundle 中每个模块通过 __d() 注册,有几个关键特征可以区分:
框架/库代码的特征:
├── 模块路径包含 "node_modules"
├── 模块路径包含 "react-native/Libraries"
├── 模块路径包含 "react/"、"@babel/"、"@react-navigation/" 等
├── 模块ID通常较小(0-499 范围居多)
├── 代码中大量内部API调用(__DEV__、invariant、warning)
└── 通常没有业务相关的字符串(API地址、中文、业务变量名)
业务代码的特征:
├── 模块路径不含 "node_modules"
├── 路径通常以 "src/"、"app/"、"screens/"、"components/" 开头
├── 包含中文字符串、业务API地址
├── 包含自定义命名(HomeScreen、UserProfile、handleLogin)
├── 模块ID通常较大(靠后注册)
└── 引用了 react-native 的组件(View、Text 等)但自身不是框架
Bundle 的三种格式
text
格式1: 开发模式 Bundle(最容易解析)
- 带文件名注释,变量名保留
- __d(function(...){...}, 501, [...], "src/screens/Home.js")
^^^^^^^^^^^^^^^^^^^^^^^^ 有文件名
格式2: 生产模式 Bundle(压缩但可解析)
- 变量名被混淆,无文件名
- __d(function(a,b,c){...}, 501, [...])
格式3: Hermes 字节码 (.hbc)(需要先反编译)
- 二进制格式,不可直接阅读
二、完整 Python 提取工具
Python
#!/usr/bin/env python3
"""
React Native Bundle 业务代码提取器
===================================
从 index.android.bundle 中提取并还原业务代码
使用方法:
python rn_extractor.py index.android.bundle -o output/
python rn_extractor.py index.android.bundle --sourcemap index.android.bundle.map -o output/
python rn_extractor.py app-release.apk -o output/
"""
import re
import os
import sys
import json
import argparse
import zipfile
import textwrap
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, field
# ============================================================
# 数据结构定义
# ============================================================
@dataclass
class ModuleInfo:
"""一个 JS 模块的信息"""
module_id: int
code: str
dependencies: List[int]
filename: Optional[str] = None # 原始文件名(开发模式才有)
guessed_filename: Optional[str] = None # 推测的文件名
is_business: bool = False # 是否是业务代码
category: str = "unknown" # 分类
restored_code: str = "" # 还原后的代码
confidence: float = 0.0 # 业务代码判定置信度
# ============================================================
# 核心解析器
# ============================================================
class BundleParser:
"""解析 React Native Bundle 文件"""
# 框架/库模块的路径关键词
FRAMEWORK_PATTERNS = [
'node_modules', 'react-native/Libraries', 'react-native/index',
'@babel/', '@react-native/', 'react/', 'react-dom/',
'metro-runtime/', 'metro/', 'hermes-', 'jsc-',
'regenerator-runtime', 'core-js/', '@polyfill',
'invariant', 'fbjs/', 'haste-', 'promise/',
'whatwg-fetch', 'event-target-shim', 'abort-controller',
'base64-js/', 'ieee754/', 'buffer/',
]
# 第三方库路径关键词
THIRD_PARTY_PATTERNS = [
'@react-navigation', 'react-redux', 'redux',
'@reduxjs', 'axios', 'lodash', 'moment',
'react-native-', '@react-native-community',
'expo-', '@expo/', 'native-base', 'react-native-paper',
'@react-native-async-storage', 'react-native-gesture-handler',
'react-native-reanimated', 'react-native-screens',
'react-native-safe-area-context', 'react-native-svg',
'react-native-vector-icons', '@gorhom/', 'formik',
'yup', 'i18next', 'react-i18next', '@tanstack',
'zustand', 'mobx', 'immer', 'date-fns',
]
# 业务代码路径特征
BUSINESS_PATTERNS = [
'src/', 'app/', 'screens/', 'components/', 'pages/',
'views/', 'containers/', 'modules/', 'features/',
'hooks/', 'utils/', 'helpers/', 'services/',
'api/', 'store/', 'stores/', 'models/',
'navigation/', 'routes/', 'assets/', 'constants/',
'config/', 'types/', 'interfaces/', 'context/',
'providers/', 'layouts/', 'widgets/', 'common/',
'shared/', 'lib/', 'core/', 'domain/',
'App.js', 'App.tsx', 'index.js', 'index.tsx',
]
# 业务代码中常见的代码特征(用于无文件名时的启发式判断)
BUSINESS_CODE_INDICATORS = [
# 中文字符
r'[\u4e00-\u9fff]',
# 业务API地址
r'https?://(?!(?:facebook|github|google|reactnative|unpkg|cdn))',
# 自定义组件名(大驼峰且不是RN内置的)
r'(?:Screen|Page|Modal|Dialog|Form|Card|List|Detail|Profile|Setting|Dashboard|Header|Footer|Sidebar|Navbar|Tab|Menu)',
# 业务相关函数名
r'(?:handle|fetch|load|submit|update|delete|create|get|set|on|toggle)(?:Login|User|Data|Order|Cart|Payment|Search|Filter|Sort)',
# 路由名称
r'["\'](?:Home|Login|Register|Profile|Settings|Detail|About|Search|Cart|Checkout|Order|Welcome)',
# AsyncStorage 键名(通常是业务相关的)
r'@(?:user|token|auth|settings|cache|app)',
]
# Polyfill 和模块系统的特征
POLYFILL_PATTERNS = [
r'__BUNDLE_START_TIME__',
r'__DEV__\s*=',
r'__METRO_GLOBAL_PREFIX__',
r'function\s+__d\s*\(',
r'function\s+__r\s*\(',
r'var\s+modules\s*=\s*\{\}',
r'regeneratorRuntime',
r'Symbol\.(?:iterator|hasInstance|toPrimitive)',
r'Object\.(?:assign|create|defineProperty|getPrototypeOf)',
r'Array\.(?:from|isArray|of)',
r'Promise\.(?:resolve|reject|all|race)',
]
def __init__(self, bundle_content: str):
self.bundle_content = bundle_content
self.modules: Dict[int, ModuleInfo] = {}
self.module_id_to_name: Dict[int, str] = {}
self.dependency_graph: Dict[int, List[int]] = {}
def parse(self) -> Dict[int, ModuleInfo]:
"""主解析入口"""
print("🔍 开始解析 Bundle...")
print(f" Bundle 大小: {len(self.bundle_content) / 1024 / 1024:.2f} MB")
# Step 1: 提取所有模块
self._extract_modules()
print(f" 发现 {len(self.modules)} 个模块")
# Step 2: 分类每个模块
self._classify_modules()
# Step 3: 利用依赖关系优化分类
self._refine_classification_by_dependencies()
# Step 4: 还原代码
self._restore_code()
return self.modules
def _extract_modules(self):
"""从 bundle 中提取所有 __d() 模块定义"""
# 模式1: 带文件名的模式 (开发模式)
# __d(function(...){...}, moduleId, [...], "filename")
pattern_with_name = re.compile(
r'__d\(\s*function\s*\(([^)]*)\)\s*\{',
re.MULTILINE
)
# 使用更精确的方法:逐个查找 __d( 调用
pos = 0
module_starts = []
while True:
idx = self.bundle_content.find('__d(function', pos)
if idx == -1:
idx = self.bundle_content.find('__d(\nfunction', pos)
if idx == -1:
idx = self.bundle_content.find('__d( function', pos)
if idx == -1:
break
module_starts.append(idx)
pos = idx + 10
print(f" 找到 {len(module_starts)} 个 __d() 调用")
for i, start in enumerate(module_starts):
try:
module = self._parse_single_module(start, module_starts, i)
if module:
self.modules[module.module_id] = module
except Exception as e:
pass # 解析失败的模块跳过
def _parse_single_module(self, start: int, all_starts: list, index: int) -> Optional[ModuleInfo]:
"""解析单个 __d() 模块"""
content = self.bundle_content
# 确定模块结束位置
if index + 1 < len(all_starts):
# 下一个 __d 之前的位置
search_end = all_starts[index + 1]
else:
search_end = len(content)
module_text = content[start:search_end]
# 提取函数体:找到匹配的花括号
func_body_start = module_text.find('{')
if func_body_start == -1:
return None
brace_count = 0
func_body_end = -1
in_string = False
string_char = ''
escape_next = False
for j in range(func_body_start, len(module_text)):
ch = module_text[j]
if escape_next:
escape_next = False
continue
if ch == '\\':
escape_next = True
continue
if in_string:
if ch == string_char:
in_string = False
continue
if ch in ('"', "'", '`'):
in_string = True
string_char = ch
continue
if ch == '{':
brace_count += 1
elif ch == '}':
brace_count -= 1
if brace_count == 0:
func_body_end = j
break
if func_body_end == -1:
return None
# 提取函数体代码
code = module_text[func_body_start + 1:func_body_end].strip()
# 提取函数体后面的元数据: }, moduleId, [deps], "filename")
metadata = module_text[func_body_end + 1:].strip()
# 解析 moduleId
id_match = re.search(r',\s*(\d+)\s*,', metadata)
if not id_match: