🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

安卓网页外壳 Lian-peiji/dsh-android-shell

DeepSeek Harness
Lian-peiji15 天前有提交已提供中文文档扫描:无法判定

把自托管网页装进手机桌面,像原生应用一样全屏运行

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Lian-peiji/dsh-android-shell
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么[目录]—
dsh 原生插件 · ui
暂无时间戳·判据 →
装得上吗[实装验证]!
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
数据截至 2026/10/21·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 15 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

⚠︎ 实装验证未通过(unknown · 2026/10/21) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
安全扫描自动 · 静态分析源码
v2.7.0
⚪ 扫描:无法判定尚无人工评估
展开查看扫描说明与证据 ↓

已尝试扫描但取不到可分析的源码:仓库或包不存在、已删除,或为非 JS/TS 实现、未随包发布源码。无法判定,不代表安全。

  • · 包内无可扫 JS
  • · TS 源码(可能为非 JS 实现或未发布源码),无法判定
扫描于 2026/11/4(0 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

把自托管 Web 应用装进手机桌面的零依赖安卓 WebView 外壳:边到边 / 系统栏常驻 / 键盘让位 / 安全区去重(零 Gradle 构建)

README

由 DeepSeek 最新模型翻译生成
DSH Android Shell 把自托管的 Web 应用,变成手机上「像原生一样」的 App。 一个单 Activity 的 WebView 外壳:零第三方依赖、零 Gradle、约 540 行 Java(含完整中文注释)。 专门解决浏览器「加到桌面」做不到的那部分 —— 边到边、系统栏常驻、键盘让位、拍平页面与原生窗口之间重复的安全区留白。 典型用法:家里 PC 上跑着 DSH(DeepSeek Harness),用 dsh-pocket 的 Cloudflare 命名隧道把它发布成一个固定 https 域名,再用这个外壳把它装进手机桌面。任何自托管 Web 应用同样适用。 ⚠️ 前置依赖(先读这段) 本壳不提供手机访问入口,也不改变页面在窄屏下的布局——这两件事由服务端的 dsh-pocket 插件负责(反向代理 + 密码校验 + 移动端样式注入)。 换句话说:这是一个「窗口」,不是一个「应用」。使用前你的服务端需要满足: 1. DSH 所在 profile 已安装并启用 dsh-pocket,已开好手机可访问的入口(局域网地址或隧道域名); 2. 把该入口地址填进 START_URL,再按「快速开始」重新编译——仓库里的 YOUR-DOMAIN 是占位符,直接编译出来的包打不开任何真实页面。
展开完整文档(README 剩余 1 节)
DSH Android Shell

把自托管的 Web 应用,变成手机上「像原生一样」的 App。

一个单 Activity 的 WebView 外壳:零第三方依赖、零 Gradle、约 540 行 Java(含完整中文注释)。
专门解决浏览器「加到桌面」做不到的那部分 —— 边到边、系统栏常驻、键盘让位、拍平页面与原生窗口之间重复的安全区留白。

典型用法:家里 PC 上跑着 DSH(DeepSeek Harness),用 dsh-pocket 的 Cloudflare 命名隧道把它发布成一个固定 https 域名,再用这个外壳把它装进手机桌面。任何自托管 Web 应用同样适用。

⚠️ 前置依赖(先读这段)

本壳不提供手机访问入口,也不改变页面在窄屏下的布局——这两件事由服务端的 dsh-pocket 插件负责(反向代理 + 密码校验 + 移动端样式注入)。

换句话说:这是一个「窗口」,不是一个「应用」。使用前你的服务端需要满足:

1. DSH 所在 profile 已安装并启用 dsh-pocket,已开好手机可访问的入口(局域网地址或隧道域名);
2. 把该入口地址填进 START_URL,再按「快速开始」重新编译——仓库里的 YOUR-DOMAIN 是占位符,直接编译出来的包打不开任何真实页面。

只装这个 App、不装 dsh-pocket:能启动,但连不上(或只有桌面版排版)。两者是互补关系,不是二选一。

📖 想搞懂每一层在干什么:见 docs/术语与原理.md —— 网络 / 视口 / PWA / 安卓原生 / 构建链 / 插件机制,全篇「大白话 + 专业名词 + 在本项目里的位置」。

背景:为什么不能直接用浏览器的「安装应用」

在没有 Google Play 服务的安卓机上,浏览器的「安装到桌面」是骗人的:

- 把网页「安装」成真 App(WebAPK)需要经 Google 服务器铸造。缺 GMS 时浏览器只是静默降级成一个快捷方式,但弹窗照样写着「安装应用」。
- 怎么识别:长按桌面图标只有「移除」、没有「应用信息」;系统应用列表里也找不到它。
- 后果:窗口由浏览器自己的 Activity 决定,manifest、display: standalone/fullscreen 一律无效,于是出现这一串经典症状:
- 顶部一块死黑(状态栏被藏起来了);
- 底部一条白线/黑线(根本没做边到边);
- 呼出系统导航栏时整页抖(窗口高度变化 → 页面重排);
- env(safe-area-inset-) 全是 0。

只有原生窗口能拿到系统的 WindowInsets,也只有它能决定「系统栏常驻」。 所以正确做法是自己写一个极薄的壳 —— 就是这个项目。

它做了什么

按重要性排,这也是整个项目真正的价值所在:

1)边到边 + 系统栏常驻,把 insets 换算成「容器 padding」 targetSdk 35 + setDecorFitsSystemWindows(false),再把 systemBars() | displayCutout() 的 insets 换算成外层容器的 padding。
三条通道兜底:① 建视图时先读系统资源 status_bar_height 垫底;② 容器的 OnApplyWindowInsetsListener(正常路径);③ 布局完成后 root.post() 里再读一次 getRootWindowInsets()(有的机型不派发监听器)。
顶部只在这三条都拿不到正值时才用兜底值,绝不把 padding 归零。窗口尺寸恒定 → 呼出导航栏不会抖。

2)底部不留白:容器的底部 padding 恒为 0 让 WebView 一直铺到物理底边,底部那点抬升交给网页自己做。
如果由容器留底边距,屏幕最底下会露出窗口背景色 —— 一条黑边,输入框看起来「和屏幕底部不贴合」。

3)键盘让位:自己按 IME 高度给容器 padding
setDecorFitsSystemWindows(false) 之后系统不再替窗口让位,必须自己处理:键盘弹出时取
insets.getInsets(WindowInsets.Type.ime()).bottom 作为容器底部 padding,平时为 0。
否则键盘只是「盖」在页面上,输入框被死死埋在键盘底下 —— 打了什么字都看不见。
(代码里还判断了引擎是否已经自己缩过视口,避免缩两次。)

4)往页面注入三条 CSS(onPageFinished 时注入一个 ,幂等)

/ a. 让输入框躲开手势条:用 max() 取大,不要相加 —— 页面自己也抬过一次 /
[data-composer-seat]{ padding-bottom: max(env(safe-area-inset-bottom, 0px), <系统栏高度>px) !important }

/ b. 键盘态把根容器钉平:压过某些移动端皮肤用 visualViewport 推算的 fixed + top:var(--vv-top) / html body [id=root][id=root][id=root][id=root]{ position: relative !important; top: 0 !important; height: 100% !important; max-height: 100% !important } / c. 掐掉文档级滚动与右侧滚动条(SPA 自己管内部滚动) */ html, body { overflow: hidden !important; overscroll-behavior: none !important; height: 100% !important }
[data-conversation-scroll] { scrollbar-width: none !important }
::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important }

权重技巧:两条规则都带 !important 时,按选择器 specificity 决胜、与注入先后无关。
这里用重复属性名把权重顶高([id=root][id=root] 是 (0,4,0)),因此不需要改第三方皮肤或网页的源码,对方升级也不会把补丁覆盖掉。

5)修「浏览器能开、App 里 401」服务端每次重启端口都会变,之前签发的 cookie 会烂在 WebView 里;而反代只在请求还没带这个 cookie 时才补发 launch token → 上游对不上就 401。
做法:启动时先 CookieManager.removeAllCookies() + flush() 再 loadUrl(...);onPause() 里 flush() 让登录 cookie 落盘。

6)开屏遮白首屏 + 密码握手要几百毫秒到几秒。先盖一层浅色开屏(插画 + 标题),onPageFinished 后淡出;下限 700 ms(防一闪而过)、上限 12 s(防卡在插画上退不出去)。

7)杂项:附件选择 onShowFileChooser、返回键 goBack()、onPause flush cookie、系统栏图标走浅色。

快速开始

0. 前置
- JDK 17(javac / keytool)
- Android SDK:build-tools/35.0.0(aapt2、d8、zipalign、apksigner)+ platforms/android-35/android.jar
- python3(只用来把 classes.dex 追加进 APK,十几行脚本)

1. 准备你的入口地址如果你也是用 DSH + dsh-pocket:打开 设置 → 手机访问,选命名隧道,填自己的域名,设一个自定义访问密码(这样它不会每次开启都轮换)。记下 域名 和 密码。

链路:手机 → Cloudflare 边缘 → cloudflared(PC 主动拨出)→ 127.0.0.1:3081(本地反代验密码)→ 应用本体全程走出站连接,不需要公网 IP、不需要开放任何入站端口。

2. 改 3 处

| 位置 | 改什么 |
| proj/src/com/example/dshshell/MainActivity.java | package 换成你自己的包名;START_URL 换成 https://你的域名/?token=你的密码 |
| proj/AndroidManifest.xml | package 与上面一致;android:label(桌面显示名);versionCode / versionName |
| proj/res/ | 替换占位图标:mipmap-xxxhdpi/ic_launcher.png(192×192)、mipmap-xxxhdpi/ic_launcher_foreground.png(432×432)、drawable/splash_art.png(开屏插画);底色改 values/colors.xml |

3. 构建 bash bash build.sh # 产物:./DSH.apk

build.sh 顶部的 JDK / SDK / KS_PASS 支持用环境变量覆盖:JDK_HOME、ANDROID_SDK_ROOT、DSH_KS_PASS。

4. 安装与更新把 APK 传到手机点装(HyperOS 要给安装来源开「安装未知应用」)。
签名一致才能覆盖安装 —— 首次构建会生成 dsh.keystore,请务必保管好;丢了就只能换签名重打包,而手机上必须先卸载(本地数据清空)。

目录结构

build.sh 零 Gradle 构建脚本 proj/ ├── AndroidManifest.xml 权限、Activity、主题、configChanges ├── src/com/example/dshshell/MainActivity.java 全部逻辑(约 540 行,中文注释,建议通读) └── res/ ├── values/styles.xml 状态栏/导航栏/窗口底色 ├── values/colors.xml 自适应图标底色 ├── mipmap-anydpi-v26/ic_launcher.xml 自适应图标描述 ├── mipmap-xxxhdpi/ 占位图标(换成你自己的) └── drawable/splash_art.png 开屏插画

构建原理

不使用 Gradle / Android Studio,一条流水线跑完:

aapt2 compile 资源 → aapt2 link 打基座 APK(--java 生成 R.java) → javac 编 MainActivity.java → d8 生成 classes.dex → python zipfile 把 classes.dex 追加进 APK → zipalign -f -p 4 → keytool 生成密钥(首次)+ apksigner 签名

踩过的坑(都写在脚本注释里)

- javac 必须带 -encoding UTF-8:中文 Windows 默认 GBK,源码里有中文注释就直接报错。
- 不要写 R.drawable.xxx:本项目只编译 MainActivity.java,R.java 不参与编译 —— 一律改用
getResources().getIdentifier("名字", "drawable"|"mipmap"|"dimen", getPackageName())。
- d8 / apksigner 的 .bat 靠 JAVA_HOME 找 java,脚本里要先导出。
- aapt2 link 的 --version-code/--version-name 会被 manifest 里的同名属性盖掉,改版本要改 manifest。
- 只给一张老式方形 PNG 当图标,系统会套上一个白方块 —— 要走 mipmap-anydpi-v26/ 这套自适应图标。

兼容性

| 项 | 值 |
| 最低版本 | Android 8.0(API 26) |
| 目标版本 | Android 15(API 35,强制边到边) |
| 编译版本 | API 35 / Java 8 字节码 |
| 权限 | 仅 INTERNET、ACCESS_NETWORK_STATE |
| 第三方依赖 | 无 |
| 实测环境 | HyperOS(无 Google Play 服务的国产安卓机) |

已知限制

- 页面里的选择器([data-composer-seat]、[data-mobile-nav="frame"]、[data-conversation-scroll] 等)来自 DSH Web 本身与常见的移动端皮肤插件。换皮肤或应用升级后如果又冒出顶部空带 / 底部黑边,按第 1~4 条的原则重新对齐即可。
- 入口地址与密码写在源码里(换取「打开就来、不用每次输密码」)。别把你改好的源码公开出去 —— 要公开就保留占位符。
- 只处理窗口与视口,不碰应用本身的任何功能。

一句话总结

浏览器(或 WebView)负责画页面,原生外壳负责窗口。
视口和安全区只能由一方留白 —— 留两次,就一定会多出一条黑边或空带。

License

MIT
预览分享卡片长什么样 ↓Lian-peiji/dsh-android-shell
分享 · DeepSeek Harness Hub
Lian-peiji/dsh-android-shell

把自托管网页装进手机桌面,像原生应用一样全屏运行

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部