WebToApp 源码解析: apkbuilder 字节级分析 + docs 站
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
source/
|
||||||
255
.memory/source-analysis.md
Normal file
255
.memory/source-analysis.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# WebToApp 源码深度解析
|
||||||
|
|
||||||
|
> **仓库**:https://github.com/shiahonb777/web-to-app
|
||||||
|
> **Clone 路径**:`source/`(gitignored)
|
||||||
|
> **分析日期**:2026-04-13
|
||||||
|
> **分析方法**:随机采样 + 关键文件逐行精读 + grep 证据链
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、项目元数据
|
||||||
|
|
||||||
|
| 指标 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 创建时间 | 2025-11-26(5 个月) |
|
||||||
|
| 最后推送 | 2026-04-12 |
|
||||||
|
| Stars / Forks | 2465 / 353 |
|
||||||
|
| 仓库大小 | 19.6 MB(Kotlin 源码 33MB) |
|
||||||
|
| 提交数 | **仅 14 次**(典型代码 dump,非社区开发) |
|
||||||
|
| 文件数 | 522 个 .kt 文件,约 10 万行 |
|
||||||
|
| 技术栈 | Kotlin 1.9 + Jetpack Compose + Room + KSP + Coroutines + Flow |
|
||||||
|
| versionCode | 32(v1.9.5)|
|
||||||
|
| 模块数 | `core/` 下 43+ 个子模块 |
|
||||||
|
|
||||||
|
**一句话定位**:这是某个商业产品(`shiaho.sbs` 品牌)的**阉割版开源引流版**,核心代码开源但服务端+激活码+订阅闭源。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、核心发现(按评分排序)
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐⭐ 1. APK 构建引擎 —— 真功夫,全项目最高价值
|
||||||
|
|
||||||
|
**位置**:`app/src/main/java/com/webtoapp/core/apkbuilder/`(480KB 源码)
|
||||||
|
|
||||||
|
**关键实锤**:
|
||||||
|
|
||||||
|
1. **自实现 AXML 二进制编辑器** —— `AxmlEditor.kt:26-248`
|
||||||
|
- UTF-8 / UTF-16LE 双编码支持
|
||||||
|
- 字节对齐替换(修改字符串池必须保持字节长度)
|
||||||
|
- 支持清除 `android:testOnly`、权限修复、组件类名恢复
|
||||||
|
|
||||||
|
2. **自实现 ARSC 资源表编辑器** —— `ArscEditor.kt:32-79`
|
||||||
|
- 第 10-128 行有详细注释,说明作者深入理解 APK 字节格式
|
||||||
|
- 可修改 `resources.arsc` 中的字符串(如应用名称本地化)
|
||||||
|
|
||||||
|
3. **集成 Google 官方 apksig 库**
|
||||||
|
- `build.gradle.kts:210` → `com.android.tools.build:apksig:8.3.0`
|
||||||
|
- `JarSigner.kt:8` 导入 `com.android.apksig.ApkSigner`
|
||||||
|
- 支持 v1/v2/v3 签名,SHA-256withRSA 2048bit RSA
|
||||||
|
- 支持 Android KeyStore 和 PKCS12 证书切换
|
||||||
|
|
||||||
|
4. **真·流式 ZIP 写入** —— `ZipUtils.kt:92-122`
|
||||||
|
- 10MB+ 文件用 STORED 模式(先算 CRC32 再流式写)避免 OOM
|
||||||
|
- `ApkBuilder.kt:1022` 注释写明:Node 二进制 ~40MB 走 streaming write
|
||||||
|
|
||||||
|
5. **Zip 4 字节对齐** —— `ZipAligner.kt:23-156`
|
||||||
|
- 自己实现对齐(Java `ZipOutputStream` 不暴露流位置,官方 API 无法做到)
|
||||||
|
|
||||||
|
**评价**:**国内罕见的字节级 APK 操作实现**。不是调 `Android Gradle Plugin`,不是调 AAPT,而是完全手搓。这块单拎出来就是一个独立项目的价值。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐ 2. 浏览器引擎抽象 —— 真集成 GeckoView
|
||||||
|
|
||||||
|
**位置**:`core/engine/`
|
||||||
|
|
||||||
|
- `BrowserEngine.kt:11-122` 定义统一接口(`loadUrl`、`evaluateJavascript`、`getShields`...)
|
||||||
|
- `GeckoViewEngine.kt:30-80` 真·导入 `org.mozilla.geckoview.*`
|
||||||
|
- `GeckoRuntime.create(context, settings)` 运行时单例(第 63 行)
|
||||||
|
- 反追踪、SafeBrowsing、Cookie 隔离配置(第 52-61 行)
|
||||||
|
- **Gecko 原生库不内置**:`build.gradle.kts:137-142` 明确排除 `libxul.so`,由 `GeckoEngineDownloader.kt` 按需下载(减小主 APK 体积)
|
||||||
|
- `SystemWebViewEngine.kt` 提供备选路径
|
||||||
|
|
||||||
|
**评价**:真的把 Firefox 内核塞进了 Android 壳应用,不是字符串常量。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐ 3. 多运行时 —— 真打包真跑
|
||||||
|
|
||||||
|
| 运行时 | 实现 | 真假 |
|
||||||
|
|------|------|------|
|
||||||
|
| **Node.js** | `NodeRuntime.kt` + JNI 桥 `NodeBridge.kt` | 真:调用 `node::Start()` 进程内跑,但 `NodeRuntime.kt:18-26` 注释说 libnode.so 是共享库,**单进程只能启动一次**(nodejs-mobile 限制) |
|
||||||
|
| **PHP** | `PhpAppRuntime.kt` + ProcessBuilder | 真:`build.gradle.kts:250-284` 有 `downloadPhpBinary` gradle task,从 `pmmp/PHP-Binaries` 下载 PHP 8.4;`PhpAppRuntime.kt:70-72` 优先从 `nativeLibraryDir` 加载(绕 SELinux `execute_no_trans`) |
|
||||||
|
| **Python** | `PythonRuntime.kt` + ProcessBuilder | 真:下载 CPython,支持 Flask/Django/FastAPI |
|
||||||
|
| **Go** | `GoRuntime.kt` + ProcessBuilder | 轻度:只支持预编译的 Go 程序 |
|
||||||
|
| **"Linux"** | `PerformanceOptimizer.kt`(2809 行)| 名不副实:其实是纯 Kotlin 做的图片压缩/JS/CSS minify |
|
||||||
|
|
||||||
|
**评价**:真打包了二进制,但通过下载策略(而非内置)控制主 APK 体积。设计合理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐⭐ 4. 商业化模块 —— 阉割版铁证
|
||||||
|
|
||||||
|
**铁证 1:硬编码云服务端** —— `CloudApiClient.kt:30`
|
||||||
|
```kotlin
|
||||||
|
const val BASE_URL = "https://api.shiaho.sbs"
|
||||||
|
```
|
||||||
|
- `/api/v1/activation/redeem`(第 50 行):激活码兑换
|
||||||
|
- `/api/v1/activation/preview`(第 80 行):激活码预览
|
||||||
|
- 完整的云备份 / 通知 / 分析后端
|
||||||
|
|
||||||
|
**铁证 2:激活码系统** —— `ActivationManager.kt:20-507`
|
||||||
|
- 4 种激活码类型:`PERMANENT` / `TIME_LIMITED` / `USAGE_LIMITED` / `DEVICE_BOUND`
|
||||||
|
- 常量时间比较防时序攻击(第 441-448 行)
|
||||||
|
- 时限/用途持久化到 DataStore(第 281-305 行)
|
||||||
|
|
||||||
|
**铁证 3:Google Play 订阅** —— `BillingManager.kt:15-25`
|
||||||
|
- 6 个 SKU:`pro_monthly/quarterly/yearly`、`ultra_monthly/quarterly/yearly`
|
||||||
|
- 集成 `com.android.billingclient:billing-ktx:7.0.0`
|
||||||
|
- 订阅检查:第 300-328 行
|
||||||
|
|
||||||
|
**评价**:开源的是引流版,真正的商业版有闭源后端。作者的变现逻辑是清晰的。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐ 5. 加密与加固 —— 教科书级
|
||||||
|
|
||||||
|
**crypto/(15 文件,4459 行)**:
|
||||||
|
- `AesCryptoEngine.kt`:AES-GCM + PBKDF2 派生
|
||||||
|
- `KeyManager.kt:48-95`:按包名+签名派生,每个 APK 独立密钥
|
||||||
|
- `AssetEncryptor.kt` + `SecureAssetLoader.kt`:资源加密加载
|
||||||
|
|
||||||
|
**disguise/(5 文件,2773 行)** —— 浏览器指纹伪装:
|
||||||
|
- `BrowserDisguiseEngine.kt:39-80` 三层架构:
|
||||||
|
- Level 1:BrowserKernel(HTTP 头 + 基础 JS)
|
||||||
|
- Level 2-5:22 个指纹向量(Canvas / WebGL / Audio / Screen / Timezone...)
|
||||||
|
- OAuth 专用层:`OAuthCompatEngine`
|
||||||
|
- `BrowserDisguiseJsGenerator.kt`:动态生成伪装 JS
|
||||||
|
- 明确针对 Google/Facebook/Microsoft OAuth 反检测
|
||||||
|
|
||||||
|
**blacktech/(`BlackTechConfig.kt` 203 行)** —— ⚠️ 可疑:
|
||||||
|
- "核弹模式""隐身模式""摩斯电码""SOS 求救"
|
||||||
|
- `forceMaxPerformance` / `forceBlockPowerKey` / `forceAirplaneMode` 需要系统权限
|
||||||
|
- 用户应用层 APK 根本拿不到这些权限 → **可能是设计文档 / PPT 功能**
|
||||||
|
|
||||||
|
**评价**:加密真功夫,指纹伪装真功夫,"黑科技"模块水分大。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐ 6. Chrome 扩展兼容层 —— 真做到了
|
||||||
|
|
||||||
|
**extension/(31 文件,22661 行,单模块最大)**
|
||||||
|
|
||||||
|
- `ChromeExtensionParser.kt:77-243`:真的解析 `manifest.json`
|
||||||
|
- 支持 `content_scripts`、`permissions`、`background` 等
|
||||||
|
- 生成虚拟 `chrome-extension://` URL scheme
|
||||||
|
- `ChromeExtensionPolyfill.kt:1409`:实现 `chrome.runtime.sendMessage()`
|
||||||
|
- `DeclarativeNetRequestEngine.kt`:声明式网络请求规则(Manifest V3)
|
||||||
|
- `ExtensionFileManager.kt:399-407`:扫描嵌套目录找 manifest.json
|
||||||
|
|
||||||
|
**评价**:可以原生运行简单 Chrome 扩展。国内少见的完整实现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⭐⭐⭐⭐ 7. AI 模块 —— 多家 LLM 适配
|
||||||
|
|
||||||
|
**ai/(14 文件,10850 行)**
|
||||||
|
|
||||||
|
- `AiApiClient.kt:30` 支持 Claude / Gemini / OpenAI / OpenRouter
|
||||||
|
- Anthropic header:`anthropic-version: 2023-06-01`
|
||||||
|
- 资源文件 `litellm_model_prices.json`(39KB)包含 100+ 模型即时价格
|
||||||
|
- **无硬编码 API Key**(grep 未命中 sk-/claude-/AIza)—— 用户自带凭证,设计正确
|
||||||
|
|
||||||
|
**评价**:不是噱头,真做了多提供商抽象。价格表的存在说明考虑了成本优化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、体积与依赖
|
||||||
|
|
||||||
|
```
|
||||||
|
core/ 模块体积 Top:
|
||||||
|
i18n/ 1.4M ← 翻译资源(可能机器生成,体积 20%+ 水分)
|
||||||
|
extension/ 916K ← Chrome 扩展系统
|
||||||
|
apkbuilder/ 480K ← ★ APK 构建
|
||||||
|
webview/ 480K
|
||||||
|
ai/ 468K
|
||||||
|
cloud/ 328K
|
||||||
|
crypto/ 172K
|
||||||
|
linux/ 108K
|
||||||
|
|
||||||
|
assets/:
|
||||||
|
litellm_model_prices.json (39KB)
|
||||||
|
php_router_server.php (24KB)
|
||||||
|
sample_projects/ (40+ 示例)
|
||||||
|
|
||||||
|
jniLibs/: libxul.so 被排除,运行时下载
|
||||||
|
```
|
||||||
|
|
||||||
|
**APK 体积控制策略**:所有大二进制(GeckoView/Node/PHP/Python/Go)均通过 gradle task 下载 + 运行时下载,不直接打包。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、技术评分矩阵
|
||||||
|
|
||||||
|
| 模块 | 评分 | 类型 | 备注 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| APK 构建引擎 | 9/10 | 真功夫 | **全项目最高价值**,字节级 APK 操作 |
|
||||||
|
| 加密体系 | 8/10 | 真功夫 | PBKDF2+AES-GCM,按包名派生 |
|
||||||
|
| 浏览器引擎 | 8/10 | 真集成 | GeckoView 真接入 |
|
||||||
|
| Chrome 扩展 | 8/10 | 真功夫 | 完整 Polyfill |
|
||||||
|
| AI 多模型 | 7/10 | 真功夫 | 无硬编码 Key |
|
||||||
|
| 运行时集成 | 7/10 | 真集成 | 下载而非内置策略 |
|
||||||
|
| 指纹伪装 | 7/10 | 部分真 | Canvas/WebGL/Audio 真做 |
|
||||||
|
| 激活/订阅 | 6/10 | 调包 | 商业化模板 |
|
||||||
|
| "黑科技" BlackTech | 3/10 | 空壳 | 系统权限拿不到 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、用处与立项判断
|
||||||
|
|
||||||
|
### 能用来干什么
|
||||||
|
|
||||||
|
1. **给自己的网站套 Android 壳并上架** —— 能做,但要小心商业版激活码锁
|
||||||
|
2. **学习 APK 字节格式 / 打包原理** —— ⭐强烈推荐,`apkbuilder/` 就是教材
|
||||||
|
3. **学习 Android 加密最佳实践** —— ⭐推荐,`crypto/` 很扎实
|
||||||
|
4. **学习 Android 里跑多语言运行时** —— PHP/Node/Python 的集成套路可抄
|
||||||
|
5. **Chrome 扩展移动化** —— 稀缺参考
|
||||||
|
6. **做商业 SaaS 应用** —— ❌ 不推荐,别用别人的引流版
|
||||||
|
|
||||||
|
### 适合的人
|
||||||
|
|
||||||
|
- ✅ Android 开发者想学 APK 底层
|
||||||
|
- ✅ Web 开发者想给自己站点套壳自用
|
||||||
|
- ✅ 研究 Android 加密 / 反爬 / 扩展系统的人
|
||||||
|
- ❌ 追求严肃商业落地的团队(用 Capacitor / Tauri Mobile)
|
||||||
|
- ❌ 想要社区生态的(单人项目 + 商业背景,风险大)
|
||||||
|
|
||||||
|
### 抄代码优先级
|
||||||
|
|
||||||
|
1. **`core/apkbuilder/` 整块** —— 字节级 APK 操作,无替代品
|
||||||
|
2. **`core/crypto/KeyManager.kt`** —— 按包名派生密钥的设计
|
||||||
|
3. **`core/engine/BrowserEngine.kt` + `GeckoViewEngine.kt`** —— GeckoView 集成套路
|
||||||
|
4. **`core/extension/ChromeExtensionParser.kt`** —— manifest.json 解析
|
||||||
|
5. **`core/ai/AiApiClient.kt`** —— 多 LLM 适配
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、关键风险提示
|
||||||
|
|
||||||
|
1. **商业背景**:`https://api.shiaho.sbs` 硬编码,上游可能随时改协议
|
||||||
|
2. **代码 dump 风格**:14 次提交 = 社区贡献几乎不可能,作者跑路就死
|
||||||
|
3. **i18n 1.4MB 水分**:翻译疑似机器生成,不是真本地化投入
|
||||||
|
4. **BlackTech 模块噱头**:核弹模式等系统级功能权限拿不到
|
||||||
|
5. **GeckoView 按需下载**:首次启动有网络依赖,离线场景会报错
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、最终定论
|
||||||
|
|
||||||
|
**"是不是噱头"**:不是。APK 构建引擎这一块是真家伙,单独拎出来都够发一篇技术文章。
|
||||||
|
|
||||||
|
**"是不是纯开源神器"**:不是。是商业产品的引流版。
|
||||||
|
|
||||||
|
**"值不值得看"**:**非常值得**。作为 Android 底层学习材料,是近年来质量最高的开源项目之一。
|
||||||
|
|
||||||
|
**"该不该用在自己项目里"**:抄技术细节,别抄整个方案。尤其不要依赖 `https://api.shiaho.sbs` 和激活码系统。
|
||||||
4
Dockerfile
Normal file
4
Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
COPY site/ /usr/share/nginx/html/
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
19
nginx.conf
Normal file
19
nginx.conf
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/javascript application/json text/html;
|
||||||
|
gzip_min_length 1000;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public, no-transform";
|
||||||
|
}
|
||||||
|
}
|
||||||
497
site/index.html
Normal file
497
site/index.html
Normal 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>
|
||||||
Reference in New Issue
Block a user