Skip to content

📤 Exporter 架构 ​

架构 导出器

导出子系统把归档的元数据与内容拆写成一组文本文件。Exporter 是纯静态工具门面,writeArchive 串联 manifest、类名表、方法表、字符串表、方法计数树五个步骤,只对 APK 的步骤用 endsWith(".apk") 守卫。

五步导出流水线 ​

mermaid
flowchart TD
    A["Exporter.writeArchive(apk, allClasses)"] --> B["① writeManifest<br/>仅 APK"]
    A --> C["② writeClassNames<br/>all_classes.txt"]
    A --> D["③ writeMethods<br/>仅 APK → all_methods.txt"]
    A --> E["④ writeStringTables<br/>仅 APK → all_strings.txt"]
    A --> F["⑤ writeMethodCounts<br/>method_counts.txt"]
    B --> B1["TranslatorFactory<br/>createTranslator(Manifest.xml)"]
    D --> D1["DexMethodsDumper.dumpMethods"]
    E --> E1["DexStringsDumper.dumpStrings<br/>+ FileChannel.map(MMAP)"]
    F --> F1["RootBuilder.fillClassesWithMethods"]
    F1 --> F2["MethodCountExporter<br/>策略导出"]

writeArchive 的六个方法全部 static,构造器私有——没有任何实例状态,调用一次即出全部产物。

各步骤输出与守卫 ​

步骤方法仅 APK?产物底层
① ManifestwriteManifest✅<getClassName>_dumpTranslatorFactory 翻译 AndroidManifest.xml
② 类名writeClassNames—all_classes.txtFileWriter 逐行写
③ 方法writeMethods✅all_methods.txtDexMethodsDumper.dumpMethods
④ 字符串writeStringTables✅all_strings.txtDexStringsDumper + mmap
⑤ 方法计数writeMethodCounts—method_counts.txtRootBuilder + TreeMethodCountExporter

🔒 APK 守卫:writeMethods / writeStringTables / writeManifest 都以 if (!archiveFile.getName().endsWith(".apk")) return; 开头,对 dex/jar 等归档静默跳过——导出方法表与字符串表在技术上依赖 dexlib2,只对 DEX 内容有意义。

writeMethodCounts 与策略接口 ​

writeMethodCounts 先构建树,再经策略接口渲染,输出到了 stdout(先打一行文件路径)与文件:

java
RootBuilder rootBuilder = new RootBuilder();
ClassNode classNode = rootBuilder.fillClassesWithMethods(archive);
MethodCountExporter exporter = new TreeMethodCountExporter(pw);
exporter.exportMethodCounts(classNode);

RootBuilder 按后缀分发解析(aar/jar/dex/apk),产出一棵按包聚合的 ClassNode 树。MethodCountExporter 是仅含 exportMethodCounts(ClassNode) 的单方法策略接口,两个实现可互换:

实现输出形态实现要点
TreeMethodCountExporter树形递归 printNode,以 ╚/╠/║/═(╚/╠/║/═)画树线,输出 key - count
FlatMethodCountExporter扁平递归时把父级 key 拼进 String[] path,以 . 连接成包路径

💡 策略模式让"方法数产出形态"成为可替换维度:CLI 用 -methodcounts [-flat] 时(见 CLI 方法计数),GUI 的 MethodsCountPanel 也可以用同一棵树喂 RingChart。见 方法计数子系统。

writeStringTables:mmap 写大字符串表 ​

writeStringTables 委托 writeListStringsChannel,用 NIO 内存映射一次写入:

java
byte[] buffer = ("                                                     " + "  \n").getBytes();
FileChannel rwChannel = new RandomAccessFile(to, "rw").getChannel();
ByteBuffer wrBuf = rwChannel.map(MapMode.READ_WRITE, 0,
        allStrings.size() * buffer.length);
for (int i = 0; i < allStrings.size(); i++) {
    wrBuf.put(allStrings.get(i).getBytes());
}
rwChannel.close();

mmap 的三处坑

  1. 定长估算 — 映射长度按"固定空白行模板 buffer.length × 字符串数量"推算,超长字符串会写穿 allStrings.size() 行后的地址。
  2. 异常静默 — catch (IOException ioe) {} 空块吞掉失败,文件可能半截却无告警。
  3. 零填充 — 实际内容短于模板时按 0 字节填充,落盘仍占满映射区。

设计要点 ​

  • 📦 纯静态门面 — 私有构造 + 全 static 方法,writeArchive 是唯一编排入口,调用方零状态。
  • 🧱 策略注入 — 计数渲染走 MethodCountExporter 接口,树形/扁平可互换,渲染与解析解耦。
  • 🛡️ APK 专属守卫 — 依赖 dex 内容的步骤全部按扩展名短路,非 APK 静默跳过。
  • ⚡ 针对性优化 — 普通列表用 FileWriter 逐行写,大字符串表单独走 mmap,按规模选择 IO 策略。

进一步阅读 ​

最后更新:

基于 Apache 2.0 协议发布