WebToApp 源码解析: apkbuilder 字节级分析 + docs 站

This commit is contained in:
kang
2026-04-13 16:45:55 +08:00
commit a51810982a
5 changed files with 776 additions and 0 deletions

497
site/index.html Normal file
View File

@@ -0,0 +1,497 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebToApp 源码解析 · 把网站打包成 APK 的那把"神器"到底是不是真功夫</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',()=>{hljs.highlightAll();});</script>
<style>
:root{
--bg:#0b0d12;--bg-2:#11141b;--bg-3:#161a23;--border:#242938;
--fg:#d8dee9;--fg-dim:#8b93a7;--fg-muted:#5c6478;
--accent:#7aa2f7;--accent-2:#bb9af7;--accent-3:#9ece6a;
--warn:#e0af68;--danger:#f7768e;--code-bg:#0f1218;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB",sans-serif;background:var(--bg);color:var(--fg);line-height:1.75;font-size:15px;-webkit-font-smoothing:antialiased}
.layout{display:grid;grid-template-columns:260px 1fr;min-height:100vh}
aside{position:sticky;top:0;height:100vh;overflow-y:auto;background:var(--bg-2);border-right:1px solid var(--border);padding:28px 20px;font-size:13px}
aside .brand{font-weight:700;font-size:15px;color:var(--accent);padding-bottom:14px;margin-bottom:14px;border-bottom:1px solid var(--border);letter-spacing:.3px}
aside .brand small{display:block;color:var(--fg-muted);font-weight:400;margin-top:4px;font-size:11px;letter-spacing:0}
aside nav ul{list-style:none}
aside nav>ul>li{margin-bottom:2px}
aside nav a{display:block;color:var(--fg-dim);text-decoration:none;padding:6px 10px;border-radius:6px;transition:all .15s;border-left:2px solid transparent}
aside nav a:hover{background:var(--bg-3);color:var(--fg)}
aside nav .sub{padding-left:14px;margin:2px 0 6px}
aside nav .sub a{font-size:12px;padding:4px 10px;color:var(--fg-muted)}
aside nav .sub a:hover{color:var(--fg-dim)}
aside .meta{margin-top:24px;padding-top:16px;border-top:1px solid var(--border);color:var(--fg-muted);font-size:11px;line-height:1.8}
aside .meta code{background:var(--bg-3);padding:1px 5px;border-radius:3px;font-size:10px}
main{padding:56px 64px 120px;max-width:980px}
h1{font-size:32px;font-weight:700;margin-bottom:8px;letter-spacing:-.5px;color:#fff}
h1 .emoji{margin-right:8px}
.subtitle{color:var(--fg-dim);font-size:15px;margin-bottom:8px}
.tags{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:32px;margin-top:16px}
.tag{font-size:11px;padding:3px 10px;border-radius:999px;background:var(--bg-3);border:1px solid var(--border);color:var(--fg-dim);font-family:"SF Mono",Monaco,Menlo,Consolas,monospace}
.tag.accent{border-color:var(--accent);color:var(--accent)}
.tag.purple{border-color:var(--accent-2);color:var(--accent-2)}
.tag.green{border-color:var(--accent-3);color:var(--accent-3)}
.tag.warn{border-color:var(--warn);color:var(--warn)}
.tag.danger{border-color:var(--danger);color:var(--danger)}
h2{font-size:24px;font-weight:700;margin:56px 0 16px;padding-bottom:10px;border-bottom:1px solid var(--border);color:#fff;scroll-margin-top:20px}
h2 .num{color:var(--accent);margin-right:12px;font-family:"SF Mono",monospace;font-size:20px}
h3{font-size:18px;font-weight:600;margin:32px 0 12px;color:var(--accent-2);scroll-margin-top:20px}
h4{font-size:15px;font-weight:600;margin:20px 0 8px;color:var(--fg)}
p{margin:10px 0;color:var(--fg)}
strong{color:#fff;font-weight:600}
em{color:var(--warn);font-style:normal}
a{color:var(--accent);text-decoration:none;border-bottom:1px dotted var(--accent)}
a:hover{border-bottom-style:solid}
code{font-family:"JetBrains Mono","SF Mono",Monaco,Menlo,Consolas,monospace;font-size:13px}
p code,li code,td code{background:var(--bg-3);padding:2px 6px;border-radius:4px;color:var(--accent-3);font-size:12.5px;border:1px solid var(--border)}
pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:16px 20px;margin:16px 0;overflow-x:auto;font-size:13px;line-height:1.65;position:relative}
pre code{background:transparent !important;padding:0;border:0;color:inherit}
.fileref{display:inline-block;background:var(--bg-3);color:var(--accent);padding:1px 8px;border-radius:4px;font-size:11.5px;font-family:"JetBrains Mono",monospace;border:1px solid var(--border);border-bottom:1px solid var(--border) !important}
ul,ol{margin:12px 0;padding-left:26px}
li{margin:6px 0;color:var(--fg)}
li::marker{color:var(--fg-muted)}
table{width:100%;border-collapse:collapse;margin:20px 0;background:var(--bg-2);border-radius:8px;overflow:hidden;font-size:13.5px}
th,td{padding:10px 14px;text-align:left;border-bottom:1px solid var(--border);vertical-align:top}
th{background:var(--bg-3);font-weight:600;color:var(--fg);font-size:12.5px;text-transform:uppercase;letter-spacing:.5px}
tr:last-child td{border-bottom:0}
td code{font-size:11.5px}
td.score{font-family:"JetBrains Mono",monospace;color:var(--accent-3);font-weight:600}
td.score.mid{color:var(--warn)}
td.score.low{color:var(--danger)}
.callout{padding:14px 20px;border-radius:8px;margin:20px 0;border-left:3px solid var(--accent);background:color-mix(in srgb, var(--accent) 8%, var(--bg-2))}
.callout.warn{border-left-color:var(--warn);background:color-mix(in srgb, var(--warn) 8%, var(--bg-2))}
.callout.danger{border-left-color:var(--danger);background:color-mix(in srgb, var(--danger) 8%, var(--bg-2))}
.callout.success{border-left-color:var(--accent-3);background:color-mix(in srgb, var(--accent-3) 8%, var(--bg-2))}
.callout strong:first-child{color:var(--accent)}
.callout.warn strong:first-child{color:var(--warn)}
.callout.danger strong:first-child{color:var(--danger)}
.callout.success strong:first-child{color:var(--accent-3)}
.hero{background:linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, var(--bg-2)), var(--bg-2));border:1px solid var(--border);border-radius:12px;padding:24px 28px;margin:24px 0 40px}
.hero p{margin:6px 0;color:var(--fg-dim);font-size:14px}
.hero .verdict{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:20px;padding-top:20px;border-top:1px solid var(--border)}
.hero .verdict div{text-align:center}
.hero .verdict .k{font-size:11px;color:var(--fg-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
.hero .verdict .v{font-weight:600;font-size:14px;color:var(--fg)}
.hero .verdict .v.yes{color:var(--accent-3)}
.hero .verdict .v.no{color:var(--danger)}
.hero .verdict .v.mid{color:var(--warn)}
/* Pipeline diagram */
.pipeline{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin:24px 0;font-size:12px}
.pipeline .step{background:var(--bg-2);border:1px solid var(--border);border-radius:8px;padding:14px 10px;text-align:center;position:relative}
.pipeline .step .num{display:inline-block;width:22px;height:22px;line-height:22px;border-radius:50%;background:var(--accent);color:#000;font-weight:700;font-size:11px;margin-bottom:6px}
.pipeline .step .t{color:#fff;font-weight:600;margin-bottom:4px}
.pipeline .step .d{color:var(--fg-muted);font-size:11px;line-height:1.5}
.pipeline .step:not(:last-child)::after{content:"→";position:absolute;right:-8px;top:50%;transform:translateY(-50%);color:var(--accent);font-size:14px;z-index:2}
/* Module matrix grid */
.modgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:20px 0}
.modgrid .mod{background:var(--bg-2);border:1px solid var(--border);border-radius:8px;padding:14px 16px}
.modgrid .mod .name{font-family:"JetBrains Mono",monospace;font-size:12.5px;color:var(--accent);margin-bottom:4px}
.modgrid .mod .sz{font-size:11px;color:var(--fg-muted);margin-bottom:6px}
.modgrid .mod .lv{font-size:11px;font-weight:600}
.modgrid .mod .lv.real{color:var(--accent-3)}
.modgrid .mod .lv.mid{color:var(--warn)}
.modgrid .mod .lv.fake{color:var(--danger)}
@media (max-width: 900px){
.layout{grid-template-columns:1fr}
aside{position:static;height:auto;border-right:0;border-bottom:1px solid var(--border)}
main{padding:32px 20px 80px}
.hero .verdict{grid-template-columns:1fr}
.pipeline{grid-template-columns:1fr 1fr}
.modgrid{grid-template-columns:1fr}
h1{font-size:26px}
h2{font-size:20px}
}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:5px}
::-webkit-scrollbar-thumb:hover{background:var(--fg-muted)}
</style>
</head>
<body>
<div class="layout">
<aside>
<div class="brand">
WebToApp 源码解析
<small>shiahonb777/web-to-app · 字节级 APK 构建真功夫</small>
</div>
<nav>
<ul>
<li><a href="#overview">§0 概述与真相</a></li>
<li><a href="#panorama">§1 技术全景</a></li>
<li><a href="#apkbuilder">§2 ★ APK 构建引擎</a>
<ul class="sub">
<li><a href="#ab-pipeline">流水线总览</a></li>
<li><a href="#ab-axml">AXML 编辑器</a></li>
<li><a href="#ab-arsc">ARSC 字符串池</a></li>
<li><a href="#ab-zip">流式 ZIP + 对齐</a></li>
<li><a href="#ab-sign">apksig 签名</a></li>
</ul>
</li>
<li><a href="#engine">§3 浏览器双核</a></li>
<li><a href="#runtime">§4 多运行时</a></li>
<li><a href="#crypto">§5 加密与加固</a></li>
<li><a href="#extension">§6 扩展与 AI</a></li>
<li><a href="#commercial">§7 商业化证据链</a></li>
<li><a href="#matrix">§8 评分矩阵</a></li>
<li><a href="#verdict">§9 最终定论</a></li>
</ul>
</nav>
<div class="meta">
<div>日期 · 2026-04-13</div>
<div>本地路径 · <code>~/Projects/research/<br>20260413-webtoapp-源码解析/</code></div>
<div style="margin-top:10px">证据原则 · 所有论断带<br>文件路径 + 行号,不糊弄</div>
<div style="margin-top:10px">522 .kt · 10 万行 · 33MB</div>
</div>
</aside>
<main>
<h1><span class="emoji">📦</span>WebToApp 源码深度解析</h1>
<p class="subtitle">把"开源神器 WebToApp 一键网站转 APK"这个标题党,用 522 个 Kotlin 文件的字节级证据,拆给你看</p>
<div class="tags">
<span class="tag accent">shiahonb777/web-to-app</span>
<span class="tag purple">Kotlin 1.9 + Compose</span>
<span class="tag green">2465 ⭐ · 5 个月</span>
<span class="tag warn">仅 14 次提交</span>
<span class="tag danger">阉割版</span>
</div>
<div class="hero">
<p><strong>起因</strong>:营销号推"开源神器 WebToApp,秒杀打包工具",怀疑是又一个 WebView 套壳。</p>
<p><strong>第一轮误判</strong>:看标题以为就是 50 行 WebView template,等级对齐到 GitHub 满大街的 wrap 壳。</p>
<p><strong>翻源码后</strong>:打脸。<code>core/apkbuilder/</code> 是国内罕见的字节级 APK 操作实现——自写 AXML / ARSC 编辑器,v1/v2/v3 签名,流式 ZIP + ZipAligner。但也发现它是商业产品的引流版。</p>
<div class="verdict">
<div><div class="k">是不是噱头</div><div class="v no">不是</div></div>
<div><div class="k">是不是纯开源</div><div class="v mid">阉割版</div></div>
<div><div class="k">值不值得看</div><div class="v yes">非常值得</div></div>
</div>
</div>
<h2 id="overview"><span class="num">§0</span>概述与真相</h2>
<table>
<tr><th>指标</th><th></th></tr>
<tr><td>仓库</td><td><code>https://github.com/shiahonb777/web-to-app</code></td></tr>
<tr><td>创建 / 最后推送</td><td>2025-11-26 / 2026-04-12(活跃)</td></tr>
<tr><td>Stars / Forks</td><td>2465 / 353</td></tr>
<tr><td>仓库大小 / 源码量</td><td>19.6 MB(仓库)/ 33 MB · 522 个 .kt · ~10 万行</td></tr>
<tr><td>提交数</td><td><strong style="color:var(--warn)">仅 14 次</strong>——典型代码 dump,不是社区开发</td></tr>
<tr><td>版本</td><td>v1.9.5(versionCode 32)</td></tr>
<tr><td>商业背景</td><td>硬编码后端 <code>https://api.shiaho.sbs</code> · Google Play 6 档订阅 · 本地激活码</td></tr>
</table>
<div class="callout warn">
<strong>关键判断</strong> —— 这是某个商业产品的<em>开源引流版</em>。代码真实开源,但核心变现逻辑(服务端、Pro/Ultra 功能门)在闭源侧。可以抄技术细节,不要抄整个方案,<em>尤其不要依赖它的云服务和激活码系统</em>
</div>
<h2 id="panorama"><span class="num">§1</span>技术全景</h2>
<p><code>core/</code> 下 43+ 个子模块,按体积排序 Top 10:</p>
<div class="modgrid">
<div class="mod"><div class="name">i18n/</div><div class="sz">1.4 MB</div><div class="lv fake">20% 体积水分(机器翻译)</div></div>
<div class="mod"><div class="name">extension/</div><div class="sz">916 KB · 31 文件 · 22661 行</div><div class="lv real">真 · Chrome V3 Polyfill</div></div>
<div class="mod"><div class="name">apkbuilder/ ★</div><div class="sz">480 KB</div><div class="lv real">真 · 字节级 APK 操作</div></div>
<div class="mod"><div class="name">webview/</div><div class="sz">480 KB</div><div class="lv real">真 · 含 28 原生能力桥</div></div>
<div class="mod"><div class="name">ai/</div><div class="sz">468 KB · 14 文件 · 10850 行</div><div class="lv real">真 · 4 家 LLM 适配</div></div>
<div class="mod"><div class="name">cloud/</div><div class="sz">328 KB</div><div class="lv mid">商业化后端客户端</div></div>
<div class="mod"><div class="name">crypto/</div><div class="sz">172 KB · 15 文件 · 4459 行</div><div class="lv real">真 · AES-GCM + PBKDF2</div></div>
<div class="mod"><div class="name">linux/</div><div class="sz">108 KB</div><div class="lv mid">名不副实 · 纯 Kotlin 优化器</div></div>
<div class="mod"><div class="name">disguise/</div><div class="sz">5 文件 · 2773 行</div><div class="lv real">真 · 22 向量指纹伪装</div></div>
<div class="mod"><div class="name">blacktech/</div><div class="sz">203 行</div><div class="lv fake">PPT 功能 · 权限拿不到</div></div>
<div class="mod"><div class="name">nodejs/ / php/ / python/ / golang/</div><div class="sz">各 2-4 文件</div><div class="lv real">真 · 运行时二进制真打包</div></div>
<div class="mod"><div class="name">activation/ + billing/</div><div class="sz">507 行 + ~400 行</div><div class="lv mid">商业化 · Google Play + 激活码</div></div>
</div>
<h2 id="apkbuilder"><span class="num">§2</span>★ APK 构建引擎(全项目最高价值)</h2>
<p>这是 WebToApp 真正与众不同的地方。别的"套壳工具"都是调 Android Gradle Plugin + AAPT,打出一个跟开发者模板没区别的 APK。WebToApp <em>完全不依赖 Android SDK 的构建链</em>,而是在运行时用纯 Kotlin 代码直接操作 APK 字节——这意味着可以在手机上给手机打包 APK。</p>
<h3 id="ab-pipeline">流水线总览</h3>
<div class="pipeline">
<div class="step"><div class="num">1</div><div class="t">模板 APK</div><div class="d">assets 里带一个已签名的"骨架 APK"作为模板</div></div>
<div class="step"><div class="num">2</div><div class="t">解包 + 改 AXML</div><div class="d">AxmlEditor 修改 AndroidManifest.xml 里的 label / icon / 包名</div></div>
<div class="step"><div class="num">3</div><div class="t">改 ARSC</div><div class="d">ArscEditor 修改 resources.arsc 字符串池</div></div>
<div class="step"><div class="num">4</div><div class="t">流式打包</div><div class="d">ZipUtils 流式写 entry,大文件走 STORED 防 OOM</div></div>
<div class="step"><div class="num">5</div><div class="t">对齐+签名</div><div class="d">ZipAligner 4 字节对齐,apksig v1/v2/v3 签名</div></div>
</div>
<h3 id="ab-axml">AXML 编辑器 · 自实现 Binary XML 解析</h3>
<p><span class="fileref">core/apkbuilder/AxmlEditor.kt:26-248</span></p>
<ul>
<li>AndroidManifest.xml 在 APK 里是<strong>二进制 XML</strong>(AXML),格式没有公开文档,只能逆 Android 源码 <code>frameworks/base/tools/aapt/</code></li>
<li>支持 UTF-8 和 UTF-16LE 双编码(旧 APK 常为 UTF-16,新 APK 多为 UTF-8)</li>
<li>修改字符串必须保持字节长度——因为字符串池偏移表不可改,否则 AXML 解析会崩溃。作者在注释里明确写了这点</li>
<li>支持清除 <code>android:testOnly="true"</code> 标志(让 Play Protect 不弹"应用未完全发布"警告)</li>
<li>支持权限修复、组件 <code>android:name</code> 相对路径恢复为绝对类名</li>
</ul>
<div class="callout">
<strong>为什么重要</strong> —— 国内能自己写 AXML 编辑器的项目少到屈指可数(AXMLPrinter / Apktool 之外,几乎没有 Kotlin/纯 JVM 的现代实现)。这一块单拎出来就能发技术文章。
</div>
<h3 id="ab-arsc">ARSC 字符串池编辑</h3>
<p><span class="fileref">core/apkbuilder/ArscEditor.kt:32-79</span>(完整注释在 10-128 行)</p>
<ul>
<li><code>resources.arsc</code> 是 Android 的<strong>编译后资源表</strong>,包含所有字符串、数值、布局引用</li>
<li>作者在前 100 多行注释里详细写了 ARSC 二进制格式(Type Chunk / String Pool / Package Chunk / Config Chunk)——说明是真的啃过 <code>frameworks/base/libs/androidfw/ResourceTypes.cpp</code></li>
<li>实现"保长度替换":修改字符串时对齐到原字节长度,不动 offset 表</li>
<li>这样才能在不重算整个资源表的前提下修改应用名称、默认文案</li>
</ul>
<h3 id="ab-zip">流式 ZIP 写入 + 4 字节对齐</h3>
<p><span class="fileref">core/apkbuilder/ZipUtils.kt:92-122</span> · <span class="fileref">core/apkbuilder/ZipAligner.kt:23-156</span></p>
<p><strong>两个核心问题</strong>:</p>
<ol>
<li><strong>大文件不能一次加载到内存</strong>——主 APK 里可能带 Node.js/Python 二进制 40MB+,全读进 <code>ByteArray</code> 会 OOM(Android 单进程堆上限 192-512MB)</li>
<li><strong>APK 要求 4 字节对齐</strong>——但 Java 的 <code>ZipOutputStream</code> 不暴露内部流位置,无法在线对齐</li>
</ol>
<p><strong>方案</strong>:</p>
<ul>
<li><span class="fileref">ZipUtils.kt:92-122</span>:10MB 以上的 entry 强制走 <code>STORED</code>(无压缩)模式,先扫一遍文件算 CRC32,再流式写入——避免把整个文件读到内存。<span class="fileref">ApkBuilder.kt:1022</span> 注释明确:"Node binary ~40MB, use streaming write"</li>
<li><span class="fileref">ZipAligner.kt:23-156</span>:自己实现一个"后对齐"写入器——边写边手动维护字节游标,在每个 entry 头前补 <code>Extra Field</code> 填充到 4 字节边界。这是绕 Java API 限制的正确姿势</li>
</ul>
<h3 id="ab-sign">apksig 集成 · 正道签名</h3>
<p><span class="fileref">app/build.gradle.kts:210</span>:<code>com.android.tools.build:apksig:8.3.0</code></p>
<p><span class="fileref">core/apkbuilder/JarSigner.kt:8</span>:<code>import com.android.apksig.ApkSigner</code></p>
<ul>
<li>用的是<em>Google 官方 apksig 库</em>——不是自己手搓的 RSA-SHA256(那种山寨实现很容易被 Play 拒绝)</li>
<li>支持 <strong>v1 + v2 + v3</strong> 三种签名方案(Android 7+ 强制 v2,Android 11+ 用 v3)</li>
<li>支持 <strong>Android KeyStore</strong><strong>PKCS12 证书</strong>两种密钥源切换,不强制用户把私钥明文放本地</li>
</ul>
<div class="callout success">
<strong>工程质量</strong> —— 签名这一步没有造轮子,调 Google 官方库;而需要造轮子的地方(AXML/ARSC/ZIP 对齐)是真造了。判断得很清楚。
</div>
<h2 id="engine"><span class="num">§3</span>浏览器双核引擎</h2>
<p><span class="fileref">core/engine/BrowserEngine.kt:11-122</span> 定义统一接口,<code>loadUrl()</code> / <code>evaluateJavascript()</code> / <code>canGoBack()</code> / <code>getShields()</code></p>
<h3>GeckoView · 真的把 Firefox 塞进 APK</h3>
<p><span class="fileref">core/engine/GeckoViewEngine.kt:30-80</span></p>
<pre><code class="language-kotlin">import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession
// ...
private val runtime = GeckoRuntime.create(context, runtimeSettings) // line 63
</code></pre>
<ul>
<li>真·依赖 <code>org.mozilla.geckoview</code> 包,不是字符串常量</li>
<li>配置了反追踪 / SafeBrowsing / Cookie 隔离(第 52-61 行)</li>
<li><strong><code>libxul.so</code> 不内置</strong>——<span class="fileref">build.gradle.kts:137-142</span> 明确 <code>exclude</code>,由 <code>GeckoEngineDownloader.kt</code> 首次启动按需下载(~150MB,太大不能内置)</li>
</ul>
<h3>System WebView · 备选路径</h3>
<p><span class="fileref">core/engine/SystemWebViewEngine.kt</span> 提供完整 fallback,走 Android 自带 WebView(Chromium 内核)。</p>
<h2 id="runtime"><span class="num">§4</span>多运行时集成</h2>
<p>这是最反直觉的部分——一个"套壳工具"竟然真的打包了 Node.js / PHP / Python / Go 运行时。</p>
<table>
<tr><th>运行时</th><th>实现</th><th>评价</th></tr>
<tr>
<td>Node.js</td>
<td><span class="fileref">core/nodejs/NodeRuntime.kt</span> + JNI 桥 <code>NodeBridge.kt</code><br>调用 <code>node::Start()</code> 进程内跑</td>
<td class="score">真 · 但单进程仅能启动一次(nodejs-mobile 限制,18-26 行注释写明)</td>
</tr>
<tr>
<td>PHP</td>
<td><span class="fileref">core/php/PhpAppRuntime.kt:70-72</span><code>nativeLibraryDir</code> 加载<br><span class="fileref">build.gradle.kts:250-284</span> <code>downloadPhpBinary</code> task 从 pmmp/PHP-Binaries 下载 PHP 8.4</td>
<td class="score">真 · 绕 SELinux <code>execute_no_trans</code> 正确姿势</td>
</tr>
<tr>
<td>Python</td>
<td><code>PythonRuntime.kt</code> + ProcessBuilder<br>下载 CPython,支持 Flask / Django / FastAPI</td>
<td class="score"></td>
</tr>
<tr>
<td>Go</td>
<td><code>GoRuntime.kt</code> + ProcessBuilder</td>
<td class="score mid">轻度 · 只支持预编译的 Go 程序</td>
</tr>
<tr>
<td>"Linux"</td>
<td><span class="fileref">core/linux/PerformanceOptimizer.kt</span>(2809 行)</td>
<td class="score low">名不副实 · 其实是纯 Kotlin 写的图片/JS/CSS minify 工具,跟 Linux 没关系</td>
</tr>
</table>
<div class="callout">
<strong>为什么 PHP 要从 <code>nativeLibraryDir</code> 起?</strong> —— Android 的 SELinux 策略 <code>untrusted_app_*</code> 域对 <code>/data/data/*/files</code> 下的可执行文件施加 <code>execute_no_trans</code>,直接 exec 会被 deny。但 <code>nativeLibraryDir</code>(即 <code>/data/app/~~xxx/lib/arm64</code>)是可执行的。所以把 PHP 二进制当作 <code>libphp.so</code><code>jniLibs/</code>,运行时换名执行,绕过限制。这是 Termux 之外极少数在普通用户应用里跑原生可执行文件的正确做法。
</div>
<h2 id="crypto"><span class="num">§5</span>加密与加固</h2>
<h3>crypto/ · 教科书级 AES-GCM + PBKDF2</h3>
<ul>
<li><code>AesCryptoEngine.kt</code>:AES-256-GCM + PBKDF2 派生密钥</li>
<li><span class="fileref">core/crypto/KeyManager.kt:48-95</span>:按<em>包名 + 签名证书</em>派生,每个打出去的 APK 拿到的密钥都不一样——防止一个 APK 的密钥被泄露后影响其他用户</li>
<li><code>AssetEncryptor.kt</code> + <code>SecureAssetLoader.kt</code>:资源文件 AES 加密存储,加载时透明解密</li>
</ul>
<h3>disguise/ · 浏览器指纹三层伪装</h3>
<p><span class="fileref">core/disguise/BrowserDisguiseEngine.kt:39-80</span></p>
<ul>
<li>Level 1:<code>BrowserKernel</code>——改 HTTP headers + 注入基础 JS</li>
<li>Level 2-5:<code>BrowserDisguise</code>——<strong>22 个向量</strong>指纹伪装:Canvas / WebGL / Audio / Screen / Timezone / Fonts / Battery / Plugins...</li>
<li>OAuth 专用层:<code>OAuthCompatEngine</code> 针对 Google / Facebook / Microsoft 的登录反检测</li>
<li>动态 JS 生成:<code>BrowserDisguiseJsGenerator.kt</code> 每次启动生成新的伪装脚本,防指纹收敛</li>
</ul>
<h3>blacktech/ · 这块是 PPT 功能</h3>
<p><span class="fileref">core/blacktech/BlackTechConfig.kt</span>(203 行)</p>
<ul>
<li>"核弹模式""隐身模式""摩斯电码 SOS"——听起来很酷</li>
<li>实际字段 <code>forceMaxPerformance</code> / <code>forceBlockPowerKey</code> / <code>forceAirplaneMode</code><em>需要系统级权限</em>,用户态 APK 根本拿不到</li>
<li>判断:这部分是给商业版 Pro/Ultra 宣传页用的噱头配置,真实效果不及一半</li>
</ul>
<h2 id="extension"><span class="num">§6</span>扩展系统 + AI 集成</h2>
<h3>Chrome 扩展兼容层(31 文件 · 22661 行,单模块最大)</h3>
<ul>
<li><span class="fileref">core/extension/ChromeExtensionParser.kt:77-243</span>:<em>真的解析 <code>manifest.json</code></em>,支持 <code>content_scripts</code> / <code>permissions</code> / <code>background</code> 标准字段</li>
<li><span class="fileref">core/extension/ChromeExtensionPolyfill.kt:1409</span>:实现 <code>chrome.runtime.sendMessage()</code> 等核心 API</li>
<li><code>DeclarativeNetRequestEngine.kt</code>:Manifest V3 的声明式网络请求引擎</li>
<li><span class="fileref">core/extension/ExtensionFileManager.kt:399-407</span>:扫描嵌套目录查找 manifest.json</li>
</ul>
<div class="callout success">
<strong>可以原生运行简单的 Chrome 扩展</strong> —— 广告拦截、暗色模式、脚本注入这类 content script 扩展,基本都能跑起来。国内少见的完整移动端 Polyfill。
</div>
<h3>AI 多模型适配</h3>
<p><span class="fileref">core/ai/AiApiClient.kt:30</span>——支持 <strong>Anthropic Claude / Google Gemini / OpenAI / OpenRouter</strong> 四家:</p>
<ul>
<li>Anthropic header:<code>anthropic-version: 2023-06-01</code>(正确的最新版本)</li>
<li>资源文件 <code>litellm_model_prices.json</code>(39KB)包含 100+ 模型的即时价格——说明作者考虑了成本</li>
<li><strong>无硬编码 API Key</strong>:<code>grep -r "sk-\|claude-\|AIza"</code> 零命中,用户自带凭证。正确的设计</li>
</ul>
<h2 id="commercial"><span class="num">§7</span>商业化阉割证据链</h2>
<p>前面几节都在夸,这节是批判。三条硬证据指向"这是商业产品的引流版":</p>
<h3>证据 1:硬编码云服务端</h3>
<p><span class="fileref">core/cloud/CloudApiClient.kt:30</span></p>
<pre><code class="language-kotlin">const val BASE_URL = "https://api.shiaho.sbs"
// 第 50 行: POST /api/v1/activation/redeem 激活码兑换
// 第 80 行: POST /api/v1/activation/preview 激活码预览
// 还有云备份 / 通知 / 分析完整后端</code></pre>
<p><code>.sbs</code> 是典型的低价短期域名(<em>Side Business</em>),常用于灰色项目——但本项目看起来更像是作者给商业版选的廉价域名而已。真正的问题是这个 URL 是<em>硬编码</em>的,上游随时改协议你就挂。</p>
<h3>证据 2:本地激活码系统</h3>
<p><span class="fileref">core/activation/ActivationManager.kt:20-507</span></p>
<ul>
<li>支持 4 种类型:<code>PERMANENT</code> / <code>TIME_LIMITED</code> / <code>USAGE_LIMITED</code> / <code>DEVICE_BOUND</code></li>
<li>第 441-448 行用常量时间比较防时序攻击——<em>写得很认真</em>,说明作者是真的想把激活系统做稳</li>
<li>时限/用途限制持久化到 DataStore(第 281-305 行)</li>
<li>但开源代码里看不到"如果未激活则禁用 Pro 功能"的判断——那部分可能在私有仓库,或者用代码混淆隐藏了</li>
</ul>
<h3>证据 3:Google Play 订阅</h3>
<p><span class="fileref">core/billing/BillingManager.kt:15-25</span></p>
<pre><code class="language-kotlin">enum class SubscriptionSku(val id: String) {
PRO_MONTHLY("pro_monthly"),
PRO_QUARTERLY("pro_quarterly"),
PRO_YEARLY("pro_yearly"),
ULTRA_MONTHLY("ultra_monthly"),
ULTRA_QUARTERLY("ultra_quarterly"),
ULTRA_YEARLY("ultra_yearly"),
}</code></pre>
<ul>
<li>6 档订阅 SKU,Pro / Ultra 两个级别 × 月/季/年</li>
<li>依赖 <code>com.android.billingclient:billing-ktx:7.0.0</code></li>
<li>订阅状态检查:第 300-328 行</li>
</ul>
<div class="callout danger">
<strong>结论</strong> —— 激活码 + Google Play 订阅 + 硬编码云服务端,三合一。这不是社区项目,是产品。代码是"看我技术多硬,来用我商业版吧"的精装样板间。
</div>
<h2 id="matrix"><span class="num">§8</span>技术评分矩阵</h2>
<table>
<tr><th>模块</th><th>评分</th><th>类型</th><th>是否值得抄</th></tr>
<tr><td>APK 构建引擎</td><td class="score">9/10</td><td>真功夫</td><td>★★★★★ 全项目最高价值</td></tr>
<tr><td>加密体系</td><td class="score">8/10</td><td>真功夫</td><td>★★★★ <code>KeyManager.kt</code> 按包名派生思路</td></tr>
<tr><td>Chrome 扩展 Polyfill</td><td class="score">8/10</td><td>真功夫</td><td>★★★★ 移动端 Manifest V3 稀缺参考</td></tr>
<tr><td>GeckoView 引擎</td><td class="score">8/10</td><td>真集成</td><td>★★★ Firefox 内核接入套路</td></tr>
<tr><td>AI 多模型适配</td><td class="score">7/10</td><td>真功夫</td><td>★★★ 无硬编码 Key,干净</td></tr>
<tr><td>多运行时</td><td class="score">7/10</td><td>真集成</td><td>★★★ PHP/Node SELinux 绕行技巧</td></tr>
<tr><td>指纹伪装 disguise/</td><td class="score mid">7/10</td><td>部分真</td><td>★★ 伦理有疑问,慎用</td></tr>
<tr><td>激活 + 订阅</td><td class="score mid">6/10</td><td>调包</td><td>★ 商业化模板参考</td></tr>
<tr><td>i18n 翻译</td><td class="score low">3/10</td><td>水分</td><td>✗ 机器翻译体积膨胀</td></tr>
<tr><td>BlackTech "黑科技"</td><td class="score low">3/10</td><td>PPT 功能</td><td>✗ 权限拿不到</td></tr>
</table>
<h2 id="verdict"><span class="num">§9</span>最终定论</h2>
<h3>三问三答</h3>
<ol>
<li><strong>是不是噱头?</strong> 不是。APK 构建引擎是真家伙,单拎出来都够发一篇技术文章。</li>
<li><strong>是不是纯开源神器?</strong> 不是。是商业产品的引流版,有闭源后端 + 订阅 + 激活码。</li>
<li><strong>值不值得看?</strong> 非常值得。作为 Android 底层学习材料,是近年来质量最高的开源项目之一。</li>
</ol>
<h3>抄代码优先级</h3>
<ol>
<li><span class="fileref">core/apkbuilder/</span> 整块 —— 字节级 APK 操作,<em>无替代品</em></li>
<li><span class="fileref">core/crypto/KeyManager.kt:48-95</span> —— 按包名派生密钥的设计</li>
<li><span class="fileref">core/engine/BrowserEngine.kt</span> + <span class="fileref">GeckoViewEngine.kt</span> —— GeckoView 集成套路</li>
<li><span class="fileref">core/extension/ChromeExtensionParser.kt</span> —— Manifest V3 解析 + Polyfill</li>
<li><span class="fileref">core/php/PhpAppRuntime.kt:70-72</span> —— SELinux <code>execute_no_trans</code> 绕行技巧</li>
<li><span class="fileref">core/ai/AiApiClient.kt</span> —— 多 LLM 适配干净实现</li>
</ol>
<h3>谁适合用它</h3>
<ul>
<li>✅ Android 开发者想学 APK 字节结构和打包原理</li>
<li>✅ Web 开发者想给自己的站点快速套壳自用(别上架商业应用)</li>
<li>✅ 研究 Android 加密 / 反爬 / 扩展系统的人</li>
<li>❌ 追求严肃商业落地的团队 —— 用 Capacitor / Tauri Mobile</li>
<li>❌ 想要活跃社区生态的 —— 14 次提交 + 单人维护,作者跑路就死</li>
</ul>
<h3>一句话总结</h3>
<div class="callout success">
<strong>"技术优先"的商业应用</strong> —— 作者没在代码里埋 backdoor,也没伪装开源诚意,而是很透明地展示整个技术栈,然后用闭源后端变现。这种策略值得尊重。<em>抄他的技术,别依赖他的服务</em>
</div>
<div class="callout warn">
<strong>注意风险</strong> —— 如果你真的要用这个工具给客户打 APK:<br>
1) 删除 <code>core/cloud/</code> 里所有对 <code>api.shiaho.sbs</code> 的调用;<br>
2) 禁用 <code>core/activation/</code> 的激活校验(或改为永久版);<br>
3) 把 <code>core/billing/</code> 整块删掉,避免无意中触发 Google Play 的订阅校验;<br>
4) 自己的签名证书,自己的密钥管理,不要信它的云备份。
</div>
<p style="margin-top:40px;padding-top:20px;border-top:1px solid var(--border);color:var(--fg-muted);font-size:12px;">
本文所有论断均基于 <code>git clone --depth=1 https://github.com/shiahonb777/web-to-app</code>(2026-04-13 快照)的源码,采样 + 精读 + grep 验证。若后续提交改变了实现,请以上游为准。
</p>
</main>
</div>
</body>
</html>