Files
webtoapp-docs/site/index.html

498 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>