Repository navigation
Releases: chjy090/DSH-Mobile
Release list
DSH Mobile v0.4.5
DSH Mobile v0.4.5 发布说明
发布对象:Android APK(DeepSeek Harness
dshWeb GUI 的远程 WebView 客户端)
源码仓库:DSH-Mobile
源码版本:0.4.5/ versionCode12
发布包准备日期:2026-10-27状态说明:按本轮任务约束,源码改动尚未
git commit/git tag,也未创建 GitHub Release;
本文件与 APK 为待发布产物,需由维护者复核后提交/发布。
1. 版本与校验
| 项目 | 内容 |
|---|---|
| 版本名 / versionCode | 0.4.5 / 12 |
| applicationId | ai.deepseek.dsh.mobile |
| 最低系统 | Android 8.0(API 26);targetSdk 35 |
| 发布包 | DSH-Mobile-v0.4.5-release.apk |
| 发布包大小 | 25,541,227 字节(24.36 MiB / 25.54 MB) |
| 发布包 SHA-256 | 3eb4394d9ceb50c7d8ec3e8cc4e0a6a88e8428001d3379e8e825100b52cbb4e3 |
| 签名证书 | CN=DSH Mobile, OU=Personal, O=Personal, L=NA, ST=NA, C=CN;证书 SHA-256 45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8(与 v0.4.0–v0.4.4 相同,可覆盖升级) |
| Debug 包 | 本轮未构建、未随发布包分发(仅提供 release) |
| 构建命令 | ./gradlew assembleRelease lintRelease --offline |
| Lint | lintRelease:0 errors, 16 warnings;报告中的 warning 路径均不涉及本轮新增/修改的 DownloadHistory.kt、MainActivity.kt 下载历史逻辑与 file_paths.xml(见 §4) |
校验方法(在本目录执行):
sha256sum -c SHA256SUMS-v0.4.5.txt2. v0.4.5 变更内容
2.1 App 内「最近下载」(核心兜底)
用户实测反馈:v0.4.4 下载文件名、重名、打开入口均正常,但 Realme 文件管理器的「最近」
仍不显示下载的文档(「下载」分类可见)。这类 OEM「最近」通常只聚合媒体库条目或系统下载记录,
应用无法强制把任意文档塞进厂商「最近」。v0.4.5 因此增加了不依赖 ROM 规则的 App 内历史:
- 数据模型:
DownloadHistory.Entry,字段为
displayName / contentUri(String?) / legacyPath(String?) / mime / size / savedAt。- Android 10+ 保存 MediaStore 实际集合返回的 content Uri;
- Android 8/9 保存公共
Download/下文件的绝对路径; - 不记录 URL、token、cookie 等任何网络凭据。
- 存储:独立 SharedPreferences(
dsh_download_history)+ JSON,最多 50 条,最新在前;
写入/读取/解析失败均静默降级(跳过写入或返回空列表),不影响下载本身。 - 写入时机:
Downloader成功回调携带最终文件名、content Uri/绝对路径、MIME 与字节数;
WebHost在onDone先写历史,再复用原有完成提示/「打开」逻辑。 - 菜单入口:应用菜单新增「最近下载」,认证网关模式与直连模式都显示;
位置在「刷新」之后、模式相关菜单项之前。 - 列表与打开:每项显示「文件名 + 保存时间(yyyy-MM-dd HH:mm)」;点击用
ACTION_VIEW+FLAG_GRANT_READ_URI_PERMISSION打开。打开前先验证 Uri 可读:- Android 10+ 直接使用 MediaStore content Uri;
- Android 8/9 使用新增的
FileProvider(authority${applicationId}.fileprovider)
把Download/绝对路径转成临时可读 Uri。
- 失败提示:文件被移动/删除或系统没有可处理应用时,Toast
「无法打开「文件名」:文件可能已被移动或删除,或没有可处理的应用。」,不崩溃。 - 清除记录:对话框底部「清除记录」→ 二次确认;只清 App 内历史,不删除任何文件。
- 重启保留:历史在 App 进程重启后仍存在;
清除数据/卸载会清空。
2.2 媒体类型分流(Android 10+,提升「最近」可见性)
Downloader 在 MIME 确定后选择 MediaStore 集合与相对目录:
| MIME | MediaStore 集合 | RELATIVE_PATH |
|---|---|---|
image/* |
MediaStore.Images.Media.EXTERNAL_CONTENT_URI |
Pictures/ |
video/* |
MediaStore.Video.Media.EXTERNAL_CONTENT_URI |
Movies/ |
audio/* |
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI |
Music/ |
| 其他(文档/归档/apk/未知) | MediaStore.Downloads.EXTERNAL_CONTENT_URI |
Download/ |
复用与适配:
- 文件名解析、清洗、扩展名补全、MIME 推断与文件头嗅探逻辑不变;
- 重名查重改为目标集合:Android 10+ 查询本次 MIME 对应的集合与
RELATIVE_PATH+
DISPLAY_NAME,不再只看Downloads/Download/;同名递增仍为(1)、(2); DATE_ADDED/DATE_MODIFIED(秒)/SIZE/IS_PENDING写入逻辑复用;notifyChange同时通知单条记录与实际写入的集合(Images/Video/Audio/Downloads);onDone返回的 content Uri 是 MediaStore insert 返回的实际集合条目 Uri,
供历史记录与打开入口使用;- 完成提示按实际目录区分:文档仍是「已保存到系统下载目录:…」,媒体文件改为
「已保存到系统媒体库(Pictures/Movies/Music):…」,避免把Pictures/说成下载目录; - Android 8/9 不变:仍统一写
Download/(不强制分流),完成后调用
MediaScannerConnection.scanFile补索引,保证旧系统行为稳定。
2.3 额外索引尝试(可选、低成本)
Android 10+ 在 IS_PENDING 解除后,尝试通过 MediaStore.MediaColumns.DATA(已 deprecated)
查询文件绝对路径;只有查到以 / 开头的非空路径时才调用一次
MediaScannerConnection.scanFile。查不到、抛异常或无回调只写 DSHDownloader 日志:
supplemental media scan skipped: no absolute pathsupplemental media scan returned no urisupplemental media scan failed
不影响下载结果,也不阻塞完成提示(MediaStore 记录 + notifyChange 仍是主路径)。
2.4 FileProvider 决策
工程此前没有配置 FileProvider。本轮选择新增(而不是“API≤28 只显示路径 Toast”),原因:
- Android 8/9 若直接把历史里的
file://路径交给ACTION_VIEW,会触发
FileUriExposedException;仅显示路径会让「最近下载」在旧系统上失去打开能力; - 工程已依赖
androidx.core,androidx.core.content.FileProvider无需新增第三方依赖; - 暴露面可收紧到最小:
res/xml/file_paths.xml只声明
<external-path name="downloads" path="Download/" />,authority 为
${applicationId}.fileprovider,exported=false,仅通过FLAG_GRANT_READ_URI_PERMISSION
临时授权; - Android 10+ 仍优先使用 MediaStore content Uri,不经过 FileProvider。
2.5 关于系统「最近」的限制(发布说明明确)
- 部分 OEM(含 Realme/OPPO 等)的「文件管理 → 最近」只聚合媒体库条目或系统下载记录;
普通 PDF/ZIP/APK 等文档即使已正确写入MediaStore.Downloads,也可能不显示。
这是厂商文件管理器策略,App 无法强制注入。 - v0.4.5 的应对:
- App 内「最近下载」作为可靠兜底;
- Android 10+ 图片/视频/音频分流到媒体库(
Pictures/、Movies/、Music/),
系统图库/相册「最近」通常可见; - Q+ 收尾尝试额外媒体扫描;
- 文档仍在
Download/,可在「文件 → 下载」分类查看。
- 因此「系统最近看不到文档」不等于下载失败;请用 App 内「最近下载」或「文件 → 下载」。
2.6 其他
- 版本号升至
0.4.5(versionCode12);AndroidManifest.xml顶部说明、README、
docs/installation.md与发布元数据同步。 - 新增字符串:
menu_download_history、download_history_item、download_history_empty、
download_history_clear、download_history_clear_title、download_history_clear_message、
download_history_cleared、download_history_open_failed、download_saved_media;
下载打开失败的既有文案改为“文件已保存在系统相册或下载目录”,以适配媒体分流;无删除既有文案。 - 未新增权限、未新增第三方依赖;TLS fail-open、Cookie 注入、Android 8/9
WRITE_EXTERNAL_STORAGE申请、扫码/相册配对、静默登录、返回手势菜单均未改动。
3. 兼容性与行为矩阵
| 场景 | 行为 |
|---|---|
| v0.4.4 覆盖升级 | 保留服务器地址、pin、deviceToken、WebView cookie、配对 profile;无需重新配对 |
| Android 10+ 图片/视频/音频 | 分别落 Pictures/、Movies/、Music/,写入对应媒体集合;图库/相册「最近」通常可见 |
| Android 10+ 文档/归档/apk/未知 | 仍在 Download/,可在「文件 → 下载」查看 |
| Android 10+ 打开历史文件 | MediaStore content Uri + ACTION_VIEW + 临时读权限 |
| Android 8/9 下载 | 仍需 WRITE_EXTERNAL_STORAGE;统一写 Download/,完成后媒体扫描 |
| Android 8/9 打开历史文件 | FileProvider 临时 content Uri(仅 Download/);文件被移动/删除时 Toast 说明 |
| 无处理应用 | Toast「无法打开…」,不崩溃 |
| 历史条数 | 最多 50 条,最新在前;App 重启后保留,清除记录只清历史不删文件 |
| 媒体文件重名 | 在对应集合/目录内递增 (1)、(2),不覆盖 |
| 服务端 MIME 错误 | 按服务端/嗅探到的 MIME 分流;无法识别时按文档处理,记录 Content-Type 便于排查 |
4. 构建与验证结果
- 构建环境:JDK 21(Temurin)、Gradle Wrapper 8.10.2、Android SDK Platform 35;
source toolchain/env.sh后执行./gradlew assembleRelease lintRelease --offline。 - 结果:
BUILD SUCCESSFUL;assembleRelease产出已签名 APK。 lintRelease:0 errors, 16 warnings。warning 类型为既有
RequiresFeature、UnusedAttribute、LockedOrientationActivity、DiscouragedApi、
SetJavaScriptEnabled、WebViewClientOnReceivedSslError、TrustAllX509TrustManager、
CustomX509TrustManager、InsecureBaseConfiguration、DataExtractionRules、
MonochromeLauncherIcon、ObsoleteSdkInt、UnusedResources等;无新增文件相关 warning。- APK 元数据(
aapt2 dump badging):package: name='ai.deepseek.dsh.mobile' versionCode='12' versionName='0.4.5'minSdkVersion:'26',targetSdkVersion:'35'native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'- Provider:
androidx.core.content.FileProvider,authority
ai.deepseek.dsh.mobile.fileprovider,exported=false,grantUriPermissions=true
- 签名(
apksigner verify --print-certs):签名有效,证书 SHA-256
45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8。 - 真机:本轮构建机
adb devices无设备连接,未执行安装/交互测试;
Android 10+ 媒体分流、Realme「最近」、Android 8/9 FileProvider 打开均未在真机验证,
待按mobile-apk/docs/manual-validation.md§14 清单执行。
5. 未验证项与风险
- 真机全链路未验证:无设备可用;MediaStore 各集合在具体 OEM 上的插入/索引行为
(尤其 Realme/OPPO)需按 §14 实测。 - 媒体分流对「最近」的改善不确定:媒体库路径通常提升图库可见性,但厂商「文件 → 最近」
是否展示文档由 ROM 决定,App 不保证;App 内「最近下载」是确定可用的兜底。 - Q+
DATA查询:MediaStore.MediaColumns.DATA已 deprecated,部分 ROM 返回空或抛异常;
代码已捕获并跳过,只影响“额外扫描”这一步,不影响下载/索引主路径。 - FileProvider 仅
Download/:Android 8/9 的历史文件必须仍在公共Download/;
若用户移动/改名,打开会提示失败(符合预期)。Android 10+ 媒体文件使用 MediaStore Uri,
不受此路径限制。 - 历史只记录 v0.4.5 之后的下载:升级前已下载的文件不会出现在「最近下载」列表中,
需要时重新下载或到系统目录查看。 - 50 条上限:仅保留最近 50 条,更早记录会被淘汰;不提供导出/搜索。
- 未提交/未发布:按任务约束未执行
git commit/git tag/GitHub Release;
版本说明、APK 与校验文件是待发布产物,提交与发布前请复核。 - 依赖缓存:构建使用
--offline(本机 Gradle 缓存完整);干净环境首次构建需联网下载依赖。
6. 产物清单
| 文件 | 说明 |
|---|---|
DSH-Mobile-v0.4.5-release.apk |
已签名 release APK(根 deliverables/ 与 deliverables/release/ 各一份) |
release-notes-v0.4.5.md |
本说明(根 deliverables/ 与 release/ 各一份) |
SHA256SUMS-v0.4.5.txt |
APK SHA-256 与版本元数据(根 deliverables/ 与 release/ 各一份,均可就地 sha256sum -c) |
deliverables/index.html 已更新为 v0.4.5(保留 v0.4.0–v0.4.4 历史下载链接),
deliverables/SHA256SUMS.txt 已追加 v0.4.5 校验行。
DSH Mobile v0.4.4
DSH Mobile v0.4.4 发布说明
发布对象:Android APK(DeepSeek Harness
dshWeb GUI 的远程 WebView 客户端)
源码仓库:DSH-Mobile
源码版本:0.4.4/ versionCode11(源码已提交并打 tagv0.4.4)
发布包准备日期:2026-10-27
1. 版本与校验
| 项目 | 内容 |
|---|---|
| 版本名 / versionCode | 0.4.4 / 11 |
| applicationId | ai.deepseek.dsh.mobile |
| 最低系统 | Android 8.0(API 26);targetSdk 35 |
| 发布包 | DSH-Mobile-v0.4.4-release.apk |
| 发布包大小 | 25,520,407 字节(24.34 MiB / 25.52 MB) |
| 发布包 SHA-256 | 860f90638d1d8e66094452c703eb4d74b17f28c38e1bc9c123a6ab7a383a8126 |
| 签名证书 | CN=DSH Mobile, OU=Personal, O=Personal, L=NA, ST=NA, C=CN;证书 SHA-256 45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8(与 v0.4.0–v0.4.3 相同,可覆盖升级) |
| Debug 包 | 本轮未构建、未随发布包分发(发布仅提供 release) |
校验方法(在本目录执行):
sha256sum -c SHA256SUMS-v0.4.4.txt2. v0.4.4 变更内容
2.1 下载文件保留原文件名(核心)
- 文件名解析优先级(
Downloader.kt):Content-Disposition的filename*=UTF-8''...(RFC 5987,percent-decode,支持 charset 声明);Content-Disposition的filename="..."(percent-decode、引号内;、反斜杠转义、常见乱码修复);- URL 最后一段(去掉 query/fragment 后按 UTF-8 percent-decode);
- 兜底
dsh-download(能推断 MIME 扩展名时补上,如dsh-download.pdf)。
- 中文与空格原样保留:
说明.pdf保存为说明.pdf;不再追加-yyyyMMdd-HHmmss时间戳,
旧的时间戳函数与相关常量已删除。 - 清洗规则:去掉路径分隔符(
/、\)与: * ? " < > |、控制字符、首尾空格与尾部点;
防路径穿越(取最后一段)、拒绝空名/./..;以.开头的名字加_前缀避免变成隐藏文件;
文件名过长(UTF-8 > 180 字节)时截断基础名并保留扩展名。 - 原名没有扩展名时按已推断 MIME 用
MimeTypeMap.getExtensionFromMimeType补(含常见类型兜底表)。 - 重名规则:
说明.pdf→说明 (1).pdf→说明 (2).pdf…(按最后一个点拆分,扩展名保持):- Android 10+:插入前查询
MediaStore.Downloads(RELATIVE_PATH = Download/+DISPLAY_NAME);
RELATIVE_PATH不带斜杠的旧数据也兼容查询; - Android 8/9:目标
File已存在则按同规则递增; - 同进程并发下载用
synchronized+ 占位集合串行分配,避免两个线程算中同一个名字。
- Android 10+:插入前查询
onDone回传最终保存名:Android 10+ 以 MediaStore 实际DISPLAY_NAME为准(系统并发改名也能对上),
提示与磁盘/索引一致。
2.2 MIME 推断更准确
- 顺序:响应
Content-Type→ 下载监听给的 type → 扩展名映射 → 文件头嗅探 →application/octet-stream;
前两步中application/octet-stream视为“未知”,继续往下推断。 - 文件头嗅探只读前 512 字节(不缓冲整个文件,读到的字节随后原样写入目标文件):
PDF(%PDF-)、ZIP 系(PK\x03\x04,含 docx/xlsx/pptx/apk)、PNG、JPEG、GIF、WebP、MP4、
MP3(ID3/帧同步)、可严格解码的 UTF-8 文本。 - 仍无法判断时允许
application/octet-stream;文件名扩展名优先保留。
2.3 MediaStore / 索引可见性(Android 10+)
目标是让下载完成后在系统「文件 → 最近 / 下载」中可见:
insertPendingDownload补写DATE_ADDED、DATE_MODIFIED、SIZE=0,保留IS_PENDING=1、
RELATIVE_PATH=Download/;若个别 OEM 拒绝这些列,自动退回最小字段重试,保证下载仍可进行。- 时间单位说明:MediaStore 的
DATE_ADDED/DATE_MODIFIED官方定义是秒(epoch seconds),
不是毫秒;写毫秒会把时间显示成数万年后的错误值,因此这里按秒写入(currentTimeMillis()/1000)。 finalizePendingDownload:IS_PENDING=0、DATE_MODIFIED=now、实际SIZE(按已写入字节数),
仅在DATE_ADDED缺失时补写;个别 OEM 拒绝整组更新时自动退回“只解除 pending”,保证文件可见。- 完成后同时
notifyChange(具体 uri)与notifyChange(MediaStore.Downloads.EXTERNAL_CONTENT_URI)。 - API 29+ 不再重复调用
MediaScannerConnection.scanFile:scoped storage 下公共路径扫描既不可靠也冗余,
MediaStore 记录 + notifyChange 才是「文件」应用的数据源。 - API 26–28 落盘后仍调用
MediaScannerConnection.scanFile,并在回调里确认是否拿到 Uri;
失败只记日志,不影响下载结果(文件已在Download/)。
2.4 下载完成提示与「打开」入口(可选实现)
- 文案保持不变:
已保存到系统下载目录:<最终文件名>。 - Android 10+ 且 Activity 在前台时,用轻量
AlertDialog展示同一文案并带「打开」/「完成」:- 「打开」使用
ACTION_VIEW+ MediaStore content Uri +FLAG_GRANT_READ_URI_PERMISSION,
不申请额外权限; - 没有可处理的应用/打开失败时,仅 Toast「未找到可打开该文件的应用;文件已保存在系统下载目录:<文件名>」。
- 「打开」使用
- 后台、弹窗失败或 API ≤28(无 content Uri)时退回原 Toast 行为,不崩溃。
2.5 其他
- 版本号升至
0.4.4(versionCode11);AndroidManifest.xml顶部说明与「关于」版本兜底字符串同步更新。 - 新增字符串:
download_open、download_done、download_open_failed;未删除既有下载文案。 - 未新增权限、未新增第三方依赖;未改变 TLS fail-open、Cookie 注入、下载权限申请逻辑
(API ≤28 仍需要WRITE_EXTERNAL_STORAGE,Android 10+ 仍不需要存储权限)。
3. 兼容性说明
| 场景 | 行为 |
|---|---|
| v0.4.3 覆盖升级 | 保留服务器地址、pin、deviceToken、WebView cookie、配对 profile;无需重新配对 |
| Android 10+ 下载 | 文件保存到公共 Download/,保留原名;重名自动 (1)、(2) |
| Android 8/9 下载 | 仍需 WRITE_EXTERNAL_STORAGE;直接写公共 Download/,File.exists 查重,完成后媒体扫描 |
| 中文/空格文件名 | 原样保留;URL/Content-Disposition 中的 UTF-8 percent 编码会正确解码 |
| 无扩展名链接 | 按 Content-Type/MIME/文件头推断扩展名(如 .pdf) |
| 服务端未给 Content-Disposition | 用 URL 最后一段;仍失败则 dsh-download[.ext] |
| blob:/data: 链接 | 与旧版一致,提示「该链接暂不支持下载」 |
| 认证网关 / 证书 pin / 静默登录 / 前台续期 | 与 v0.4.3 一致,未改动 |
| 直连模式隐藏/解锁、相册二维码 | 与 v0.4.3 一致,未改动 |
4. 实现说明与取舍
- 为什么不再用
URLUtil.guessFileName:它无法保证 RFC 5987 的filename*优先级,也容易被
application/octet-stream带偏;本版改为显式解析,并对老服务器常见的 Latin-1/CP1252 乱码做
可逆修复(如说明.pdf→说明.pdf,正常中文/带重音的西文名不受影响)。 - 为什么先读 512 字节:MediaStore 的
MIME_TYPE在 insert 时就要确定;先读文件头再 insert 才能
让索引里的类型正确,同时避免把整个文件缓存在内存。已读字节写回目标文件,不会丢内容。 - 时间字段单位:见 §2.3;这是对“毫秒”需求的一处有意修正,避免 1970/数万年错误时间。
- 索引可见性边界:不同厂商文件管理器「最近」的判定不同(有的读 MediaStore
DATE_MODIFIED,
有的读DATE_ADDED,有的读文件系统 mtime)。本版三处都尽量补齐;仍无法保证所有 ROM 的
「最近」列表一定展示,需真机验证。
5. 验证记录(构建机,2026-10-27)
| 项目 | 结果 |
|---|---|
source toolchain/env.sh && ./gradlew assembleRelease |
BUILD SUCCESSFUL |
./gradlew lintRelease |
BUILD SUCCESSFUL;0 error / 16 warning(与 v0.4.3 相同集合,均为既有 TLS/WebView/Manifest 告警) |
| APK badging | versionCode='11' versionName='0.4.4',minSdk 26 / targetSdk 35 |
| 签名 | apksigner verify --print-certs 通过,证书 SHA-256 与 v0.4.3 一致(可覆盖升级) |
| zipalign | zipalign -c -v 4 通过 |
| 文件名/MIME 离线用例 | 通过(RFC 5987、filename= percent、CP1252 乱码修复、URL 解码、路径穿越/隐藏文件/非法字符、扩展名拆分、超长截断、PDF/ZIP/PNG/JPEG/GIF/WebP/MP4/MP3/文本嗅探等 30+ 项,JVM 纯函数测试,未在真机跑) |
| 真机安装/下载验证 | 未执行:adb devices 无设备(RMX5200 未连接);未启动模拟器 |
真机验证清单见仓库
mobile-apk/docs/manual-validation.md§13(v0.4.4 中文名 PDF / 普通文件 /
重名(1)/ 文件管理器「下载/最近」位置 / 打开入口 / API 26–28 回归),需在真机上逐项勾选后再对外发布。
6. 交付物
| 文件 | 说明 |
|---|---|
deliverables/DSH-Mobile-v0.4.4-release.apk |
发布 APK |
deliverables/release/DSH-Mobile-v0.4.4-release.apk |
发布 APK(release 目录副本) |
deliverables/release/SHA256SUMS-v0.4.4.txt |
本版本校验文件(sha256sum -c 通过) |
deliverables/release/release-notes-v0.4.4.md |
本文件 |
deliverables/SHA256SUMS.txt |
全量校验清单(已追加 v0.4.4 行) |
deliverables/index.html |
下载页(当前版本 v0.4.4,保留旧版本链接) |
7. 未验证项与风险
- 真机未验证(无 adb 设备):MediaStore 索引、「最近」可见性、重名递增、打开 Intent、
Android 8/9 媒体扫描均需真机确认。 - 部分 ROM 的「文件 → 最近」可能只展示最近修改的多媒体类型;下载类文件是否进入「最近」由厂商
文件管理器决定,本版只能保证 MediaStore 记录与时间/大小字段正确。 - Android 8/9 若用户拒绝存储权限,仍按旧逻辑下载失败并提示,不会写入半成品。
- 极少数 OEM 若拒绝
SIZE/DATE_*更新,会退回“只解除 pending”,文件可见但大小/时间可能不准。 - 从并发外部应用抢注同名文件时,最终以 MediaStore 自动改名为准,提示名与实际名仍一致。
- 离线文件名用例只覆盖纯解析函数,未覆盖 MediaStore/文件系统交互。
DSH Mobile v0.4.3
DSH Mobile v0.4.3 发布说明
发布对象:Android APK(DeepSeek Harness
dshWeb GUI 的远程 WebView 客户端)
源码仓库:DSH-Mobile
源码版本:0.4.3/ versionCode10(源码已提交并打 tagv0.4.3)
发布包准备日期:2026-10-27
1. 版本与校验
| 项目 | 内容 |
|---|---|
| 版本名 / versionCode | 0.4.3 / 10 |
| applicationId | ai.deepseek.dsh.mobile |
| 最低系统 | Android 8.0(API 26);targetSdk 35 |
| 发布包 | DSH-Mobile-v0.4.3-release.apk |
| 发布包大小 | 25,519,147 字节(24.34 MiB / 25.52 MB) |
| 发布包 SHA-256 | 35cfdfbae6c97b1bbb1821f36b55b459246c784fffafa23da84153091ddf0304 |
| 签名证书 | CN=DSH Mobile, OU=Personal, O=Personal, L=NA, ST=NA, C=CN;证书 SHA-256 45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8(与 v0.4.0/v0.4.1/v0.4.2 相同,可覆盖升级) |
| Debug 包 | 本轮未构建、未随发布包分发(发布仅提供 release) |
校验方法(在本目录执行):
sha256sum -c SHA256SUMS-v0.4.3.txt2. v0.4.3 变更内容
2.1 配对页新增「从相册选择二维码」
- 配对页在「扫码配对」下方新增独立按钮「从相册选择二维码」;点击后打开系统照片选择器,
选择相册中已有的配对二维码截图/照片即可完成识别,不需要相机、不需要存储权限。 - 选择器使用
ActivityResultContracts.PickVisualMedia(androidx.activity:activity-ktx:1.9.3,
未升级依赖):Android 13+ 走系统照片选择器;低版本/无照片选择器时由 androidx 自动回退为
ACTION_OPEN_DOCUMENT+image类型,同样不需要存储权限。 - 解码复用现有 bundled ML Kit
barcode-scanning(Barcode.FORMAT_QR_CODE,不依赖 Google Play 服务),
未新增任何依赖或权限:- 读图在后台单线程(
dsh-gallery-qr)完成,不阻塞主线程; - 先
inJustDecodeBounds计算inSampleSize,把最长边压到 3072 px 以内再解码,避免 48MP 原图
OOM;按 EXIF 方向转正后交给 ML Kit; - 任何解码/识别异常只提示失败,不崩溃;20 秒兜底超时解除「进行中」状态,可再次选图;
- Activity 销毁、超时或被新选图替代时,通过代际 token 丢弃迟到回调,不再弹 Toast/对话框。
- 读图在后台单线程(
- 严格校验不放宽:图片来自任意相册,但识别出的原始文本仍必须通过
QrPairPayload.parse()
(仅接受https://…/__auth/pair#v=1&t=…&fp=…),与扫码完全同一条链路
→ 「确认配对」→POST /__auth/pair/redeem兑换;多码时取第一个可解析的。 - 失败提示(新增文案):「未能从图片中识别配对二维码,请使用原图截图(勿用转发压缩图)重试」;
用户取消选图时静默返回配对页。
2.2 直连模式默认隐藏(「关于」连点版本号 7 次解锁)
- 配对页默认只显示:标题、说明、「扫码配对」、「从相册选择二维码」;说明文案改为
「请让服务端管理页生成配对二维码后扫码」。原「服务器地址 / Token / 连接」不再直接暴露。 - 隐藏解锁入口:应用菜单 →「关于」→ 版本号 TextView 现在可点击;连续点击 7 次
(相邻两次间隔 ≤ 2 秒)切换解锁状态,Toast 分别为「直连模式已解锁」/「直连模式已锁定」。 - 解锁后配对页出现可展开的「直连模式(高级,仅内网直连裸 dsh 或未部署认证网关时使用)」区域:
点击标题展开/收起,展开后为原 URL / Token / 连接控件(另加一行直连模式说明),连接逻辑继续
调用attemptPair()(规范化地址、提取 token、保存直连配对),行为与 v0.4.2 一致。 - 解锁状态存 SharedPreferences:独立文件
dsh_mobile_ui_prefs,键direct_mode_unlocked;
与配对数据分开存放,因此「忘记此设备」/重新配对清除dsh_mobile_prefs时不会重置解锁状态;
只有系统「清除数据」/卸载才恢复默认隐藏。 - 「关于」对话框改为自定义视图:版本号一行(可点,隐藏入口)+ 原包名 / Android 版本 /
服务器地址 / 设备有效期信息(可选中复制),内容与 v0.4.2 等价。 - 扫码配对、证书 pin、认证网关兑换/登录、启动静默登录、前台续期、返回手势菜单与 v0.4.2 一致,
安全语义未改变;直连模式仍为 fail-open(仅隐藏入口,未删除)。
2.3 其他
- 版本号升至
0.4.3(versionCode10);「关于」版本兜底字符串同步更新。 - 新增字符串:
pair_gallery、pair_gallery_failed、pairing_message_scan、direct_mode_title、
direct_mode_hint、direct_mode_unlocked、direct_mode_locked、about_version_line;
删除旧pairing_message(由pairing_message_scan取代)。 - 未新增权限、未新增第三方依赖;相册解码与直连隐藏入口均在
MainActivity内实现。 - 诊断日志/Logcat 只记录失败异常类型、识别超时、解锁/锁定状态等,不打印二维码内容、
ticket、token、deviceToken 或 cookie。
3. 兼容性说明
| 场景 | 行为 |
|---|---|
| v0.4.2 覆盖升级 | 保留服务器地址、pin、deviceToken、WebView cookie、配对 profile;无需重新配对 |
| v0.4.3 新配对页 | 默认只显示扫码/相册;直连模式需在「关于」连点版本号 7 次解锁 |
| v0.4.2 已解锁? | v0.4.2 没有该标记,升级后默认隐藏;需要直连的用户按 7 次点击解锁 |
| 直连解锁状态 | 存独立 dsh_mobile_ui_prefs,忘记设备/重新配对不清除;清除 App 数据/卸载后重置 |
| Android 13+ | 相册入口使用系统照片选择器 |
| Android 8–12 | androidx 回退 ACTION_OPEN_DOCUMENT(image 类型),不需要存储权限 |
| 无相机设备 | 可用相册入口;无照片选择器/文件选择器时退回原有扫码页行为,不崩溃 |
| 相册图片不是配对二维码 | 只 Toast 失败提示,不进入配对/兑换,不放宽 QrPairPayload 校验 |
| 认证网关 / 证书 pin / 静默登录 / 前台续期 | 与 v0.4.2 完全一致 |
| 直连模式(解锁后) | fail-open TLS、地址 + token 行为与 v0.3.0–v0.4.2 一致 |
4. 验证记录(构建机,2026-10-27)
| 项目 | 结果 |
|---|---|
source toolchain/env.sh && ./gradlew assembleRelease |
BUILD SUCCESSFUL(含 lintVitalRelease) |
./gradlew lintRelease |
BUILD SUCCESSFUL;0 error / 16 warning(与 v0.4.2 相同集合,均为既有项;新增代码无告警) |
| APK badging | versionCode='10' versionName='0.4.3',minSdk 26 / targetSdk 35 |
| 签名 | apksigner verify --print-certs 通过,证书 SHA-256 与 v0.4.2 一致(可覆盖升级) |
| zipalign | zipalign -c -v 4 通过 |
| 真机安装/流程验证 | 未执行:adb devices 无设备(RMX5200 未连接);本轮未启动模拟器 |
真机验证清单见仓库
mobile-apk/docs/manual-validation.md§12(v0.4.3 相册配对 / 扫码回归 /
7 次点击解锁与锁定 / 直连模式连接内网 dsh),需在真机上逐项勾选后再对外发布。
5. 交付物
| 文件 | 说明 |
|---|---|
deliverables/DSH-Mobile-v0.4.3-release.apk |
发布 APK |
deliverables/release/DSH-Mobile-v0.4.3-release.apk |
发布 APK(release 目录副本) |
deliverables/release/SHA256SUMS-v0.4.3.txt |
本版本校验文件(sha256sum -c 通过) |
deliverables/release/release-notes-v0.4.3.md |
本文件 |
deliverables/SHA256SUMS.txt |
全量校验清单(已追加 v0.4.3 行) |
deliverables/index.html |
下载页(当前版本 v0.4.3,保留旧版本链接) |
6. 敏感信息与安全
- 相册识别到的二维码内容只在内存中解析,不落盘、不写日志;ticket 仍只存在于二维码 fragment。
- 直连解锁标记不含任何凭据,仅一个布尔值。
- 本次发布未改变任何认证网关/证书 pin 逻辑,也没有放宽
QrPairPayload校验。
DSH Mobile v0.4.2
DSH Mobile v0.4.2 发布说明
发布对象:Android APK(DeepSeek Harness
dshWeb GUI 的远程 WebView 客户端)
源码仓库:DSH-Mobile
源码版本:0.4.2/ versionCode9(按任务要求未 git commit / 未打 tag,由 Lead 验证后处理)
发布包准备日期:2026-10-27
1. 版本与校验
| 项目 | 内容 |
|---|---|
| 版本名 / versionCode | 0.4.2 / 9 |
| applicationId | ai.deepseek.dsh.mobile |
| 最低系统 | Android 8.0(API 26);targetSdk 35 |
| 发布包 | DSH-Mobile-v0.4.2-release.apk |
| 发布包大小 | 25,516,087 字节(24.33 MiB / 25.52 MB) |
| 发布包 SHA-256 | 1ad91b691e183c03039c5f6e88a3639c9474a68a06b6b96f2620d3e9b61b3495 |
| 签名证书 | CN=DSH Mobile, OU=Personal, O=Personal, L=NA, ST=NA, C=CN;证书 SHA-256 45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8(与 v0.4.0/v0.4.1 相同,可覆盖升级) |
| Debug 包 | 本轮未构建、未随发布包分发(发布仅提供 release) |
校验方法(在本目录执行):
sha256sum -c SHA256SUMS-v0.4.2.txt2. v0.4.2 变更内容
2.1 启动即静默登录(不闪登录页)
- App 冷启动进入 WebView 前,如果当前是认证网关模式、证书 pin 有效、已保存
deviceToken,并且本机曾经成功登录过(authDeviceTrusted;v0.4.1 升级用户用
authSessionEstablished兼容),先在后台调用POST /__auth/renew:- 成功:把服务端返回的 Set-Cookie 注入 WebView,随后直接加载 GUI 根地址;
期间顶部显示「正在使用设备凭据登录…」与加载进度,不会先闪一下登录页。 - 服务端返回 401(
device_required/origin_changed/ 设备 token 无效):
加载登录页,并在顶部红色提示条明确显示「设备配对已失效,需要重新扫码配对」;
点击提示条会弹出「忘记此设备」确认,确认后回配对页。 - 网络/TLS 等暂时性失败:加载登录页,顶部提示「自动登录失败:网络或 TLS 连接异常,
点击重试」;点击提示条或原生网络错误层的「重试」都会重新走静默登录,而不是只刷新
登录页。
- 成功:把服务端返回的 Set-Cookie 注入 WebView,随后直接加载 GUI 根地址;
- 首次扫码配对后仍必须完成网关登录(用户名/密码;TOTP 在服务端生成配对二维码时
校验):静默登录只在设备凭据已经过一次真实登录验证后启用,避免绕过设备绑定这一第二因素。 - 与 v0.4.0/v0.4.1 的“登录页出现后再续期”兜底共存:启动静默续期前会置位
loginPageRenewTried,失败后登录页不会再自动触发一次同样的续期,避免
“登录页 → renew → 登录页”死循环;成功进入 GUI 后复位。
2.2 前台恢复自动续期
onResume时若 GUI 已登录且距上次成功 renew 超过 1 小时,后台静默 renew:- 更新 WebView 的 session Set-Cookie;
- 记录
lastAuthRenewAt; - 若服务端返回可选的
deviceExpiresAt(毫秒时间戳),更新本机设备有效期; - 不刷新当前页面(只有续期期间页面恰好 401,才补一次 reload)。
- 节流/去重:所有 renew(启动、401、前台)共享同一个 in-flight 标记;失败后 1 分钟内
不会因前后台切换重复请求;Activity 销毁/退出登录/忘记设备时取消 Future 并作废回调代际。 - 失败处理:401/device_required/origin_changed 不立刻打断当前页面,标记后下一次前台
恢复或下一次导航时回登录页;网络/TLS 暂时失败只记日志,保留页面,下次前台再试。
2.3 菜单拆分:退出当前会话 / 忘记此设备
认证网关模式的应用菜单由原来的「登出」拆为两项(同时去掉与“忘记此设备”重复的旧
「重新配对」项):
- 退出当前会话
- 仍执行 v0.4.0/v0.4.1 的登出流程:WebView POST
/__auth/logout,清 WebView 的
session cookie 后回登录页; - 保留 deviceToken 与
dsh_gate_device设备绑定 cookie; - 下次冷启动 App 会静默 renew 自动登录(当前进程内不会立刻把会话建回来)。
- 仍执行 v0.4.0/v0.4.1 的登出流程:WebView POST
- 忘记此设备(重新配对)
- 二次确认后删除本机该 host 的 deviceToken(
DeviceTokenStore.clear); - 清除该 host 的全部 WebView cookie(
keepDeviceCookie = false,含设备绑定); - 清空 session/profile(服务器地址、pin、信任标记、设备有效期等);
- 回到配对页,下次必须重新扫码。
- 二次确认后删除本机该 host 的 deviceToken(
直连模式(未部署认证网关)菜单保持 v0.4.1 的「刷新 / 重新配对 / 诊断日志 / 关于 /
退出应用」,返回手势状态机与 v0.4.1 完全一致。
2.4 设备有效期展示
AuthGateClient.renewDetailed解析服务端可选字段deviceExpiresAt(毫秒)。- 扫码配对时把
/__auth/pair/redeem响应里的expiresAt(网关侧设备有效期)作为初始
设备有效期保存,因此新配对设备即使服务端暂未返回deviceExpiresAt也能展示。 - 应用菜单「关于」新增一行「设备有效期至:yyyy-MM-dd HH:mm」;未知/旧数据/旧服务端
显示「—」,不伪造数据。 - 有效期只存 SharedPreferences 毫秒值,不敏感;不含 token/cookie。
2.5 其他
- 版本号升至
0.4.2(versionCode9);「关于」版本兜底字符串同步更新。 - 新增字符串:退出当前会话、忘记此设备、启动静默登录、设备失效提示、网络重试提示、
设备有效期等;删除旧的menu_logout。 - 诊断日志/Logcat 只记录 HTTP status、error code、host、hasDeviceExpiresAt 等,
不打印 deviceToken、ticket、cookie 或 token 明文。
3. 兼容性说明
| 场景 | 行为 |
|---|---|
| v0.4.1 覆盖升级 | 保留服务器地址、pin、deviceToken、WebView cookie;首次启动直接尝试静默登录 |
旧服务端不返回 deviceExpiresAt |
静默登录/前台续期正常;「关于」显示“—”或扫码时的设备有效期,不报错 |
服务端未返回 redeem expiresAt |
设备有效期显示“—”,其余功能不受影响 |
| 首次扫码配对 | 不静默登录,仍走网关登录页(用户名/密码) |
| 网关设备被吊销 / origin 变更 | 启动/前台续期得到 401,提示需要重新扫码配对 |
| 直连模式(地址 + token) | 行为与 v0.4.1 一致,无网关静默登录/菜单拆分 |
当前已部署服务端 /__auth/renew 响应 |
截至本轮构建只返回 expiresAt(session 到期)和 deviceId,本版不把它误当设备有效期;设备有效期来自配对响应。服务端将来返回 deviceExpiresAt 时会自动采用 |
4. 验证记录(构建机,2026-10-27)
| 项目 | 结果 |
|---|---|
source toolchain/env.sh && ./gradlew assembleRelease |
BUILD SUCCESSFUL(含 lintVitalRelease) |
./gradlew lintRelease |
BUILD SUCCESSFUL;0 error / 16 warning(均为 v0.4.1 已有项,新增代码无告警) |
| APK badging | versionCode='9' versionName='0.4.2',minSdk 26 / targetSdk 35 |
| Manifest 检查 | enableOnBackInvokedCallback=false 仍写入 APK(返回手势行为不变) |
| 签名 | apksigner verify --print-certs 通过,证书 SHA-256 与 v0.4.1 一致(可覆盖升级) |
| zipalign | zipalign -c -v 4 通过 |
| 真机安装/流程验证 | 未执行:adb devices 无设备(RMX5200 未连接);emulator -accel-check 报 Could not open /dev/kvm : Permission denied,且无可用 AVD,未做软件模拟 |
真机验证清单见仓库
mobile-apk/docs/manual-validation.md§11(v0.4.2 静默登录/续期/菜单
拆分验证清单);请在真机上按该清单逐项勾选后再对外分发。
5. 升级与安装
- 可直接覆盖安装 v0.4.0/v0.4.1(同一 release 签名),配对信息、deviceToken、WebView
cookie 均保留:adb install -r DSH-Mobile-v0.4.2-release.apk
- 若从 debug 包或 v0.3.0 及更早版本升级,需先卸载(签名不同):
adb uninstall ai.deepseek.dsh.mobile adb install DSH-Mobile-v0.4.2-release.apk
- 升级后首次冷启动:已登录过的认证网关设备会先显示「正在使用设备凭据登录…」,
成功后直接进入 GUI;首次配对设备仍进入登录页。 - 菜单入口仍是系统返回手势/返回键 → 应用菜单(无悬浮 ⋮ 按钮)。
6. 已知限制
- 本轮未做真机回归:启动静默登录、前台 1 小时续期、菜单拆分、设备有效期展示
仅通过编译、Android Lint 与代码审查验证,需按manual-validation.md§11 真机确认。 - 当前服务端未返回
deviceExpiresAt:前台续期无法延长/刷新设备有效期;「关于」中
新配对设备显示配对响应里的有效期,v0.4.1 升级设备在重新配对前显示“—”。 - 设备有效期不会自动续期:服务端设备 TTL 到期后仍会 401,需要重新扫码配对
(v0.4.2 不会静默绕过设备有效期)。 - Activity 销毁时的网络取消是尽力而为:
Future.cancel(true)不一定能中断
HttpsURLConnection,但连接有 15 秒超时,且迟到回调会被代际标记丢弃。 - 退出当前会话后立即从最近任务返回:当前进程可能仍停在登录页;只有冷启动/重新
创建 Activity 才会静默自动登录,这是为了不让“退出”在用户眼前立刻失效。 - 其它限制同 v0.4.1:入口依赖系统返回手势、认证网关换证书需重新扫码、直连模式仍是
fail-open、无后台推送、仅适配 Android 8.0+。
7. 安全提示
- 访问令牌与设备令牌等同于登录凭据:请勿外传、截图或提交到公开渠道;
设备令牌经 Android Keystore 加密保存。 - 静默登录只使用已保存的设备凭据;首次配对仍需在网关登录页输入用户名/密码(TOTP 在生成配对二维码时校验)。
- 出现 TLS 警告条时,务必点开核对证书指纹与主机名;认证网关模式仍是严格证书 pin。
- 只从可信来源下载 APK,并核对本文档给出的 SHA-256。
- 服务器自签证书私钥(
dsh.key.pem)应妥善保管,不要随 APK 或发布资产分发。
DSH Mobile v0.4.1
DSH Mobile v0.4.1 发布说明
发布对象:Android APK(DeepSeek Harness
dshWeb GUI 的远程 WebView 客户端)
源码仓库:DSH-Mobile
源码版本:0.4.1/ versionCode8(源码 commitd7d0277,tagv0.4.1)
发布包准备日期:2026-10-27
1. 版本与校验
| 项目 | 内容 |
|---|---|
| 版本名 / versionCode | 0.4.1 / 8 |
| applicationId | ai.deepseek.dsh.mobile |
| 最低系统 | Android 8.0(API 26);targetSdk 35 |
| 发布包 | DSH-Mobile-v0.4.1-release.apk |
| 发布包大小 | 25,494,303 字节(24.31 MiB / 25.49 MB) |
| 发布包 SHA-256 | a9ad7979c1487e31b0c55ba374c39aed6e3f5b5990c3421ebf38748838848e80 |
| 签名证书 | CN=DSH Mobile, OU=Personal, O=Personal, L=NA, ST=NA, C=CN;证书 SHA-256 45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8 |
| Debug 包 | 本轮未构建、未随发布包分发(发布仅提供 release) |
校验方法(在本目录执行,SHA256SUMS-v0.4.1.txt 同时给出源码版本对照信息):
sha256sum -c SHA256SUMS-v0.4.1.txt2. v0.4.1 变更内容
2.1 移除右上角悬浮 ⋮ 按钮
- v0.4.0 及更早版本在 WebView 右上角固定一个 48dp 的 ⋮ 悬浮按钮,会压住 dsh Web GUI
右上角的「打开右侧边栏」按钮,导致该按钮无法点击。 - v0.4.1 起彻底移除该悬浮按钮(不再
addView,相关构建代码/尺寸常量一并删除),
dsh GUI 顶栏右上角恢复完整可点区域。 - 顶部 TLS / 明文 HTTP 警告条原先为避让 ⋮ 按钮而下移 64dp,现改为贴在内容区顶部
(topMargin 归零),不再与任何悬浮控件耦合。
2.2 返回手势打开「应用菜单」
- 菜单入口改为系统返回手势(真我等手势导航机型从屏幕左/右边缘向内滑)或
三键导航的返回键:在根页面按返回即弹出「应用菜单」。 - 菜单由
AlertDialog.Builder().setItems(...)列表实现,未引入任何新依赖,
包含原有全部菜单项并新增「退出应用」:- 刷新
- 重新配对
- 退出登录(仅认证网关模式显示,行为与 v0.4.0 一致)
- 诊断日志
- 关于
- 退出应用(
finishAffinity(),仅结束任务栈,不卸载、不清数据)
- 返回事件状态机(优先级从高到低):
- 错误覆盖层可见 → 关闭错误层(若渲染进程已死会重建 WebView,避免停在空白页);
- 输入法可见 → 收起输入法;
WebView.canGoBack()→ 网页后退;- 应用菜单正在显示 → 关闭菜单;
- 否则 → 弹出「应用菜单」。
- dsh 是单页应用(SPA),在根页面没有 WebView 历史,因此每次在根页面按返回都会弹菜单,
这是 v0.4.1 的预期行为(不再有「再按一次返回键退出」的旧逻辑,退出请用菜单里的「退出应用」)。 - 输入法可见性用
ViewCompat.setOnApplyWindowInsetsListener+WindowInsetsCompat
维护:Android 11+ 直接读ime()的可见性,Android 10 及以下由 androidx 兼容层折算,
不依赖InputMethodManager的猜测。 - 首次进入主界面(已配对)时 Toast 提示一次
「从屏幕边缘向内滑动(返回手势)可打开应用菜单」,用 SharedPreferences 键
back_gesture_menu_hint_v1记录,每个安装只提示一次。
2.3 预测性返回显式关闭
AndroidManifest.xml设置android:enableOnBackInvokedCallback="false":
Android 13+ 不做预测性返回动画,返回手势/返回键统一交付
Activity.onBackPressed → OnBackPressedDispatcher,避免不同 OEM/系统版本上的
预测性返回差异,保证状态机行为一致。
2.4 其他
- 版本号升至
0.4.1(versionCode8);「关于」对话框的版本兜底字符串同步更新。 - 移除不再使用的字符串资源:
menu_open(⋮ 按钮无障碍描述)、exit_confirm
(双击返回退出提示)。
3. 验证记录(构建机,2026-10-27)
| 项目 | 结果 |
|---|---|
./gradlew assembleRelease |
BUILD SUCCESSFUL(含 lintVitalRelease) |
./gradlew lintRelease |
BUILD SUCCESSFUL;0 error / 16 warning(均为 v0.4.0 已有项,新增代码无告警) |
| APK badging | versionCode='8' versionName='0.4.1',minSdk 26 / targetSdk 35 |
| Manifest 检查 | enableOnBackInvokedCallback=false 已写入 APK |
| 签名 | apksigner verify --print-certs 通过,证书 SHA-256 与 v0.4.0 一致(可覆盖升级) |
| 真机安装/手势验证 | 未执行:adb devices 无设备(RMX5200 未连接);模拟器 /dev/kvm 被容器拒绝(Permission denied),-accel off 软件模拟未能在本轮完成开机/验证 |
真机验证清单见仓库
docs/manual-validation.md§10(v0.4.1 手势菜单验证清单);
请在真机上按该清单逐项勾选后再对外分发。
4. 升级与安装
- 可直接覆盖安装 v0.4.0(同一 release 签名),配对信息、token、WebView cookie 均保留:
adb install -r DSH-Mobile-v0.4.1-release.apk
- 若从 debug 包或 v0.3.0 及更早版本升级,需先卸载(签名不同):
adb uninstall ai.deepseek.dsh.mobile adb install DSH-Mobile-v0.4.1-release.apk
- 首次打开后的 1~2 秒会出现一次返回手势提示 Toast;之后不再出现。
- 菜单入口变化:不再有右上角 ⋮ 按钮,从屏幕边缘向内滑(或按三键返回键)打开。
5. 已知限制
- 入口依赖系统返回手势/返回键:vivo/OPPO 等机型的「全屏手势」需保持系统默认的
左右边缘内滑返回;若用户在系统设置里关闭了返回手势或改用手势 App,菜单可能难以发现
(首次 Toast 与本文档用于引导)。 - 认证网关模式换证书仍需重新扫码:证书 pin 是唯一信任锚(与 v0.4.0 相同)。
- 直连模式仍是 fail-open:证书问题只提示不阻断,不可信网络建议改用认证网关模式。
- 完全依赖服务端:App 不内置前端资源,服务端不可达时界面不可用。
- 无后台推送/常驻:App 退到后台后连接可能被系统回收,需重新打开。
- 本轮未做真机回归:手势导航/三键导航、键盘优先收起、错误层优先关闭等行为
已在代码层实现并通过编译与静态检查,但需按docs/manual-validation.md§10 在真机确认。 - 仅适配 Android 8.0+:未针对平板/折叠屏做专门布局。
6. 安全提示
- 访问令牌与设备令牌等同于登录凭据:请勿外传、截图或提交到公开渠道;
配对信息保存在应用私有存储中(设备令牌经 Android Keystore 加密)。 - 出现 TLS 警告条时,务必点开核对证书指纹与主机名,确认是预期服务器后再继续。
- 只从可信来源下载 APK,并核对本文档给出的 SHA-256。
- 服务器自签证书私钥(
dsh.key.pem)应妥善保管,不要随 APK 或发布资产分发。
DSH Mobile v0.4.0
DSH Mobile v0.4.0 发布说明
发布对象:Android APK(DeepSeek Harness
dshWeb GUI 的远程 WebView 客户端)
源码仓库:DSH-Mobile
源码 tag:v0.4.0(annotated tag)→ commit12ae7f030e7c63fdfae41d3a1a9ce9aebb87b47e
发布包准备日期:2026-10-27
1. 版本与校验
| 项目 | 内容 |
|---|---|
| 版本名 / versionCode | 0.4.0 / 7 |
| applicationId | ai.deepseek.dsh.mobile |
| 最低系统 | Android 8.0(API 26);targetSdk 35 |
| 发布包 | DSH-Mobile-v0.4.0-release.apk |
| 发布包大小 | 25,493,855 字节(24.31 MiB / 25.49 MB) |
| 发布包 SHA-256 | 5d19e16e9445fc54e94f82ba1e89f356ceed03df71c7dbc726847b16c35552c5 |
| Debug 包 | DSH-Mobile-v0.4.0-debug.apk |
| Debug 包大小 | 26,521,977 字节(25.29 MiB / 26.52 MB) |
| Debug 包 SHA-256 | 849139bb2ee989fc2c3ae81237a1a286f6f593f82b0a060d75a40f8885abe207 |
| 签名证书 | CN=DSH Mobile, OU=Personal, O=Personal, L=NA, ST=NA, C=CN;证书 SHA-256 45dee26ac9aa785dda281970bc07072665624ef381616d310331f849216f62c8 |
校验方法(在本目录执行,SHA256SUMS-v0.4.0.txt 同时给出源码 tag 对照信息):
sha256sum -c SHA256SUMS-v0.4.0.txt2. 功能列表
- 远程 WebView 客户端:用系统 WebView 加载远端
dshWeb GUI,不内置任何前端静态资源,界面与功能始终跟随所连接的dsh服务端版本,客户端无需频繁随前端重新发版。 - TLS 处理
- 直连模式:fail-open,证书不受信任时不阻断连接,仅在页面顶部显示黄色警告条;点开可查看证书主体、颁发者、有效期、SHA-256 与错误类型,并复制详情。
- 认证网关模式:严格证书 pin,只信任配对二维码中携带的证书 SHA-256 指纹,指纹不匹配时阻断连接(fail-closed)。
- 语音输入 / 录音:WebView 页面
getUserMedia/MediaRecorder走原生麦克风授权;需要https://安全上下文,首次使用弹出系统麦克风权限。 - 下载与剪贴板:页面中的附件/导出文件交给 App 下载器保存到系统「下载」目录(API 29+ 经 MediaStore,API 28 及以下写公共 Downloads);WebView Clipboard API 不可用时回退到原生剪贴板桥。
- 扫码配对 gateway 设备(v0.4.0 新增):CameraX 取景 + ML Kit 二维码识别,扫描认证网关生成的配对二维码,自动完成服务器地址、令牌与证书指纹配置;也可手动粘贴完整配对链接。
- 设备令牌:设备令牌自动续期与登出,无需反复输入服务端凭据;支持重新配对。
- fail-open 自签证书:v0.3.0 起 App 不内置任何证书或指纹,自签 HTTPS 服务器可直接连接(仅警告条);认证网关模式则由扫码获得 pin,仅对该 host 严格校验。
- 诊断日志:App 内「⋮ → 诊断日志」收集 WebView / JS / 下载 / TLS 日志,写入前对
token=等敏感查询参数脱敏,可一键复制。 - Android 权限
INTERNET:访问远端服务。ACCESS_NETWORK_STATE:网络状态判断。CAMERA(运行时权限):扫码配对;无相机设备仍可安装,改用直连/粘贴链接。VIBRATE:扫码成功震动反馈。RECORD_AUDIO(运行时权限):语音输入。MODIFY_AUDIO_SETTINGS:WebView 选择通信音频设备(缺失会报NotReadableError)。WRITE_EXTERNAL_STORAGE(maxSdkVersion=28):仅 Android 9 及以下保存下载文件。
- 细节适配:沉浸式状态栏/导航栏、软键盘避让、深色模式跟随系统、返回键两级退出、401 鉴权失败覆盖层(重试 / 重新配对)、断网与渲染进程崩溃提示。
3. 前置要求
| 项目 | 要求 |
|---|---|
| 服务器 | 已部署并可访问 dsh(DeepSeek Harness),手机与服务器网络互通 |
| 认证网关 | 扫码配对与设备令牌需要服务端安装并启用 dsh-auth-gate v0.4+;未安装时仍可使用原有「服务器地址 + 访问令牌」直连模式 |
| 手机系统 | Android 8.0(API 26)及以上 |
| 可选硬件 | 相机(扫码配对)、麦克风(语音输入) |
4. 安装步骤
- Sideload 安装
- 用 ADB:
adb install -r DSH-Mobile-v0.4.0-release.apk - 或用文件管理器打开 APK,按系统提示允许「安装未知应用」后安装。
- 安装前可先核对 SHA-256 与 §1 一致。
- 用 ADB:
- 扫码配对 gateway 设备
- 在服务端生成认证网关的配对二维码(由
dsh-auth-gate的配对流程/管理页面生成)。 - 打开 App →「扫码配对」→ 扫描该二维码;App 自动写入服务器地址、令牌与证书 SHA-256 指纹,并进入网关登录页。
- 无法扫码时可粘贴完整配对链接;也可退回直连模式,手动填写服务器地址与访问令牌。
- 在服务端生成认证网关的配对二维码(由
- 用户名 + 密码登录
- 在网关登录页输入用户名与密码;若服务端启用了 TOTP,再输入 6 位动态验证码。
- 登录成功后进入
dshWeb GUI;设备令牌会自动续期,后续无需重复输入凭据。
5. 已知限制
- 完全依赖服务端:App 不含前端资源,服务端不可达或版本不兼容时界面无法使用。
- 直连 fail-open 有 MITM 风险:证书校验失败仍会连接,仅显示警告条;不可信网络下建议改用认证网关模式、受信 CA 证书或 VPN。
- 认证网关模式换证书需重新扫码:证书指纹是唯一信任锚,网关更换证书或地址后必须重新配对。
- 语音输入仅 HTTPS:
http://页面无法调用麦克风(WebView 安全上下文限制)。 - 仅适配 Android 8.0+:未针对平板/折叠屏做专门布局,横竖屏以 WebView 自适应为主。
- 无后台推送/常驻:App 退到后台后连接可能被系统回收,需重新打开;WebSocket 断线需重试或重进。
- 下载能力有限:仅保存到系统下载目录,不提供下载队列管理/断点续传界面;超大文件受系统与网络影响。
- Debug 包仅供调试:体积更大、可调试,不要分发给最终用户。
6. 安全提示
- 访问令牌与设备令牌等同于登录凭据:请勿外传、截图或提交到公开渠道;配对信息保存在应用私有存储中。
- 出现 TLS 警告条时,务必点开核对证书指纹与主机名,确认是预期服务器后再继续。
- 优先使用认证网关模式(严格 pin);在咖啡厅、酒店等不可信网络中避免使用直连 fail-open,或先连接 VPN。
- 只从可信来源下载 APK,并核对本文档给出的 SHA-256。
- 服务器自签证书私钥(
dsh.key.pem)应妥善保管,不要随 APK 或发布资产分发。 - 共享设备或已 root 设备请自行评估风险;退出前可通过「重新配对」清空配对信息,或直接卸载。