【免费下载链接】WebUI-X-Portable
WebUI X from MMRL but portable!
WebUI X: Portable(简称 WXP)是运行在 Android 上的 WebUI 运行时宿主与模块开发环境,支持在 KernelSU、Magisk、APatch 及非 Root ADB 环境下运行、调试模块 WebUI。本文是模块开发指南的下篇,带你吃透三大核心机制——SuPathHandler、WebrootPathHandler与KernelSU I/O API,搞懂 WebUI X 是如何把手机上的真实文件“安全地”喂给前端的。
一、WebUI X 路径处理架构总览 💡
理解 WXP 的路径处理,先记住一句话:所有文件访问最终都汇聚到SuFile(dev.mmrlx.nio.SuFile)这个 su 权限的文件抽象上,再向上分三条对外出口。
| 出口 | 触发方式 | 核心文件 |
|---|---|---|
| HTTP 路由 | 浏览器请求mui.kernelsu.org/... | SuPathHandler.kt、WebrootPathHandler.kt |
JSksu.io() | 前端调用File/FileInputStream/RandomAccessFile | KsuIO.kt |
JSfs | 类 Node.js 的readFileSync/writeFile | FileSystemInterface.kt |
三条线共用同一个“万能工具函数”sufile,它负责把路径参数安全地转换成一个SuFile实例:
// [ExtraOptions.kt#L37-L45](https://link.gitcode.com/i/d9a3e63608f668c529cc97777ac28148) fun WebUI.sufile(vararg paths: Any): SuFile { val f = file(*paths) if (f !is SuFile) throw IllegalArgumentException("Provided file factory is not a `dev.mmrlx.nio.SuFile`") return f }为什么用
SuFile而不是java.io.File?因为 WXP 需要以 su 身份访问/data/adb等受保护目录,普通File拿不到权限。整个 I/O 层围绕它构建,是“Portable”的关键。
二、SuPathHandler:把 su 目录挂载成 URL 路由 🚀
SuPathHandler 只干一件事:把一个文件系统目录,映射成一个可被浏览器访问的 URL 前缀。它的实现极其精简,几乎只有一行核心逻辑。
// [SuPathHandler.kt#L9-L21](https://link.gitcode.com/i/008390535e1273d1f6fd27d21a5de789#L9-L21) fun RouteRegistry.suRoute(name: String, directory: String) { route(name) { try { sufile(directory, request.path).asResponse() } catch (e: IOException) { response(status = ResponseStatus.BAD_REQUEST, data = null) } } }四个预置挂载点
在 WebUIScreen.kt#L157-L173 中,WXP 一口气注册了四条suRoute,这也是你在开发者工具网络面板里看到的那些“隐藏路径”的由来:
| URL 前缀 | 映射目标 | 用途 |
|---|---|---|
/.{moduleId}/ | module.path.moduleDir | 模块根目录(按模块隔离) |
/.adb/ | adbPath.baseDir | ADB 基础目录 |
/.config/ | adbPath.configDir | 全局配置(.config) |
/.local/ | adbPath.localDir | 本地数据(.local) |
这些/.xxx前缀配合asResponse()(见 Responses.kt#L103-L142)把SuFile转成带正确 MIME、CORS 头的WebResourceResponse返回给前端。
三、WebrootPathHandler:WebUI 前端的静态资源服务器 🌐
如果说 SuPathHandler 是“通用管道”,那么 WebrootPathHandler.kt 就是真正服务模块 WebUI 前端(webroot/index.html等)的核心服务器,它承担了安全防护、样式注入、缓存与回退等复杂职责。
1)路径穿越防护(安全核心)⚠️
前端请求的request.path绝不能直接拼到目录后读取,否则会出现../../越权访问。WXP 用“规范化后必须仍是子路径”的校验来兜底:
// [WebrootPathHandler.kt#L80-L86](https://link.gitcode.com/i/e09c56f827b5e00fc8dbdc284784d438#L80-L86) val file = directory.getCanonicalFileIfChild(path) ?: run { Log.e("webrootPathHandler", "The requested file: $path is outside the mounted directory: $directory") return@route forbiddenResponse // 403 }2)historyFallback 单页应用回退
对于 SPA(单页应用),当请求的路由路径没有对应文件时,可回退到index.html。这一行为由 WebrootConfig.kt#L293-L297 的historyFallback/historyFallbackFile两个配置项控制。
3)CSS / JS 自动注入 ✨
WXP 会自动扫描config目录,把开发者准备的样式与脚本“无感注入”进 HTML:
config/style/*.css→ 注入<link rel="stylesheet">config/js/head/*.js→ 注入到</head>前config/js/body/*.js→ 注入到</body>前,且.mjs文件会自动补上type="module"
// [WebrootPathHandler.kt#L42-L59](https://link.gitcode.com/i/e09c56f827b5e00fc8dbdc284784d438#L42-L59) if (it.extension == "mjs") append(" type=\"module\"")4)CSP 合并 + 缓存 + ETag
- 内容安全策略:默认 CSP 定义在 WebrootConfig.kt#L299-L303,允许
'unsafe-inline'以兼容常见前端写法,同时限制connect-src *。 - 静态资源缓存:对
js / css / png / svg / woff2等扩展名下发Cache-Control: public, max-age=...(cachingMaxAge默认 86400 秒)。 - ETag 校验:
ETag = 文件大小-最后修改时间,让前端能做增量校验。
四、KernelSU I/O API:让 JS 直接读写文件 📁
这是 WXP 最具“扩展性”的部分。通过 KsuIO.kt 与 KernelSUInterface.kt#L307-L309,前端只需ksu.io()即可拿到一组受权限保护的文件对象。
四大核心对象
// [KsuIO.kt#L48-L53](https://link.gitcode.com/i/f0ac5c3caa337533a98d1f164319c4d8#L48-L53) fun JavaScriptScope.ksuIO(io: KsuIO) { function("File") { newObject { fileInterface(io, checkString(0)) } } function("FileInputStream") { newObject { fileInputStreamInterface(io) } } function("FileOutputStream") { newObject { fileOutputStreamInterface(io) } } function("RandomAccessFile") { newObject { randomAccessFileInterface(io) } } }| 对象 | 典型能力 | 底层 |
|---|---|---|
File | exists / list / mkdirs / renameTo / length / getUsableSpace | SuFile |
FileInputStream | open / read / available / close(分块读取,Base64 传输) | ManagedInputStream |
FileOutputStream | open / write / writeByte / flush / close(支持追加) | SuFileOutputStream |
RandomAccessFile | readInt / writeUTF / seek / length / setLength等随机访问 | SuRandomAccessFile |
资源生命周期统一管理 🔒
KsuIO用三个ConcurrentHashMap分别追踪打开的输入流、输出流与随机文件,并在 WebUI 销毁时统一closeAll,避免 su 权限下的文件句柄泄漏:
// [KsuIO.kt#L30-L36](https://link.gitcode.com/i/f0ac5c3caa337533a98d1f164319c4d8#L30-L36) onDestroy { closeAll(inputs) { it.close() } closeAll(outputs) { it.close() } closeAll(randomFiles) { it.close() } }fs 接口:类 Node.js 的文件读写
除了ksu.io(),WXP 还提供更接近 Node.js 习惯的fs命名空间(见 FileSystemInterface.kt#L20-L81),暴露readFile / readFileSync / writeFile / access / inputstream / outputstream等方法,并通过fs.constants提供O_RDONLY、O_CREAT等打开标志,方便从 Node 环境移植代码。
五、整体串联:一次请求的完整旅程 🧭
把所有模块串起来,就能看懂 WebUIScreen.kt 的装配顺序:
- 注册 I/O 工厂(WebUIScreen.kt#L83-L105):把
inputStreamFactory、outputStreamFactory、randomAccessFileFactory、fileFactory绑定到SuFile/SuRandomAccessFile。 - 注册 JS 接口(WebUIScreen.kt#L126-L135):挂载
mod、ksu、fs等命名空间。 - 注册路由(WebUIScreen.kt#L151-L176):
iconPathHandler→internalPathHandler→ 四条suRoute→webrootPathHandler。 - 启动 WebView:
WebUIView(wstate)渲染并接管。
开发提示:路由注册有先后顺序,
webrootPathHandler放在最后作为“兜底”,确保它接管所有未匹配的常规路径请求。
六、模块路径模型:ModulePath 与 AdbPath 📍
上面反复出现的moduleDir、webrootDir、configDir等,都由 ModulePaths.kt 统一推导,避免开发者手拼路径出错。
| 属性 | 相对位置 | 说明 |
|---|---|---|
moduleDir | {baseDir}/modules/{id} | 模块根目录 |
webrootDir | {moduleDir}/webroot | 前端静态资源目录 |
webrootConfig | {moduleDir}/webroot/config.json | WebUI 配置文件 |
configDir | {baseDir}/.config/{id} | 模块私有配置 |
serviceFiles | service.sh/action.sh/sepolicy.rule等 | 生命周期脚本 |
这些路径既会通过mod.path/mod.adbPath暴露给 JS,也会以LuaTable形式喂给 Lua 插件(见 ModulePaths.kt#L113-L139),实现跨脚本语言的统一访问。
七、开发实战清单 ✅
- 前端静态资源:放在
webroot/,自定义样式放.config/{id}/style/,脚本放.config/{id}/js/head|body/。 - SPA 路由:在
config.json开启historyFallback: true,避免刷新 404。 - 读写系统文件:优先用
fs.readFileSync/ksu.io().RandomAccessFile,注意close释放句柄。 - 安全红线:任何路径拼接都要经过“规范化 + 子路径校验”,不要绕过
getCanonicalFileIfChild。 - 权限申请:危险接口受
Permissions.KSU.IO/MX.IO约束(见 Permissions.kt#L34-L42),调用前确保config.json已声明相应权限。
想跑起来体验?克隆仓库后用
./gradlew assembleDebug构建即可:git clone https://gitcode.com/gh_mirrors/we/WebUI-X-Portable
小结:WebUI X: Portable 的路径处理,本质是“一个SuFile抽象 + 三条受控出口”。SuPathHandler负责把目录变成 URL,WebrootPathHandler负责把 WebUI 前端安全地端出来,KernelSU I/O API则把 su 级别的文件能力安全地交给 JS。理解了这条主线,你就能自信地为模块编写前后端联动的 WebUI 了。
【免费下载链接】WebUI-X-Portable
WebUI X from MMRL but portable!
相关推荐
5 分钟跑通 terminal-bench:给你的 AI 终端代理做一场硬核考试
5 分钟跑通 terminal bench:给你的 AI 终端代理做一场硬核考试 当你的 AI 代理在聊天框里口若悬河,一到真实终端就频频翻车,靠人工抽查脚本验
人工智能大模型AI 评测Agent 评测KernelSU模块开发指南:WebUI交互功能详解
KernelSU模块开发指南:WebUI交互功能详解 前言 在Android系统模块化开发领域,KernelSU提供了一种创新的WebUI交互方式,让模块开发者
操作系统内核驱动系统编程移动开发KernelSU 模块开发完全指南:systemless 机制、模块结构与启动脚本详解
KernelSU 模块开发完全指南:systemless 机制、模块结构与启动脚本详解 KernelSU 提供了一套与 Magisk 高度相似的 模块机制 ,让
操作系统内核驱动系统编程移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考