Wrap a desktop-layout web app (DSH = DeepSeek Harness Web GUI) into an Android APK for full-screen use on a phone.
This is not a generic browser shell. It is a native shell built for one specific problem: a desktop web app on a narrow mobile screen — which means controlling the layout width, escaping WebView's scaling traps, and handling edge-to-edge safe areas correctly.
No private domains, backend addresses, or API endpoints are included — you set the server address yourself, at build time or inside the app.
The DSH frontend ships a manifest.webmanifest (display: fullscreen), so in theory
"Add to Home Screen" in a browser would also go full screen. But that manifest only has
an SVG icon and no service worker, so Android may not treat it as an installable app.
A browser shell also brings an address bar, external-link hijacking, and cookie isolation.
So a native shell it is.
| Need | Implementation |
|---|---|
| Full screen, no browser UI | No ActionBar theme + WebView filling the window |
| Login/verification redirects work | All http/https navigation stays inside the WebView, with third-party cookies enabled |
| Verification persists | Cookies stored in the app's private directory, flushed on exit |
| File upload | onShowFileChooser + SAF |
| File download | DownloadManager, forwarding the WebView's cookies |
| Back button | Go back in the page first; double-tap to exit at the root |
| Change address | Tap the top-right corner three times to open settings (no UI footprint) |
| Stable scale | Scaling baseline is fixed to physical pixels, unaffected by the system's "Display size / Font size" |
| Bottom safe area | Measured system insets (systemBars / cutout / gesture area, per-edge max), no hardcoded height |
dsh-android/
├─ src/com/dshmobile/app/MainActivity.java The only Activity; all the logic
├─ res/ Icons, theme, strings, network security config
├─ AndroidManifest.xml
├─ assets/logo-512.png Icon source image
├─ tools/make-android-icons.ps1 Generates per-density mipmaps + adaptive icon foreground
├─ docs/webview-viewport-pitfalls.md Post-mortem: WebView scaling and the layout viewport
├─ release/ ★ Ready-to-install distribution files
│ ├─ DSH-mobile-1.5.2.apk Prebuilt APK
│ └─ dsh-release.keystore Public signing key (so upgrades install over the top)
├─ build-apk.ps1 Builds the APK (no Gradle)
├─ toolchain/ JDK + Android SDK (~450MB, safe to delete and re-download)
├─ build/ Intermediate artifacts
└─ out/DSH-mobile.apk Build output
Gradle + the Android Gradle Plugin pulls several hundred MB of extra dependencies and is picky about the JDK version. This project has exactly one Activity, so the SDK's own command-line tools are enough:
aapt2 compile -> compile resources
aapt2 link -> link resources + generate R.java
javac -> compile Java
d8 -> convert to classes.dex
zipalign -> align
apksigner -> sign
If you don't want to build it yourself, just download and install the APK in release/:
→ release/DSH-mobile-1.5.2.apk
Transfer it to your phone, tap to install, and allow "Install unknown apps" when prompted.
⚠️ The server address inside this APK is a placeholder (http://192.168.1.10:3080/), so it will show "Cannot connect" on first launch. Follow the steps below to set your own address.
You'll see a guide screen. There are two ways to change the address:
- In the app (recommended — no rebuild needed) Tap the top-right corner of the screen three times → open Settings → fill in "Server address" → tap "Save and reload". Settings also lets you adjust "Layout width" to change content size, and shows a read-only viewport diagnostic panel.
- Edit the source and rebuild
Change
DEFAULT_URLinsrc/com/dshmobile/app/MainActivity.java, or override it at build time with the-Urlparameter (see below).
It is deliberately committed to this repository, with the password dshmobile.
The reason: Android requires an update package to be signed with the same key as the
installed app, otherwise installing over the top fails and users must uninstall first.
Publishing the signing key lets you upgrade to future versions directly.
This key is public and is only meant for this project's sample APK. Do not use it to sign a real release (anyone could forge update packages with it). Generate your own keystore for a real release.
If you want to modify the code, you never need Gradle.
If you don't have a JDK and the Android SDK locally, install them first:
$root = $PWD
$tc = Join-Path $root "toolchain"
New-Item -ItemType Directory -Force -Path $tc | Out-Null
# 1) JDK 17
Invoke-WebRequest "https://api.adoptium.net/v3/binary/latest/17/ga/windows/x64/jdk/hotspot/normal/eclipse" -OutFile "$tc\jdk17.zip"
Expand-Archive "$tc\jdk17.zip" -DestinationPath $tc -Force
# 2) Android cmdline-tools
Invoke-WebRequest "https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip" -OutFile "$tc\cmdline-tools.zip"
$sdk = "$tc\android-sdk"
Expand-Archive "$tc\cmdline-tools.zip" -DestinationPath $sdk -Force
New-Item -ItemType Directory -Force -Path "$sdk\cmdline-tools\latest" | Out-Null
Get-ChildItem "$sdk\cmdline-tools" -Exclude latest | Move-Item -Destination "$sdk\cmdline-tools\latest"
# 3) SDK components
$env:JAVA_HOME = (Get-ChildItem $tc -Directory | Where-Object Name -like "jdk-17*").FullName
"y" * 200 -join [char]10 | Set-Content "$tc\yes.txt"
Get-Content "$tc\yes.txt" | & "$sdk\cmdline-tools\latest\bin\sdkmanager.bat" "--sdk_root=$sdk" --licenses
Get-Content "$tc\yes.txt" | & "$sdk\cmdline-tools\latest\bin\sdkmanager.bat" "--sdk_root=$sdk" "platforms;android-35" "build-tools;35.0.0" "platform-tools"Change DEFAULT_URL in src/com/dshmobile/app/MainActivity.java,
or override it at build time (this rewrites that constant in the source):
powershell -ExecutionPolicy Bypass -File .\build-apk.ps1 -Url "http://192.168.1.10:3080/"You can also change it inside the app at any time by tapping the top-right corner three times.
powershell -ExecutionPolicy Bypass -File .\build-apk.ps1Output: out\DSH-mobile.apk
If you deleted dsh-release.keystore, a new one is generated automatically on the first build.
Keep it — otherwise future upgrades require uninstalling the old version first.
Installing over the top requires the same key, which is why a copy lives in release/.
Option A: over USB
& "toolchain\android-sdk\platform-tools\adb.exe" install -r out\DSH-mobile.apkOption B: transfer out\DSH-mobile.apk to your phone (chat app / cloud drive / USB) and tap to install.
- Launches straight into a full-screen page
- If it redirects to a verification page, just verify normally — it then stays valid long-term
- Tap the top-right corner three times → Settings (address, layout width, device diagnostics)
- Back button: go back in the page first; double-tap to exit at the root
- Pinch to zoom to magnify a region yourself
Scale percentage = 100 × physical screen width / target layout width.
A desktop layout needs enough width so the sidebar doesn't squeeze out the main content, so the target layout width defaults to 1280 CSS px. Phone screens are usually only 1000–1400 physical pixels wide, so the ratio lands near 100% and the page renders almost 1:1.
"Layout width" in the settings panel is that target:
- Increase it (e.g. 1400–1600) = smaller content, more on screen, closer to a desktop view
- Decrease it (e.g. 900–1000) = larger content, easier to tap
- A horizontal scrollbar at the bottom = scaled too large; increase the target value
⚠️ This scale depends only on the physical resolution, not on the system's "Display size / Font size". Seedocs/webview-viewport-pitfalls.mdfor the reasoning and the traps we hit.
| Symptom | Cause / fix |
|---|---|
| Stuck on the verification page | The verifier may detect WebView. Try setting s.setUserAgentString(<desktop Chrome UA>) in configureWebView() |
| Blank screen | Debug remotely via chrome://inspect; or try a LAN IP address |
| Icon is a small image on white | The adaptive icon background is transparent, and some launchers add their own. Change res/values/colors.xml for a solid background |
| Install-over fails | The signing key changed. Uninstall the old version, or always use the same keystore |
| Broken layout, tiny content | See the troubleshooting below |
Open the settings panel and look at "Viewport diagnostic":
| Observation | Meaning |
|---|---|
innerWidth ≈ target layout width |
Normal; the scale landed correctly |
innerWidth is several times the target |
Layout viewport out of control — see the post-mortem in docs |
| A horizontal scrollbar exists | Scaled too large; increase "Layout width" |
- All data lives in the WebView's cookies / localStorage, in the app's private directory, and is never uploaded elsewhere
- The app requests only
INTERNET,ACCESS_NETWORK_STATE, and storage permission on API 28 and below (for downloads) - Cleartext HTTP is enabled so you can test against a LAN address; use HTTPS in production
MIT
Author Jason · Blog blog.20240606.xyz · GitHub 666su
🌐 Language / 语言 → [ English ↑ ] [ 中文 ]
把一个桌面版式的 Web 应用(DSH = DeepSeek Harness Web GUI)封装成 Android APK, 在手机上全屏使用。
这不是一个通用的浏览器壳,而是针对一类具体问题做的原生外壳:桌面 Web 应用 + 移动端窄屏, 需要控制版式宽度、绕开 WebView 的缩放陷阱、正确处理 edge-to-edge 安全区。
项目里不含任何私有域名、后台地址或 API 端点——服务地址由你自己在构建时或应用内设置。
DSH 前端自带 manifest.webmanifest(display: fullscreen),理论上浏览器"添加到主屏幕"
也能全屏。但那份清单只有 SVG 图标、没有 service worker,安卓上不一定被当作可安装应用,
而且浏览器壳会带来地址栏、跳转外链、cookie 隔离等问题。所以做了原生外壳。
| 需求 | 实现 |
|---|---|
| 全屏无浏览器界面 | 无 ActionBar 主题 + WebView 铺满 |
| 登录/验证跳转能走通 | 所有 http/https 跳转留在 WebView 内,开启第三方 cookie |
| 验证一次长期有效 | cookie 持久化在应用私有目录,退出时主动 flush |
| 文件上传 | onShowFileChooser + SAF |
| 文件下载 | DownloadManager,并带上 WebView 的 cookie |
| 返回键 | 先网页后退,到底后双击退出 |
| 改地址 | 连点右上角三次 打开设置(无界面占用) |
| 尺度稳定 | 缩放基准固定为物理像素,不随系统「显示大小 / 字体大小」漂移 |
| 底部安全区 | 按系统实测 insets(systemBars / 刘海 / 手势区 per-edge 取最大),不写死高度 |
dsh-android/
├─ src/com/dshmobile/app/MainActivity.java 唯一的 Activity,全部逻辑
├─ res/ 图标、主题、字符串、网络安全配置
├─ AndroidManifest.xml
├─ assets/logo-512.png 图标源图
├─ tools/make-android-icons.ps1 生成各密度 mipmap + 自适应图标前景
├─ docs/webview-viewport-pitfalls.md 踩坑记录:WebView 缩放与布局视口
├─ release/ ★ 可直接下载安装的分发件
│ ├─ DSH-mobile-1.5.2.apk 预构建 APK
│ └─ dsh-release.keystore 公开签名密钥(保证能覆盖升级)
├─ build-apk.ps1 构建 APK(不用 Gradle)
├─ toolchain/ JDK + Android SDK(约 450MB,可删可重下)
├─ build/ 中间产物
└─ out/DSH-mobile.apk 产物
Gradle + Android Gradle Plugin 需要额外下载几百 MB 依赖,而且对 JDK 版本挑剔。 这个工程只有一个 Activity,直接用 SDK 自带的命令行工具就够了:
aapt2 compile -> 编译资源
aapt2 link -> 链接资源 + 生成 R.java
javac -> 编译 Java
d8 -> 转成 classes.dex
zipalign -> 对齐
apksigner -> 签名
不想自己构建的话,直接下载 release/ 里的 APK 安装:
→ release/DSH-mobile-1.5.2.apk
传到手机点击安装,按提示允许「安装未知来源应用」。
⚠️ 这个 APK 里的服务地址是示例占位符(http://192.168.1.10:3080/), 所以打开会看到「无法连接」。按下面提示改成你自己的地址即可。
打开 APK 后会看到引导页,两种改法:
- 应用内改(推荐,不用重新构建) 连点屏幕右上角三次 → 打开设置 → 填「服务器地址」→ 点「保存并重新加载」。 在设置里还能调「版式宽度」改变内容大小,并查看只读的视口诊断。
- 改源码重新构建
改
src/com/dshmobile/app/MainActivity.java里的DEFAULT_URL, 或者构建时用-Url参数覆盖(见下)。
它故意放在仓库里,口令是 dshmobile。原因:Android 要求升级包与已安装应用
签名一致,否则覆盖安装会失败、必须先卸载。把签名密钥一起发布,你才能直接升级后续版本。
这个密钥是公开的,仅用于本项目的示例 APK。不要用它签名正式发布的应用 (任何人都能用它伪造你的更新包)。自己正式发布请生成自己的 keystore。
如果你要改代码,可以完全不用 Gradle。
本机没有 JDK 和 Android SDK 时需要先装:
$root = $PWD
$tc = Join-Path $root "toolchain"
New-Item -ItemType Directory -Force -Path $tc | Out-Null
# 1) JDK 17
Invoke-WebRequest "https://api.adoptium.net/v3/binary/latest/17/ga/windows/x64/jdk/hotspot/normal/eclipse" -OutFile "$tc\jdk17.zip"
Expand-Archive "$tc\jdk17.zip" -DestinationPath $tc -Force
# 2) Android cmdline-tools
Invoke-WebRequest "https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip" -OutFile "$tc\cmdline-tools.zip"
$sdk = "$tc\android-sdk"
Expand-Archive "$tc\cmdline-tools.zip" -DestinationPath $sdk -Force
New-Item -ItemType Directory -Force -Path "$sdk\cmdline-tools\latest" | Out-Null
Get-ChildItem "$sdk\cmdline-tools" -Exclude latest | Move-Item -Destination "$sdk\cmdline-tools\latest"
# 3) SDK 组件
$env:JAVA_HOME = (Get-ChildItem $tc -Directory | Where-Object Name -like "jdk-17*").FullName
"y" * 200 -join [char]10 | Set-Content "$tc\yes.txt"
Get-Content "$tc\yes.txt" | & "$sdk\cmdline-tools\latest\bin\sdkmanager.bat" "--sdk_root=$sdk" --licenses
Get-Content "$tc\yes.txt" | & "$sdk\cmdline-tools\latest\bin\sdkmanager.bat" "--sdk_root=$sdk" "platforms;android-35" "build-tools;35.0.0" "platform-tools"改 src/com/dshmobile/app/MainActivity.java 里的 DEFAULT_URL,
或者在构建时覆盖(会直接改写源码里的该常量):
powershell -ExecutionPolicy Bypass -File .\build-apk.ps1 -Url "http://192.168.1.10:3080/"装好后也可以随时连点右上角三次在应用内改。
powershell -ExecutionPolicy Bypass -File .\build-apk.ps1产物:out\DSH-mobile.apk
如果你删掉了 dsh-release.keystore,首次构建会自动生成一个新的。
生成后要留着,否则以后升级必须先卸载旧版。想覆盖安装就必须用同一个密钥——
这也是 release/ 里放了一份的原因。
方式一:USB 连接后
& "toolchain\android-sdk\platform-tools\adb.exe" install -r out\DSH-mobile.apk方式二:把 out\DSH-mobile.apk 传到手机(微信/网盘/USB),点击安装。
- 打开即全屏加载
- 若跳转到验证页,正常验证即可,之后长期免验证
- 连点右上角三次 → 设置(改地址、改版式宽度、看实机诊断)
- 返回键:先网页后退,到底后双击退出
- 双指缩放:可自己放大局部
缩放百分比 = 100 × 物理屏宽 / 目标版式宽。
桌面版式需要足够宽度,侧边栏才不会挤掉正文,所以目标版式宽默认 1280 CSS px。 手机屏宽通常只有 1000~1400 物理像素,因此这个比值接近 100%,页面几乎 1:1 显示。
设置面板里的「版式宽度」就是这个目标值:
- 调大(如 1400~1600)= 内容变小,一屏看更多,更接近桌面视野
- 调小(如 900~1000)= 内容变大,更好点
- 底部出现横向滚动条 = 缩放过大,把目标值调大即可
⚠️ 这个尺度只依赖物理分辨率,与系统「显示大小 / 字体大小」无关。 原因和踩过的坑见docs/webview-viewport-pitfalls.md。
| 现象 | 原因 / 处理 |
|---|---|
| 一直卡在验证页 | 验证方可能识别 WebView。可尝试在 configureWebView() 里设置 s.setUserAgentString(<桌面 Chrome UA>) |
| 白屏 | 用 chrome://inspect 远程调试;或把地址改成局域网 IP 试 |
| 图标是白底小图 | 自适应图标背景是透明的,部分启动器会自己加底色。需要实色底可改 res/values/colors.xml |
| 覆盖安装失败 | 换了签名密钥。必须先卸载旧版,或始终用同一个 keystore |
| 版式错乱、内容极小 | 见下面的排查方法 |
打开设置面板看「视口诊断」:
| 观察 | 含义 |
|---|---|
innerWidth ≈ 目标版式宽度 |
正常,缩放落点正确 |
innerWidth 是目标值的数倍 |
布局视口失控,见 docs 里的踩坑记录 |
| 有横向滚动条 | 缩放过大,调大「版式宽度」 |
- 所有数据都在 WebView 的 cookie / localStorage 里,存于应用私有目录,不额外上传
- 应用只申请
INTERNET、ACCESS_NETWORK_STATE,以及 API 28 以下的存储权限(用于下载) - 开启明文 HTTP 是为了能改用局域网地址自测,生产环境建议用 HTTPS
MIT
作者 Jason · 博客 blog.20240606.xyz · GitHub 666su