GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

逆向工程 --- 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 原生"壳"应用,它负责:

  1. 初始化 JS 引擎(Hermes)
  2. 加载 JS Bundle
  3. 提供 原生 View 容器(ReactRootView)
  4. 桥接 JS 调用与原生 API
  5. 渲染 JS 描述的 UI 为原生 Android 组件

 

 

index.android.bundle 深度解析

一、这个文件到底是什么?

text
index.android.bundle 本质上就是一个【超大的 JavaScript 文件】
它把你项目中所有的 JS/JSX/TS/TSX 代码打包压缩成一个文件

通俗比喻

text
想象你写了一本书:
├── 第1章.docx   (App.js)
├── 第2章.docx   (HomeScreen.js)  
├── 第3章.docx   (LoginScreen.js)
├── 附录A.docx   (react 框架代码)
├── 附录B.docx   (react-native 框架代码)
├── 附录C.docx   (第三方库代码)
└── 附录D-Z.docx (更多依赖...)

index.android.bundle = 把上面所有文件合并成【一本大书】

二、为什么这么大?

典型大小分布

text
┌─────────────────────────────────────────────────┐
│         index.android.bundle (典型 2-10MB+)      │
│                                                  │
│  ┌─────────────────────────────────┐             │
│  │  React 框架源码        ~300KB   │  ██████     │
│  └─────────────────────────────────┘             │
│  ┌─────────────────────────────────┐             │
│  │  React Native 框架     ~800KB   │  █████████  │
│  └─────────────────────────────────┘             │
│  ┌─────────────────────────────────┐             │
│  │  第三方库 (navigation,          │             │
│  │  axios, redux, UI库等)  ~2-5MB  │  ██████████████████ │
│  └─────────────────────────────────┘             │
│  ┌─────────────────────────────────┐             │
│  │  Polyfills (兼容代码)   ~100KB  │  ██         │
│  └─────────────────────────────────┘             │
│  ┌─────────────────────────────────┐             │
│  │  你的业务代码           ~50-500KB│  ████       │
│  └─────────────────────────────────┘             │
│                                                  │
│  👆 你的代码通常只占 5%-15%                       │
└─────────────────────────────────────────────────┘

大的原因(通俗解释)

text
原因1:【全家桶打包】
  你只用了 lodash 的一个函数 _.get()
  但整个 lodash 库可能都被打进去了(取决于配置)
  好比:你只想要一首歌,但下载了整张专辑

原因2:【框架本身很大】
  React + React Native 运行时代码本身就几百KB
  好比:你写了一页Word文档,但Word软件本身就要几百MB

原因3:【转译膨胀】
  你写的简洁的 ES6+/JSX 代码,被 Babel 转译后会变长
  好比:中文翻译成英文,字数往往会变多

原因4:【没有 Tree Shaking】
  Metro bundler 的 tree shaking 能力不如 webpack
  很多用不到的代码也被打包进来了

原因5:【开发模式更大】
  Debug 模式包含大量调试信息、警告、错误提示代码
  Release 模式会小很多

三、Bundle 内部结构详解

实际打开 bundle 文件,你会看到这样的结构:

JavaScript
// ============================================
// 第1部分:Polyfills(兼容层)
// ============================================
// 让旧引擎支持新语法,比如 Promise、Symbol、Array.from 等
(function(global) {
  // ... 几千行兼容代码
})(typeof global !== 'undefined' ? global : window);

// ============================================
// 第2部分:模块系统定义
// ============================================
// Metro 的模块加载器,类似于一个迷你的 require() 系统
var __BUNDLE_START_TIME__ = Date.now();
var modules = {};  // 所有模块存在这里

// 定义模块的函数
function __d(factory, moduleId, dependencyMap) {
  modules[moduleId] = {
    factory: factory,      // 模块的代码(函数形式)
    dependencies: dependencyMap,
    isInitialized: false,
    exports: {}
  };
}

// 加载模块的函数  
function __r(moduleId) {
  var module = modules[moduleId];
  if (!module.isInitialized) {
    module.factory(/* ... */);  // 执行模块代码
    module.isInitialized = true;
  }
  return module.exports;
}

// ============================================
// 第3部分:所有模块定义(这是最大的部分)
// ============================================

// 模块0: 入口文件 index.js
__d(function(require, module, exports) {
  var _AppRegistry = require(1);    // react-native
  var _App = require(500);          // 你的 App.js
  _AppRegistry.registerComponent('MyApp', function() { return _App; });
}, 0, [1, 500]);

// 模块1: react-native/index.js  
__d(function(require, module, exports) {
  // React Native 框架导出
  exports.View = require(42);
  exports.Text = require(43);
  exports.Image = require(44);
  // ... 几百个导出
}, 1, [42, 43, 44, ...]);

// 模块42: View 组件实现
__d(function(require, module, exports) {
  // ... 几百行 View 的实现代码
}, 42, [...]);

// ... 中间可能有几千个模块 (编号 2-499) ...
// 都是 React、React Native、第三方库的代码

// 模块500: 你的 App.js ← 你的业务代码从这里开始
__d(function(require, module, exports) {
  var _react = require(200);
  var _reactNative = require(1);
  var _HomeScreen = require(501);
  
  function App() {
    return _react.createElement(
      _reactNative.View, null,
      _react.createElement(_HomeScreen, null)
    );
  }
  module.exports = App;
}, 500, [200, 1, 501]);

// 模块501: 你的 HomeScreen.js
__d(function(require, module, exports) {
  // ... 你的业务代码
}, 501, [...]);

// ============================================
// 第4部分:入口调用
// ============================================
__r(0);  // 执行模块0(index.js),启动整个应用

通俗解释每个部分

text
┌─────────────────────────────────────────────────────┐
│                                                     │
│  第1部分 - Polyfills(垫片)                         │
│  通俗:给旧手机装"翻译器",让它能理解新语法           │
│  例如:手机不认识 Promise,polyfill 教它认识          │
│  占比:约 2-5%                                      │
│                                                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  第2部分 - 模块系统(__d 和 __r)                    │
│  通俗:一个"图书管理员",负责登记和查找所有代码模块    │
│  __d = define = 登记一本书(编号+内容)               │
│  __r = require = 借阅一本书(按编号查找)             │
│  占比:约 1%                                        │
│                                                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  第3部分 - 所有模块(最大!)                        │
│  通俗:图书馆里所有的书,从框架教材到你写的日记       │
│                                                     │
│  模块 0-199:   React 框架代码                       │
│  模块 200-399: React Native 框架代码                 │
│  模块 400-499: 第三方库代码                          │
│  模块 500+:    你的业务代码  ← 你真正写的东西         │
│                                                     │
│  占比:约 90-95%                                    │
│                                                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  第4部分 - 启动调用                                  │
│  通俗:按下"启动按钮",让程序开始运行                 │
│  __r(0) 就像按下电源键                               │
│  占比:约 0.01%                                     │
│                                                     │
└─────────────────────────────────────────────────────┘

四、如何提取/查看业务层代码

方法1:生成 Source Map 反解

Bash
# 生成 bundle 和 source map
npx react-native bundle \
  --platform android \
  --dev false \
  --entry-file index.js \
  --bundle-output output/index.android.bundle \
  --sourcemap-output output/index.android.bundle.map \
  --assets-dest output/assets
Bash
# 使用 source-map 工具还原
npm install -g source-map-cli

# 查看 source map 中包含的所有原始文件列表
source-map ls output/index.android.bundle.map

用 Node.js 脚本提取所有原始源码:

JavaScript
// extract_sources.js
const fs = require('fs');
const path = require('path');
const { SourceMapConsumer } = require('source-map');

async function extractSources() {
  const rawMap = JSON.parse(
    fs.readFileSync('output/index.android.bundle.map', 'utf8')
  );

  // sourcesContent 包含所有原始文件的源码
  rawMap.sources.forEach((sourcePath, index) => {
    const content = rawMap.sourcesContent?.[index];
    if (!content) return;

    // 只提取业务代码(过滤 node_modules)
    if (sourcePath.includes('node_modules')) return;

    console.log(`\n${'='.repeat(60)}`);
    console.log(`📄 文件: ${sourcePath}`);
    console.log('='.repeat(60));
    console.log(content);

    // 保存到文件
    const outputPath = path.join('extracted', sourcePath);
    fs.mkdirSync(path.dirname(outputPath), { recursive: true });
    fs.writeFileSync(outputPath, content);
  });
}

extractSources();
Bash
node extract_sources.js
# 结果会保存到 extracted/ 目录,还原出原始的 JS/TS 源码

方法2:从 Bundle 中直接定位业务代码

JavaScript
// locate_business_code.js
const fs = require('fs');
const bundle = fs.readFileSync('index.android.bundle', 'utf8');

// 方法A:通过搜索你知道的字符串
const searchTerms = [
  'HomeScreen',
  'LoginScreen', 
  'your unique string',
  'your API endpoint'
];

searchTerms.forEach(term => {
  const index = bundle.indexOf(term);
  if (index !== -1) {
    // 提取该位置前后的代码
    const start = Math.max(0, bundle.lastIndexOf('__d(function', index));
    const end = bundle.indexOf('\n__d(function', index);
    console.log(`Found "${term}" in module:`);
    console.log(bundle.substring(start, end));
    console.log('---');
  }
});

方法3:生成可读的(非压缩)Bundle

Bash
# 生成开发模式 bundle(不压缩、不混淆、保留注释)
npx react-native bundle \
  --platform android \
  --dev true \               # ← 关键:开发模式
  --entry-file index.js \
  --bundle-output output/readable.bundle
Bash
# 或者使用 --minify false
npx react-native bundle \
  --platform android \
  --dev false \
  --minify false \           # ← 不压缩
  --entry-file index.js \
  --bundle-output output/readable.bundle

开发模式 bundle 的样子(可读):

JavaScript
// 开发模式下每个模块都有清晰的注释和原始变量名
__d(function(require, _$$_REQUIRE, module, exports, _dependencyMap) {
  'use strict';
  
  var _react = require(_dependencyMap[0], "react");
  var _reactNative = require(_dependencyMap[1], "react-native");
  
  // 你的 HomeScreen 组件 - 变量名保留!
  function HomeScreen({ navigation }) {
    const [userName, setUserName] = _react.useState('');
    const [isLoading, setIsLoading] = _react.useState(false);
    
    const handleLogin = async function() {
      setIsLoading(true);
      const response = await fetch('https://api.example.com/login');
      // ... 完整的业务逻辑,清晰可读
    };
    
    return _react.createElement(
      _reactNative.View,
      { style: styles.container },
      _react.createElement(
        _reactNative.Text,
        null,
        "Welcome, " + userName
      )
    );
  }
  
  module.exports = HomeScreen;

}, 501, [200, 1], "HomeScreen.js");  // ← 注意最后有文件名!

方法4:解析 Bundle 的模块注册表

JavaScript
// parse_modules.js  —— 解析 bundle 中所有模块
const fs = require('fs');
const bundle = fs.readFileSync('output/readable.bundle', 'utf8');

// 正则匹配所有 __d() 调用
const moduleRegex = /__d\(function\s*\([^)]*\)\s*\{([\s\S]*?)\}\s*,\s*(\d+)\s*,\s*\[([\d,\s]*)\](?:\s*,\s*"([^"]*)")?\)/g;

let match;
const modules = [];

while ((match = moduleRegex.exec(bundle)) !== null) {
  modules.push({
    id: parseInt(match[2]),
    dependencies: match[3].split(',').map(Number),
    filename: match[4] || 'unknown',
    codeLength: match[1].length,
    code: match[1]
  });
}

// 分类统计
const businessModules = modules.filter(m => 
  m.filename && 
  !m.filename.includes('node_modules') &&
  m.filename !== 'unknown'
);

const frameworkModules = modules.filter(m => 
  m.filename?.includes('node_modules/react-native')
);

const thirdPartyModules = modules.filter(m => 
  m.filename?.includes('node_modules') &&
  !m.filename?.includes('react-native') &&
  !m.filename?.includes('react/')
);

console.log(`总模块数: ${modules.length}`);
console.log(`框架模块: ${frameworkModules.length}`);
console.log(`第三方库: ${thirdPartyModules.length}`);
console.log(`业务模块: ${businessModules.length}`);

console.log('\n📦 你的业务文件:');
businessModules.forEach(m => {
  console.log(`  [${m.id}] ${m.filename} (${(m.codeLength/1024).toFixed(1)}KB)`);
});

// 保存业务代码
businessModules.forEach(m => {
  const outputPath = `extracted/${m.filename}`;
  fs.mkdirSync(require('path').dirname(outputPath), { recursive: true });
  fs.writeFileSync(outputPath, m.code);
});

方法5:从 APK 中提取 Bundle

Bash
# 1. 拿到 APK 文件
# 开发构建的 APK 在:
# android/app/build/outputs/apk/debug/app-debug.apk

# 2. APK 本质是 ZIP,直接解压
unzip app-release.apk -d extracted_apk/

# 3. Bundle 文件在 assets 目录
ls extracted_apk/assets/
# → index.android.bundle   ← 就是这个文件

# 4. 如果是 Hermes 编译的(.hbc 字节码)
file extracted_apk/assets/index.android.bundle
# 如果输出 "Hermes JavaScript bytecode" 则是字节码格式

如果是 Hermes 字节码 (.hbc),需要反编译:

Bash
# 安装 hermes 反编译工具
# 方法A:使用 hbcdump
npx hermes --dump-bytecode extracted_apk/assets/index.android.bundle > bytecode_dump.txt

# 方法B:使用 hbc-decompiler (社区工具)
npm install -g hbc-decompiler
hbc-decompiler extracted_apk/assets/index.android.bundle output.js

# 方法C:使用 hermes-dec
pip install hermes-dec
hermes-dec extracted_apk/assets/index.android.bundle -o output.js

五、生成原始 JS 代码的完整方案

方案A:最佳方案 — Source Map 还原

Bash
# 步骤1:确保构建时生成 source map
# 修改 android/app/build.gradle
project.ext.react = [
    enableHermes: true,
    hermesFlagsRelease: ["-O", "-output-source-map"],  // 添加这个
    extraPackagerArgs: ["--sourcemap-output", "sourcemap.js.map"]
]
JavaScript
// 步骤2:完整还原脚本 restore_source.js
const fs = require('fs');
const path = require('path');

async function restoreFromSourceMap(mapFile, outputDir) {
  const { SourceMapConsumer } = require('source-map');
  const rawMap = JSON.parse(fs.readFileSync(mapFile, 'utf8'));
  
  console.log(`📊 Source Map 包含 ${rawMap.sources.length} 个源文件\n`);
  
  let businessFileCount = 0;
  let frameworkFileCount = 0;
  
  for (let i = 0; i < rawMap.sources.length; i++) {
    const sourcePath = rawMap.sources[i];
    const content = rawMap.sourcesContent?.[i];
    
    if (!content) continue;
    
    // 分类
    const isNodeModule = sourcePath.includes('node_modules');
    
    if (isNodeModule) {
      frameworkFileCount++;
      continue; // 跳过框架代码,只要业务代码
    }
    
    businessFileCount++;
    
    // 还原文件
    const cleanPath = sourcePath
      .replace(/^\//, '')
      .replace(/\.\.\//g, '');
    const fullOutputPath = path.join(outputDir, cleanPath);
    
    fs.mkdirSync(path.dirname(fullOutputPath), { recursive: true });
    fs.writeFileSync(fullOutputPath, content);
    
    console.log(`✅ 还原: ${cleanPath} (${(content.length/1024).toFixed(1)}KB)`);
  }
  
  console.log(`\n📊 统计:`);
  console.log(`   业务文件: ${businessFileCount} 个`);
  console.log(`   框架文件: ${frameworkFileCount} 个(已跳过)`);
  console.log(`\n📁 输出目录: ${outputDir}`);
}

restoreFromSourceMap('index.android.bundle.map', './restored_source');

运行结果示例:

text
✅ 还原: src/App.js (3.2KB)
✅ 还原: src/screens/HomeScreen.js (5.1KB)
✅ 还原: src/screens/LoginScreen.tsx (4.8KB)
✅ 还原: src/components/Button.js (1.2KB)
✅ 还原: src/utils/api.js (2.3KB)
✅ 还原: src/store/userSlice.js (1.8KB)

📊 统计:
   业务文件: 47 个
   框架文件: 1,234 个(已跳过)

📁 输出目录: ./restored_source

方案B:直接生成可读 Bundle + 解析

Bash
# 一键生成可读的、带文件名标注的 bundle
npx react-native bundle \
  --platform android \
  --dev true \
  --minify false \
  --entry-file index.js \
  --bundle-output readable.bundle \
  --sourcemap-output readable.bundle.map

方案C:使用 Metro 的模块图分析

JavaScript
// analyze_bundle.js
const Metro = require('metro');
const path = require('path');

async function analyzeDependencies() {
  const config = await Metro.loadConfig();
  
  const graph = await Metro.graph(config, {
    entryFile: 'index.js',
    platform: 'android',
    dev: false,
  });
  
  // graph.dependencies 包含所有模块信息
  const allModules = Array.from(graph.dependencies.entries());
  
  const businessModules = allModules.filter(([filePath]) => 
    !filePath.includes('node_modules')
  );
  
  console.log('📦 业务模块依赖图:');
  businessModules.forEach(([filePath, moduleInfo]) => {
    console.log(`\n📄 ${path.relative(process.cwd(), filePath)}`);
    console.log(`   依赖: ${moduleInfo.dependencies.size} 个模块`);
    moduleInfo.dependencies.forEach(dep => {
      console.log(`     → ${dep.absolutePath}`);
    });
  });
}

analyzeDependencies();

六、JSX 转译对照表

你写的代码 vs Bundle 中的代码:

text
┌──────────────────────────────────┬──────────────────────────────────────────┐
│        你写的 JSX 代码            │         Bundle 中转译后的代码              │
├──────────────────────────────────┼──────────────────────────────────────────┤
│                                  │                                          │
│ <View style={styles.container}>  │ React.createElement(                     │
│   <Text>Hello</Text>             │   View,                                  │
│   <Button onPress={handleClick}  │   {style: styles.container},             │
│     title="Click"                │   React.createElement(Text, null,        │
│   />                             │     "Hello"),                            │
│ </View>                          │   React.createElement(Button,            │
│                                  │     {onPress: handleClick,               │
│                                  │      title: "Click"})                    │
│                                  │ )                                        │
│                                  │                                          │
├──────────────────────────────────┼──────────────────────────────────────────┤
│                                  │                                          │
│ const App = () => {              │ var App = function App() {               │
│   const [count, setCount]        │   var _useState = React.useState(0),     │
│     = useState(0);               │       count = _useState[0],              │
│   return <Text>{count}</Text>;   │       setCount = _useState[1];           │
│ };                               │   return React.createElement(            │
│                                  │     Text, null, count);                  │
│                                  │ };                                       │
│                                  │                                          │
├──────────────────────────────────┼──────────────────────────────────────────┤
│                                  │                                          │
│ async function fetchData() {     │ function fetchData() {                   │
│   const res = await fetch(url);  │   return regeneratorRuntime              │
│   const data = await res.json(); │     .async(function fetchData$() {       │
│   return data;                   │       var res, data;                     │
│ }                                │       return regeneratorRuntime          │
│                                  │         .wrap(function(_ctx) {           │
│                                  │           switch(_ctx.prev=_ctx.next) {  │
│                                  │             case 0:                      │
│                                  │               _ctx.next = 2;            │
│                                  │               return fetch(url);        │
│                                  │             case 2:                      │
│                                  │               res = _ctx.sent;          │
│                                  │               // ...更多                 │
│                                  │           }                              │
│                                  │         });                              │
│                                  │     });                                  │
│                                  │ }                                        │
│                                  │                                          │
├──────────────────────────────────┼──────────────────────────────────────────┤
│                                  │                                          │
│ import { View } from             │ var _reactNative =                       │
│   'react-native';                │   require(_dependencyMap[0]);            │
│ import Home from './Home';       │ var _Home =                              │
│                                  │   require(_dependencyMap[1]);            │
│                                  │                                          │
│ export default App;              │ module.exports = App;                    │
│                                  │                                          │
└──────────────────────────────────┴──────────────────────────────────────────┘

七、实用工具和命令总结

Bash
# 📊 查看 bundle 大小分析
npx react-native-bundle-visualizer

# 📄 生成可读 bundle(不压缩)
npx react-native bundle --platform android --dev true --minify false \
  --entry-file index.js --bundle-output readable.bundle

# 🗺️ 生成 source map
npx react-native bundle --platform android --dev false \
  --entry-file index.js \
  --bundle-output output.bundle \
  --sourcemap-output output.bundle.map

# 📦 从 APK 提取 bundle
unzip app-release.apk -d extracted/ && cat extracted/assets/index.android.bundle

# 🔍 检查是否是 Hermes 字节码
file assets/index.android.bundle
# 如果显示 "data" 或 "Hermes" → 是字节码,需要反编译
# 如果显示 "ASCII text" → 是普通 JS,可以直接阅读

# 🔄 反编译 Hermes 字节码
npx hermes --dump-bytecode index.android.bundle > dump.txt

完整提取流程图

text
                APK 文件
            解压 (unzip)
     assets/index.android.bundle
            ┌──────┴──────┐
            ▼             ▼
        普通 JS        Hermes 字节码
        (可读)         (.hbc 格式)
            │             │
            │         反编译工具
            │         hermes-dec
            │             │
            ▼             ▼
        Bundle 文本 (JavaScript)
         ┌─────────┴─────────┐
         ▼                   ▼
    有 Source Map          无 Source Map
         │                   │
    SourceMapConsumer     正则解析 __d()
         │                   │
         ▼                   ▼
    完美还原原始源码     提取模块代码
    (包括 TS/JSX)       (转译后的 JS)
         │                   │
    过滤 node_modules    过滤框架模块ID
         │                   │
         ▼                   ▼
      ✅ 你的业务代码        ✅ 你的业务代码
      (原始格式)            (转译后格式)
 
 
 

从 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:
            return None
        module_id = int(id_match.group(1))

        # 解析依赖数组
        deps = []
        deps_match = re.search(r'\[\s*([\d\s,]*)\s*\]', metadata)
        if deps_match and deps_match.group(1).strip():
            deps = [int(d.strip()) for d in deps_match.group(1).split(',') if d.strip()]

        # 解析文件名(如果有)
        filename = None
        name_match = re.search(r',\s*"([^"]+\.(?:js|jsx|ts|tsx|json))"\s*\)', metadata)
        if name_match:
            filename = name_match.group(1)

        return ModuleInfo(
            module_id=module_id,
            code=code,
            dependencies=deps,
            filename=filename,
        )

    def _classify_modules(self):
        """分类每个模块"""
        for mid, module in self.modules.items():
            score = self._calculate_business_score(module)
            module.confidence = score

            if module.filename:
                # 有文件名的情况,直接判断
                if any(p in module.filename for p in self.FRAMEWORK_PATTERNS[:5]):
                    module.category = "framework"
                    module.is_business = False
                elif any(p in module.filename for p in self.THIRD_PARTY_PATTERNS):
                    module.category = "third_party"
                    module.is_business = False
                elif 'node_modules' in module.filename:
                    module.category = "third_party"
                    module.is_business = False
                else:
                    module.category = "business"
                    module.is_business = True
            else:
                # 无文件名,用启发式评分
                if score >= 0.5:
                    module.category = "business"
                    module.is_business = True
                elif score >= 0.3:
                    module.category = "possible_business"
                    module.is_business = True  # 宁可多取
                else:
                    module.category = "framework_or_lib"
                    module.is_business = False

    def _calculate_business_score(self, module: ModuleInfo) -> float:
        """
        计算一个模块是业务代码的可能性分数 (0.0 - 1.0)
        多维度综合评估
        """
        score = 0.0
        code = module.code
        code_len = len(code)

        if code_len == 0:
            return 0.0

        # ---- 维度1: 文件名分析 (权重最高) ----
        if module.filename:
            fn = module.filename
            if any(p in fn for p in self.BUSINESS_PATTERNS):
                score += 0.4
            if 'node_modules' in fn:
                score -= 0.6
            if any(p in fn for p in self.FRAMEWORK_PATTERNS):
                score -= 0.5

        # ---- 维度2: 中文字符检测 ----
        chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', code))
        if chinese_chars > 0:
            score += min(0.3, chinese_chars * 0.02)

        # ---- 维度3: 业务代码特征 ----
        for pattern in self.BUSINESS_CODE_INDICATORS:
            matches = re.findall(pattern, code, re.IGNORECASE)
            if matches:
                score += min(0.15, len(matches) * 0.03)

        # ---- 维度4: 自定义组件/函数名检测 ----
        # 大驼峰命名的自定义组件(排除 React Native 内置组件)
        rn_builtins = {
            'View', 'Text', 'Image', 'ScrollView', 'FlatList',
            'TouchableOpacity', 'TouchableHighlight', 'TextInput',
            'Modal', 'ActivityIndicator', 'Switch', 'StatusBar',
            'StyleSheet', 'Platform', 'Dimensions', 'Alert',
            'Animated', 'Easing', 'LayoutAnimation', 'Linking',
            'AppState', 'BackHandler', 'Keyboard', 'PixelRatio',
            'Component', 'PureComponent', 'Fragment', 'Suspense',
            'React', 'ReactNative', 'SafeAreaView', 'SectionList',
            'VirtualizedList', 'Pressable', 'RefreshControl',
        }
        custom_components = set(re.findall(
            r'\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b', code
        )) - rn_builtins
        if custom_components:
            score += min(0.2, len(custom_components) * 0.03)

        # ---- 维度5: API 调用检测 ----
        api_calls = re.findall(
            r'(?:fetch|axios|XMLHttpRequest)\s*\(\s*["\']https?://(?!(?:facebook|github|google|reactnative))',
            code, re.IGNORECASE
        )
        if api_calls:
            score += min(0.15, len(api_calls) * 0.05)

        # ---- 维度6: 路由/导航相关 ----
        navigation_patterns = re.findall(
            r'(?:navigation\.navigate|navigation\.push|navigation\.goBack|'
            r'createStackNavigator|createBottomTabNavigator|Stack\.Screen|Tab\.Screen)\s*\(',
            code
        )
        if navigation_patterns:
            score += 0.15

        # ---- 维度7: 代码复杂度启发 ----
        # 框架代码通常有大量的 typeof、invariant、Object.defineProperty
        framework_indicators = len(re.findall(
            r'(?:typeof\s+\w+\s*[!=]==|invariant\(|'
            r'Object\.defineProperty|Object\.create\(|'
            r'__DEV__|process\.env\.NODE_ENV|'
            r'throw\s+new\s+(?:TypeError|RangeError|Error)\s*\(\s*["\'])',
            code
        ))
        if framework_indicators > 5:
            score -= min(0.3, framework_indicators * 0.03)

        # ---- 维度8: Polyfill 检测 ----
        for pattern in self.POLYFILL_PATTERNS:
            if re.search(pattern, code):
                score -= 0.2
                break

        # ---- 维度9: 代码长度启发 ----
        # 业务代码通常适中长度,超长代码更可能是库
        if code_len > 50000:
            score -= 0.1
        elif code_len < 100:
            score -= 0.1  # 太短的通常是 re-export

        # ---- 维度10: import/require 分析 ----
        # 业务代码通常 require 较少的模块
        require_count = len(re.findall(r'require\s*\(', code))
        if require_count > 30:
            score -= 0.1  # 大量 require 更像是框架入口

        # 归一化到 0-1
        return max(0.0, min(1.0, score))

    def _refine_classification_by_dependencies(self):
        """利用依赖关系优化分类"""
        # 如果一个模块被多个业务模块依赖,它更可能是业务代码
        # 如果一个模块只被框架模块依赖,它更可能是框架代码
        
        dependency_count = {}  # 被谁依赖
        business_dep_count = {}  # 被多少个业务模块依赖
        
        for mid, module in self.modules.items():
            for dep_id in module.dependencies:
                dependency_count[dep_id] = dependency_count.get(dep_id, 0) + 1
                if module.is_business:
                    business_dep_count[dep_id] = business_dep_count.get(dep_id, 0) + 1

        # 如果一个未确定的模块被至少2个业务模块依赖,提升它的分数
        for mid, module in self.modules.items():
            if module.category in ("possible_business", "unknown"):
                biz_deps = business_dep_count.get(mid, 0)
                if biz_deps >= 2:
                    module.is_business = True
                    module.category = "business"
                    module.confidence = min(1.0, module.confidence + 0.2)

    def _restore_code(self):
        """还原代码的可读性"""
        for mid, module in self.modules.items():
            if module.is_business:
                module.restored_code = self._restore_single_module(module)

    def _restore_single_module(self, module: ModuleInfo) -> str:
        """还原单个模块的代码"""
        code = module.code

        # Step 1: 提取参数名映射
        # __d(function(global, require, _$$_IMPORT_DEFAULT, module, exports, _dependencyMap)
        # 或者压缩后: __d(function(a,b,c,d,e,f)

        # Step 2: 还原 require 调用
        code = self._restore_requires(code, module)

        # Step 3: 还原 JSX(createElement → JSX 语法)
        code = self._restore_jsx(code)

        # Step 4: 格式化代码
        code = self._format_code(code)

        # Step 5: 添加文件头注释
        header = self._generate_header(module)

        return header + code

    def _restore_requires(self, code: str, module: ModuleInfo) -> str:
        """还原 require 调用为 import 语句"""
        imports = []
        
        # 匹配模式: var _xxx = require(_dependencyMap[0], "module-name")
        pattern1 = re.compile(
            r'var\s+(\w+)\s*=\s*(?:_interopRequireDefault\()?'
            r'require\s*\(\s*(?:_dependencyMap\[\d+\]|\d+)\s*'
            r'(?:,\s*"([^"]+)")?\s*\)\)?;?'
        )
        
        # 匹配模式: var _xxx = require(数字)
        pattern2 = re.compile(
            r'var\s+(\w+)\s*=\s*(?:_interopRequireDefault\()?'
            r'require\s*\(\s*(\d+)\s*\)\)?;?'
        )
        
        # 匹配模式: var { xxx, yyy } = require(...)
        pattern3 = re.compile(
            r'var\s*\{([^}]+)\}\s*=\s*require\s*\(\s*(?:_dependencyMap\[\d+\]|\d+)\s*'
            r'(?:,\s*"([^"]+)")?\s*\);?'
        )

        def replace_require_with_import(match):
            var_name = match.group(1)
            module_name = match.group(2) if match.lastindex >= 2 else None

            if not module_name:
                # 尝试通过依赖 ID 查找名称
                id_match = re.search(r'(\d+)', match.group(0))
                if id_match:
                    dep_id = int(id_match.group(1))
                    dep_module = self.modules.get(dep_id)
                    if dep_module and dep_module.filename:
                        module_name = dep_module.filename
                    else:
                        module_name = f'__module_{dep_id}'

            # 清理变量名
            clean_name = var_name
            if clean_name.startswith('_') and not clean_name.startswith('__'):
                clean_name = clean_name[1:]
            if clean_name.endswith('Default'):
                clean_name = clean_name[:-7]
            if clean_name.startswith('interopRequireDefault'):
                return match.group(0)  # 跳过辅助函数

            return f"import {clean_name} from '{module_name or 'unknown'}';"

        code = pattern1.sub(replace_require_with_import, code)
        
        # 还原解构 require
        def replace_destructured_require(match):
            names = match.group(1).strip()
            module_name = match.group(2) if match.lastindex >= 2 else 'unknown'
            return f"import {{ {names} }} from '{module_name}';"
        code = pattern3.sub(replace_destructured_require, code)
        return code
    def _restore_jsx(self, code: str) -> str:
        """还原 React.createElement 为 JSX 语法(简化版)"""
        # 简单替换:_react.createElement(View, {style: xxx}, children)
        # → <View style={xxx}>{children}</View>
        # 注意:完美还原 JSX 是非常复杂的,这里做近似还原
        # 替换无 props 无 children: createElement(Component, null)
        code = re.sub(
            r'(?:_react(?:Default)?(?:\[["\']\w+["\']\])?\.)?createElement\(\s*(\w+)\s*,\s*null\s*\)',
            r'<\1 />',
            code
        )
        # 替换有文本 children: createElement(Text, null, "hello")
        code = re.sub(
            r'(?:_react(?:Default)?(?:\[["\']\w+["\']\])?\.)?createElement\(\s*(\w+)\s*,\s*null\s*,\s*(["\'][^"\']*["\'])\s*\)',
            r'<\1>{\2}</\1>',
            code
        )
        # 标记未能还原的 createElement
        code = re.sub(
            r'(?:_react(?:Default)?(?:\[["\']\w+["\']\])?\.)?createElement\(',
            '/* JSX: */ React.createElement(',
            code
        )
        return code
    def _format_code(self, code: str) -> str:
        """基本的代码格式化"""
        # 在语句之间添加换行
        code = re.sub(r';\s*(?=\S)', ';\n', code)
        # 在花括号后添加换行
        code = re.sub(r'\{\s*(?=\S)', '{\n  ', code)
        code = re.sub(r'(?<=\S)\s*\}', '\n}', code)
        # 清理多余空行
        code = re.sub(r'\n{3,}', '\n\n', code)
        # 基础缩进(简单版)
        lines = code.split('\n')
        formatted_lines = []
        indent_level = 0
        for line in lines:
            stripped = line.strip()
            if not stripped:
                formatted_lines.append('')
                continue
            # 减少缩进的标记
            if stripped.startswith('}') or stripped.startswith(')') or stripped.startswith(']'):
                indent_level = max(0, indent_level - 1)
            formatted_lines.append('  ' * indent_level + stripped)
            # 增加缩进的标记
            open_count = stripped.count('{') + stripped.count('(') + stripped.count('[')
            close_count = stripped.count('}') + stripped.count(')') + stripped.count(']')
            indent_level += open_count - close_count
            indent_level = max(0, indent_level)
        return '\n'.join(formatted_lines)
    def _generate_header(self, module: ModuleInfo) -> str:
        """生成文件头注释"""
        filename = module.filename or module.guessed_filename or f"module_{module.module_id}"
        lines = [
            f"/**",
            f" * 文件: {filename}",
            f" * 模块ID: {module.module_id}",
            f" * 分类: {module.category}",
            f" * 置信度: {module.confidence:.1%}",
            f" * 依赖模块: {module.dependencies}",
        ]
        # 列出依赖的文件名
        dep_names = []
        for dep_id in module.dependencies:
            dep_module = self.modules.get(dep_id)
            if dep_module:
                name = dep_module.filename or f"module_{dep_id}"
                dep_names.append(f"  {dep_id} → {name}")
        if dep_names:
            lines.append(f" * 依赖详情:")
            for dn in dep_names[:10]:
                lines.append(f" *   {dn}")
            if len(dep_names) > 10:
                lines.append(f" *   ... 还有 {len(dep_names) - 10} 个")
        lines.append(f" */")
        lines.append(f"")
        return '\n'.join(lines) + '\n'
# ============================================================
# Source Map 解析器
# ============================================================
class SourceMapParser:
    """使用 Source Map 完美还原源码"""
    def __init__(self, sourcemap_path: str):
        print(f"📦 加载 Source Map: {sourcemap_path}")
        with open(sourcemap_path, 'r', encoding='utf-8') as f:
            self.sourcemap = json.load(f)
        self.sources = self.sourcemap.get('sources', [])
        self.sources_content = self.sourcemap.get('sourcesContent', [])
        print(f"   包含 {len(self.sources)} 个源文件")
    def extract_business_sources(self, output_dir: str) -> Dict[str, str]:
        """提取所有业务代码源文件"""
        results = {}
        business_count = 0
        framework_count = 0
        no_content_count = 0
        for i, source_path in enumerate(self.sources):
            content = self.sources_content[i] if i < len(self.sources_content) else None
            if not content:
                no_content_count += 1
                continue
            # 判断是否是业务代码
            if self._is_business_source(source_path):
                business_count += 1
                clean_path = self._clean_source_path(source_path)
                results[clean_path] = content
                # 保存文件
                output_path = os.path.join(output_dir, clean_path)
                os.makedirs(os.path.dirname(output_path), exist_ok=True)
                with open(output_path, 'w', encoding='utf-8') as f:
                    f.write(content)
                size_kb = len(content) / 1024
                print(f"   ✅ {clean_path} ({size_kb:.1f} KB)")
            else:
                framework_count += 1
        print(f"\n📊 Source Map 提取统计:")
        print(f"   业务文件:     {business_count}")
        print(f"   框架/库文件:  {framework_count}")
        print(f"   无内容文件:   {no_content_count}")
        return results
    def _is_business_source(self, path: str) -> bool:
        """判断源文件路径是否属于业务代码"""
        if 'node_modules' in path:
            return False
        if any(p in path for p in BundleParser.FRAMEWORK_PATTERNS):
            return False
        if path.endswith('.json') and 'package.json' in path:
            return False
        # polyfill 文件
        if 'polyfill' in path.lower():
            return False
        return True
    def _clean_source_path(self, path: str) -> str:
        """清理源文件路径"""
        # 去掉路径前缀
        path = re.sub(r'^(?:\.\.?/)+', '', path)
        path = re.sub(r'^/', '', path)
        # 去掉 webpack/metro 特有前缀
        path = re.sub(r'^(?:webpack://|metro://)[^/]*//', '', path)
        return path
# ============================================================
# APK 处理器
# ============================================================
class APKExtractor:
    """从 APK 文件中提取 Bundle"""
    BUNDLE_PATHS = [
        'assets/index.android.bundle',
        'assets/index.android.bundle.js',
        'assets/main.jsbundle',
    ]
    SOURCEMAP_PATHS = [
        'assets/index.android.bundle.map',
        'assets/sourcemap.js.map',
    ]
    @staticmethod
    def extract_from_apk(apk_path: str, output_dir: str) -> Tuple[Optional[str], Optional[str]]:
        """从 APK 提取 bundle 和 sourcemap"""
        print(f"📱 解析 APK: {apk_path}")
        bundle_path = None
        sourcemap_path = None
        with zipfile.ZipFile(apk_path, 'r') as apk:
            file_list = apk.namelist()
            # 查找 bundle
            for bp in APKExtractor.BUNDLE_PATHS:
                if bp in file_list:
                    output = os.path.join(output_dir, os.path.basename(bp))
                    apk.extract(bp, output_dir)
                    extracted = os.path.join(output_dir, bp)
                    os.rename(extracted, output)
                    bundle_path = output
                    print(f"   ✅ 找到 Bundle: {bp}")
                    break
            # 查找 sourcemap
            for sp in APKExtractor.SOURCEMAP_PATHS:
                if sp in file_list:
                    output = os.path.join(output_dir, os.path.basename(sp))
                    apk.extract(sp, output_dir)
                    extracted = os.path.join(output_dir, sp)
                    os.rename(extracted, output)
                    sourcemap_path = output
                    print(f"   ✅ 找到 Source Map: {sp}")
                    break
            if not bundle_path:
                # 搜索所有 .bundle 和 .jsbundle 文件
                for f in file_list:
                    if f.endswith('.bundle') or f.endswith('.jsbundle'):
                        output = os.path.join(output_dir, os.path.basename(f))
                        apk.extract(f, output_dir)
                        bundle_path = output
                        print(f"   ✅ 找到 Bundle: {f}")
                        break
        if not bundle_path:
            print("   ❌ 未在 APK 中找到 Bundle 文件")
        return bundle_path, sourcemap_path
# ============================================================
# Hermes 字节码检测
# ============================================================
class HermesDetector:
    """检测和处理 Hermes 字节码"""
    HERMES_MAGIC = b'\x1f\xc6\xd7\x84'  # Hermes 字节码魔数
    @staticmethod
    def is_hermes_bytecode(file_path: str) -> bool:
        """检测文件是否是 Hermes 字节码"""
        with open(file_path, 'rb') as f:
            magic = f.read(4)
        return magic == HermesDetector.HERMES_MAGIC
    @staticmethod
    def suggest_decompile(file_path: str):
        """建议反编译方法"""
        print(f"\n⚠️  检测到 Hermes 字节码格式!")
        print(f"    该文件是编译后的二进制格式,不是纯 JavaScript。")
        print(f"\n    请先使用以下工具反编译:")
        print(f"")
        print(f"    方法1: hermes-dec (推荐)")
        print(f"      pip install hermes-dec")
        print(f"      hermes-dec {file_path} -o output.js")
        print(f"")
        print(f"    方法2: hbctool")
        print(f"      pip install hbctool")
        print(f"      hbctool disasm {file_path} output_dir/")
        print(f"")
        print(f"    方法3: hermes CLI")
        print(f"      npx hermes --dump-bytecode {file_path} > dump.txt")
        print(f"")
        print(f"    反编译后再用本工具处理输出的 JS 文件。")
# ============================================================
# 报告生成器
# ============================================================
class ReportGenerator:
    """生成提取报告"""
    @staticmethod
    def generate(modules: Dict[int, ModuleInfo], output_dir: str):
        """生成详细报告"""
        business = {k: v for k, v in modules.items() if v.is_business}
        framework = {k: v for k, v in modules.items() if v.category == "framework"}
        third_party = {k: v for k, v in modules.items() if v.category == "third_party"}
        unknown = {k: v for k, v in modules.items() if v.category in ("unknown", "framework_or_lib")}
        total_size = sum(len(m.code) for m in modules.values())
        business_size = sum(len(m.code) for m in business.values())
        report = []
        report.append("=" * 70)
        report.append("📊 React Native Bundle 分析报告")
        report.append("=" * 70)
        report.append("")
        report.append(f"模块总数:       {len(modules)}")
        report.append(f"业务代码模块:   {len(business)} ({len(business)/max(len(modules),1)*100:.1f}%)")
        report.append(f"框架模块:       {len(framework)}")
        report.append(f"第三方库模块:   {len(third_party)}")
        report.append(f"未分类模块:     {len(unknown)}")
        report.append("")
        report.append(f"代码总大小:     {total_size/1024:.1f} KB")
        report.append(f"业务代码大小:   {business_size/1024:.1f} KB ({business_size/max(total_size,1)*100:.1f}%)")
        report.append("")
        report.append("-" * 70)
        report.append("📁 业务代码文件列表:")
        report.append("-" * 70)
        # 按置信度排序
        sorted_business = sorted(business.values(), key=lambda m: m.confidence, reverse=True)
        for m in sorted_business:
            name = m.filename or m.guessed_filename or f"module_{m.module_id}"
            size = len(m.code) / 1024
            conf = m.confidence
            deps = len(m.dependencies)
            report.append(f"  [{m.module_id:4d}] {name:<50s} {size:6.1f}KB  置信度:{conf:.0%}  依赖:{deps}")
        report.append("")
        report.append("-" * 70)
        report.append("🔗 业务模块依赖关系:")
        report.append("-" * 70)
        for m in sorted_business[:30]:  # 只显示前30个
            name = m.filename or f"module_{m.module_id}"
            dep_names = []
            for dep_id in m.dependencies:
                dep = modules.get(dep_id)
                if dep:
                    dep_name = dep.filename or f"module_{dep_id}"
                    dep_names.append(dep_name)
            if dep_names:
                report.append(f"\n  {name}")
                for dn in dep_names:
                    report.append(f"    → {dn}")
        report_text = '\n'.join(report)
        # 打印报告
        print(report_text)
        # 保存报告
        report_path = os.path.join(output_dir, 'extraction_report.txt')
        with open(report_path, 'w', encoding='utf-8') as f:
            f.write(report_text)
        print(f"\n📄 报告已保存到: {report_path}")
# ============================================================
# 主程序
# ============================================================
def main():
    parser = argparse.ArgumentParser(
        description='React Native Bundle 业务代码提取器',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent('''
        使用示例:
          # 基本提取
          python rn_extractor.py index.android.bundle -o output/
          # 使用 Source Map(最佳还原效果)
          python rn_extractor.py index.android.bundle --sourcemap index.android.bundle.map -o output/
          # 从 APK 提取
          python rn_extractor.py app-release.apk -o output/
          # 调整业务代码判定阈值
          python rn_extractor.py index.android.bundle -o output/ --threshold 0.3
          # 输出所有模块(包括框架代码)
          python rn_extractor.py index.android.bundle -o output/ --all
        ''')
    )
    parser.add_argument('input', help='Bundle 文件路径 (.bundle/.js) 或 APK 文件路径 (.apk)')
    parser.add_argument('-o', '--output', default='./extracted', help='输出目录 (默认: ./extracted)')
    parser.add_argument('--sourcemap', '-s', help='Source Map 文件路径')
    parser.add_argument('--threshold', '-t', type=float, default=0.3, 
                       help='业务代码判定阈值 0.0-1.0 (默认: 0.3, 越小提取越多)')
    parser.add_argument('--all', action='store_true', help='输出所有模块(包括框架代码)')
    parser.add_argument('--raw', action='store_true', help='输出原始代码(不做还原处理)')
    parser.add_argument('--list-only', action='store_true', help='只列出模块,不提取代码')
    parser.add_argument('--search', help='搜索包含指定文本的模块')
    args = parser.parse_args()
    # 创建输出目录
    os.makedirs(args.output, exist_ok=True)
    input_path = args.input
    bundle_path = None
    sourcemap_path = args.sourcemap
    print("🚀 React Native Bundle 业务代码提取器")
    print("=" * 50)
    # ---- 处理 APK 输入 ----
    if input_path.endswith('.apk'):
        bundle_path, auto_sourcemap = APKExtractor.extract_from_apk(input_path, args.output)
        if not bundle_path:
            print("❌ 无法从 APK 中提取 Bundle")
            sys.exit(1)
        if auto_sourcemap and not sourcemap_path:
            sourcemap_path = auto_sourcemap
    else:
        bundle_path = input_path
    # ---- 检测 Hermes 字节码 ----
    if HermesDetector.is_hermes_bytecode(bundle_path):
        HermesDetector.suggest_decompile(bundle_path)
        sys.exit(1)
    # ---- 优先使用 Source Map ----
    if sourcemap_path:
        print(f"\n🗺️  使用 Source Map 模式(完美还原)")
        sm_parser = SourceMapParser(sourcemap_path)
        results = sm_parser.extract_business_sources(args.output)
        print(f"\n✅ 完成!提取了 {len(results)} 个业务源文件")
        print(f"📁 输出目录: {args.output}")
        return
    # ---- Bundle 解析模式 ----
    print(f"\n📄 使用 Bundle 解析模式")
    print(f"   (提示:使用 --sourcemap 参数可获得更好的还原效果)\n")
    # 读取 Bundle
    with open(bundle_path, 'r', encoding='utf-8', errors='replace') as f:
        bundle_content = f.read()
    # 解析
    bp = BundleParser(bundle_content)
    modules = bp.parse()
    # 搜索模式
    if args.search:
        print(f"\n🔍 搜索包含 '{args.search}' 的模块:")
        for mid, module in modules.items():
            if args.search.lower() in module.code.lower():
                name = module.filename or f"module_{mid}"
                print(f"  [{mid}] {name} (置信度: {module.confidence:.0%})")
        return
    # 只列出模式
    if args.list_only:
        ReportGenerator.generate(modules, args.output)
        return
    # 提取代码
    business_modules = {k: v for k, v in modules.items() 
                       if v.is_business and v.confidence >= args.threshold}
    if args.all:
        business_modules = modules
    print(f"\n📦 提取 {len(business_modules)} 个业务模块...")
    # 保存文件
    business_dir = os.path.join(args.output, 'business_code')
    all_code_dir = os.path.join(args.output, 'all_modules')
    os.makedirs(business_dir, exist_ok=True)
    for mid, module in sorted(business_modules.items()):
        # 确定文件名
        if module.filename:
            filename = module.filename
            # 清理路径
            filename = re.sub(r'^(?:\.\.?/)+', '', filename)
        else:
            filename = f"module_{mid}.js"
        # 确定输出内容
        if args.raw:
            content = module.code
        else:
            content = module.restored_code or module.code
        # 保存
        output_path = os.path.join(business_dir, filename)
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(content)
        size_kb = len(content) / 1024
        print(f"  ✅ [{mid:4d}] {filename} ({size_kb:.1f} KB) 置信度: {module.confidence:.0%}")
    # 生成合并文件(方便搜索)
    merged_path = os.path.join(args.output, 'all_business_code.js')
    with open(merged_path, 'w', encoding='utf-8') as f:
        f.write("// 所有业务代码合并文件\n")
        f.write(f"// 共 {len(business_modules)} 个模块\n\n")
        for mid, module in sorted(business_modules.items()):
            f.write(f"\n// {'='*60}\n")
            name = module.filename or f"module_{mid}"
            f.write(f"// 模块 [{mid}]: {name}\n")
            f.write(f"// 置信度: {module.confidence:.0%}\n")
            f.write(f"// {'='*60}\n\n")
            f.write(module.restored_code or module.code)
            f.write('\n')
    # 生成报告
    print("")
    ReportGenerator.generate(modules, args.output)
    print(f"\n✅ 提取完成!")
    print(f"📁 业务代码目录: {business_dir}")
    print(f"📄 合并文件:     {merged_path}")
    print(f"📊 分析报告:     {os.path.join(args.output, 'extraction_report.txt')}")
if __name__ == '__main__':
    main()

三、使用方法

安装和基本使用

Bash
# 保存脚本为 rn_extractor.py

# 基本用法 - 直接解析 bundle
python rn_extractor.py index.android.bundle -o output/

# 使用 Source Map(最佳效果,完美还原 JSX/TS 源码)
python rn_extractor.py index.android.bundle \
  --sourcemap index.android.bundle.map \
  -o output/

# 从 APK 直接提取
python rn_extractor.py app-release.apk -o output/

# 搜索特定代码
python rn_extractor.py index.android.bundle --search "login"
python rn_extractor.py index.android.bundle --search "api.example.com"

# 只看模块列表(不提取)
python rn_extractor.py index.android.bundle --list-only

# 降低阈值,提取更多可能的业务代码
python rn_extractor.py index.android.bundle -t 0.2 -o output/

# 提取全部模块(包括框架)
python rn_extractor.py index.android.bundle --all -o output/

输出示例

text
🚀 React Native Bundle 业务代码提取器
==================================================

📄 使用 Bundle 解析模式
   (提示:使用 --sourcemap 参数可获得更好的还原效果)

🔍 开始解析 Bundle...
   Bundle 大小: 4.23 MB
   找到 1847 个 __d() 调用
   发现 1842 个模块

📦 提取 43 个业务模块...
  ✅ [ 501] src/App.js (3.2 KB) 置信度: 92%
  ✅ [ 502] src/screens/HomeScreen.js (5.1 KB) 置信度: 88%
  ✅ [ 503] src/screens/LoginScreen.tsx (4.8 KB) 置信度: 95%
  ✅ [ 504] src/screens/ProfileScreen.js (3.6 KB) 置信度: 85%
  ✅ [ 505] src/components/Button.js (1.2 KB) 置信度: 72%
  ✅ [ 506] src/components/Header.js (2.1 KB) 置信度: 78%
  ✅ [ 510] src/services/api.js (2.8 KB) 置信度: 90%
  ✅ [ 515] src/store/userSlice.js (1.8 KB) 置信度: 82%
  ✅ [ 520] src/utils/helpers.js (1.0 KB) 置信度: 65%
  ...

======================================================================
📊 React Native Bundle 分析报告
======================================================================

模块总数:       1842
业务代码模块:   43 (2.3%)
框架模块:       389
第三方库模块:   1205
未分类模块:     205

代码总大小:     3892.4 KB
业务代码大小:   127.3 KB (3.3%)

✅ 提取完成!
📁 业务代码目录: output/business_code/
📄 合并文件:     output/all_business_code.js
📊 分析报告:     output/extraction_report.txt

输出的还原代码示例

JavaScript
/**
 * 文件: src/screens/LoginScreen.js
 * 模块ID: 503
 * 分类: business
 * 置信度: 95%
 * 依赖模块: [200, 1, 510, 515]
 * 依赖详情:
 *   200 → react
 *   1 → react-native
 *   510 → src/services/api.js
 *   515 → src/store/userSlice.js
 */

import React from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
import api from 'src/services/api';
import { setUser } from 'src/store/userSlice';

function LoginScreen({ navigation }) {
  var _useState = React.useState('');
  var username = _useState[0];
  var setUsername = _useState[1];

  var _useState2 = React.useState('');
  var password = _useState2[0];
  var setPassword = _useState2[1];

  var handleLogin = async function handleLogin() {
    var response = await api.post('/auth/login', {
      username: username,
      password: password
    });
    if (response.success) {
      setUser(response.data);
      navigation.navigate('Home');
    }
  };

  return /* JSX: */ React.createElement(
    View,
    { style: styles.container },
    /* JSX: */ React.createElement(Text, { style: styles.title }, "登录"),
    /* JSX: */ React.createElement(TextInput, {
      placeholder: "请输入用户名",
      value: username,
      onChangeText: setUsername
    }),
    /* JSX: */ React.createElement(TouchableOpacity, {
      onPress: handleLogin
    }, /* JSX: */ React.createElement(Text, null, "登录"))
  );
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold'
  }
});

module.exports = LoginScreen;

四、不同场景的最佳策略

text
┌─────────────────┬────────────────┬──────────────────────────────┐
│     你有什么     │    还原效果    │          操作方式              │
├─────────────────┼────────────────┼──────────────────────────────┤
│                 │                │                              │
│ Source Map      │  ⭐⭐⭐⭐⭐     │ python rn_extractor.py       │
│ + Bundle       │  完美还原       │   bundle -s map -o out/      │
│                 │  原始JSX/TS    │                              │
│                 │                │                              │
├─────────────────┼────────────────┼──────────────────────────────┤
│                 │                │                              │
│ 开发模式 Bundle │  ⭐⭐⭐⭐       │ python rn_extractor.py       │
│ (--dev true)    │  有文件名      │   bundle -o out/             │
│                 │  变量名保留    │                              │
│                 │                │                              │
├─────────────────┼────────────────┼──────────────────────────────┤
│                 │                │                              │
│ 生产模式 Bundle │  ⭐⭐⭐         │ python rn_extractor.py       │
│ (minified)     │  启发式分析     │   bundle -o out/ -t 0.3      │
│                 │  变量名混淆    │                              │
│                 │                │                              │
├─────────────────┼────────────────┼──────────────────────────────┤
│                 │                │                              │
│ APK 文件       │  ⭐⭐⭐         │ python rn_extractor.py       │
│                 │  自动提取      │   app.apk -o out/            │
│                 │                │                              │
├─────────────────┼────────────────┼──────────────────────────────┤
│                 │                │                              │
│ Hermes 字节码  │  ⭐⭐           │ 先 hermes-dec 反编译         │
│ (.hbc)         │  需先反编译     │ 再用本工具处理               │
│                 │                │                              │
└─────────────────┴────────────────┴──────────────────────────────┘

posted on 2026-03-29 16:54  GKLBB  阅读(8)  评论(0)    收藏  举报