📥 ContentReader 解析引擎
架构 内容读取
com.google.classyshark.silverghost.contentreader包把「任意二进制归档」统一解析成类名列表 + 归档组件。核心是 ContentReader 按归档扩展名路由到具体 Reader,所有 Reader 收敛到同一个 BinaryContentReader 策略接口。解析与翻译彻底分层——内容层只回答「包里有什么」,语义交给 Translator 架构。
扩展名 → Reader 路由
new ContentReader(binaryArchive) 在构造器内把文件名 toLowerCase() 后逐段 endsWith 匹配,load() 时不再分支:
mermaid
flowchart TD
CR["new ContentReader(file)"] --> E{"归档名后缀?"}
E -- ".apk" --> APK["ApkReader"]
APK --> MD["MultidexReader<br/>扫描 zip 条目"]
E -- ".dex" --> DEX["DexReader"]
DEX --> DL["DexlibLoader.loadDexFile"]
E -- ".jar" --> JAR["JarReader"]
JAR --> JIS["JarInputStream 扫 .class 条目"]
E -- ".aar" --> AAR["AarReader"]
AAR --> AEX["提取内嵌 jar → JarReader"]
E -- 其它(含 .class)" --> CLZ["ClazzReader"]
CLZ --> CV["ASM ClassNameVisitor"]
APK & DEX & JAR & AAR & CLZ --> BI["BinaryContentReader 接口"]
BI --> OUT["类名列表 + Components"]| 扩展名 | Reader | 底层技术 | 说明 |
|---|---|---|---|
.apk | ApkReader | MultidexReader | 委托多 dex 扫描,含组件标注 |
.dex | DexReader | dexlib2 DexlibLoader | 类名转换:replaceAll("/", ".") 并去首尾括号 |
.jar | JarReader | JarInputStream | 遍历 .class 条目 + native lib 组件 |
.aar | AarReader | 解 jar → JarReader | 提取内嵌 classes.jar 委托 JarReader,有 manifest 则补标组件 |
其它 / .class | ClazzReader | ASM ClassReader.accept | 单类文件兜底,未知扩展名也落这里 |
统一策略接口:BinaryContentReader
所有 Reader 实现同一三方法接口,上层不感知格式差异:
| 方法 | 语义 |
|---|---|
read() | 读盘、解析,填充内部状态 |
getClassNames() | 返回类名列表(GUI 类树/搜索的唯一数据源) |
getComponents() | 返回归档组件列表 |
输出契约:类名 + 组件
java
public static class Component {
String name; // 组件名,如 "AndroidManifest.xml"、"lib/arm64-v8a/libnative.so"
ARCHIVE_COMPONENT component; // 组件类型
}
public enum ARCHIVE_COMPONENT { ANDROID_MANIFEST, NATIVE_LIBRARY }组件是解析层的副产品标注:manifest 与 native 库除了在类名列表里出现,还会单独标类型,供 GUI 分栏展示。
load() 的实例级缓存
java
public void load() {
if (allClassNames.isEmpty()) { // 判空即缓存
formatReader.read();
allClassNames = formatReader.getClassNames();
}
}- 一次成型 — 每个
ContentReader实例至多读盘一次,重复调用零成本。 - 静默降级 — 读异常时置空列表不抛错,上层判空识别坏归档。
- 只读暴露 —
getAllClassNames()返回不可变列表。
各 Reader 要点
- ApkReader → MultidexReader — 扫描 APK zip 条目:
.xml条目直接作为类名;.dex用 SherlockHash 提取后交给DexReader.readClassNamesFromDex;lib/条目标NATIVE_LIBRARY;jar/zip条目递归处理内层 zip。 - DexReader — 类名从
classDef.getType()出发,/→.并去掉首L尾;,得到标准 JVM 类名。 - JarReader —
JarInputStream逐条目,.class名直接入库。⚠️ 源码中的 native 组件匹配有 bug(startsWith("resources") && startsWith(".so")不可能同时成立,带 TODO),实际只靠类名条目。 - AarReader — 把内嵌
classes.jar解到临时文件再委托 JarReader,实现「AAR ≈ 增强版 JAR」的复用。 - ClazzReader —
Files.readAllBytes+ ASMClassReader.accept(new ClassNameVisitor(), 0),从字节码头读类名,兜底任意未知格式。
与上层关系
- SilverGhost 三阶段编排的阶段 1 持
ContentReader拿类名,喂给 Reducer 做自动补全。 - 混淆映射(TokensMapper)只作用于翻译层,不影响内容读取——类名列表始终是原始名。
- SherlockHash 的 zip 提取缓存在
MultidexReader内被复用,避免重复解压同一条目。
设计要点
- 🧺 统一契约 — 全格式收敛到
BinaryContentReader三方法,上层零分支。 - 🔀 构造期路由 — 扩展名匹配发生在构造器,
load()是纯执行,职责单一。 - 📦 缓存即约定 — 读一次、缓存、只读暴露,配合 IO 层提取缓存。
- 🧩 兜底 ClazzReader — 未知格式一律当单类解析,保证「什么文件都能开」。
进一步阅读
- 🧩 ContentReader · BinaryContentReader · ApkReader · DexReader · JarReader · AarReader · ClazzReader · MultidexReader
- 🏗️ SilverGhost 引擎 · 翻译器架构 · IO 层架构
- 📚 内容读取 SPI · 概念:multidex · 概念:dex