# 截图直链（Android）

点一下按钮 = **自动退到桌面/背后的界面 → 截那一屏 → 上传到你的接口 → 直链复制进剪贴板**。

- 安装包：`截图直链.apk`（同目录 `shotup.apk` 是编译产物，同一个文件）
- 包名：`com.shotup.app`　版本 1.0　最低 Android 6.0（API 23），targetSdk 34
- 源码：`src\com\shotup\app\`（MainActivity.java、ShotService.java）、`AndroidManifest.xml`、`res\`
- 重新编译：运行 `编译.ps1`（脚本会把源码同步到 `C:\shotup_build` 再打包，因为 aapt2 不认中文路径）

## 一、截图逻辑（重要）

点「截图并上传」后，App **立刻把自己收起来**，回到你点它之前那个界面（桌面、游戏、网页都行），**再截那一屏**——所以截到的是里面的画面，不是这个 App 自己的界面。

实测：在桌面点按钮 → App 收起 → 截到的就是桌面图标那一屏（720×1280、506 KB 无损 PNG，App 界面完全不在图里）。

截完上传成功后：
- 直链自动进剪贴板（可以直接粘贴）
- 弹一条通知「截图已上传，直链已复制」，**点通知回到本应用**就能看到预览、直链和“最近上传”记录
- 相册「Pictures/截图直链/」也留一份原图

## 二、界面：没有任何输入框

按你的要求，界面上**一个输入框都没有了**：只有「截图并上传」按钮、状态、预览、直链、复制直链、相册多选、最近上传。上传地址是**内置常量**，装好就按那条地址走。

## 二点五、截图按 zip 打包上传

每次截图都先存成**无损 PNG 原图**，再**原封不动打进一个 zip**（zip 里就一张 PNG，打包不二次压缩，PNG 字节一模一样），上传的就是这个 `.zip`（例：`shot_20261113_221719.zip`）。

- 为什么要 zip：网盘/接口多数只收压缩包，直接传图片会被拦
- 已实测：服务端收到 `shot.zip`，解出来是 `shot_20261113_221719.png`，720×1280，505038 字节，PNG 无损
- 打包开关在源码常量 `PACK_AS_ZIP`（true=传 zip，false=传原图 PNG）

## 二点六、直链地址怎么工作（内置，不用填）

- 地址是**可覆盖写的直链**（例：`https://你的域名/img/shot.zip`）→ 用 `PUT` 直接写上去，**同一条链接永远是最新那张截图**
- 地址是**上传接口** → PUT 被拒（404/405）时自动改走 `POST multipart/form-data`，再从返回里取直链
- 请求头、字段名、文件名模板都是内部常量（`DEFAULT_URL` 等），改一行重新编译就换目标

⚠️ 网盘“分享页”不行：`share.feijipan.com/s/xxx`、`qq1212.lanzout.com/b0zl9v3wh` 这类都是**下载分享页**，
- 飞机盘分享页前端接口只有 `/ws/share/*`（列表、下载），**没有上传接口**
- 蓝奏分享页 HTML 里也没有上传按钮，只有下载

要上传到网盘必须用**账号**（登录后才走 `/html5up.php` 那套上传），分享密码不是登录密码——
用 `qq1212 / cqou` 试登录，服务器直接回「密码错误！」。

可选的三条路：
1. 给蓝奏**登录账号 + 登录密码**（不是分享密码），我在 App 里做一次自动登录，之后每次截图上传 zip 到你的文件夹
2. 给对象存储/图床的**可写直链**（OSS/COS/七牛/R2/S3 的 PUT 地址 + 签名）
3. 给**自己的上传接口**（URL + 鉴权 + 返回直链字段）

## 三、第一次使用（3 步）

1. 打开 App，直接点「截图并上传」。
2. 系统弹「要开始使用截图直链录制或投放内容吗？」→ 点**立即开始**（同一会话只弹一次）。
3. 之后每次点按钮都是「收起 → 截图 → 打包 zip → 上传 → 复制直链」，通知里能看到结果。

杀进程后第一次点按钮会再弹一次系统授权框，这是 Android 的规定。

## 三、你的上传接口要满足什么

App 用标准 `POST multipart/form-data` 上传，参数都能在界面上改：

| 设置项 | 默认值 | 说明 |
|---|---|---|
| 上传接口地址 | 空 | 例：`https://你的域名/api/upload`；http 也支持（已开 cleartext） |
| 请求头 | 空 | 一行一个 `key: value`，例：`Authorization: Bearer 你的token` |
| 文件字段名 | `file` | 服务端接收文件的字段名 |
| 上传文件名 | `shot_{time}.png` | `{time}` 换成 `年月日_时分秒_毫秒`，**每张独立、不覆盖** |
| 返回里直链的字段名 | `url` | 返回 JSON 时取哪个字段；嵌套写 `data.url`；纯文本接口留空 |
| 附加表单字段 | 空 | 一行一个 `key=value`，例：`token=abc`、`bucket=shot` |

服务端返回什么都能识别：
- JSON：`{"code":0,"data":{"url":"https://cdn.xx.com/shot.png"}}` → 字段名填 `data.url`
- JSON：`{"url":"https://.../a.png"}` → 字段名填 `url`
- 纯文本：直接返回 `https://.../a.png` → 字段名留空
- 兜底：JSON 里找不到配置字段时，自动抓返回内容里的第一个 http(s) 地址

如果服务端要求 **PUT / 二进制 body**，或返回的是 HTML 页面里的链接，这套 multipart 方案不适用，把实际格式发我再改。

## 四、画质与多张

- **原图、不压画质**：以**无损 PNG**（`Bitmap.compress(PNG, 100)`）上传，分辨率就是屏幕真实分辨率，不缩放、不转 JPEG。实测 720×1280 的机器上传回来就是 720×1280，逐像素无损。
- **不覆盖**：文件名默认带毫秒时间戳，连续截多少张都是各自独立的直链。
- **批量**：点「从相册选图上传（可多选）」→ 长按第一张进入多选 → 再点其它图片 → 右上角「选择」→ 逐张上传，最后所有直链换行拼一起复制进剪贴板。（这条只上传相册里已有的图，不会退桌面。）
- 想让某个固定地址永远显示最新一张（覆盖式热更新），把「上传文件名」改成 `latest.png` 之类固定名字即可。

## 五、实测记录（Android 14 x86_64 模拟器 720×1280，全部通过）

| 用例 | 结果 |
|---|---|
| 点按钮后是否退出本应用 | 是，直接退回桌面/背后界面 |
| 截到的是不是那一屏 | 是，上传图里是桌面图标，App 界面不在图中 |
| 文件规格 | 720×1280，无损 PNG，约 500 KB |
| 覆盖写入同一条直链 | `PUT /cover.png` → HTTP 200，原地址内容被替换成新截图，GET 回来 `image/png`、字节数一致 |
| 直链访问 | `HTTP 200`，`Content-Type: image/png` |
| 连点两次 | 覆盖模式下永远一条链接、始终最新；普通上传模式下新文件独立命名，旧文件保留 |
| 相册多选 2 张 | 两张各自上传成功，界面列出直链 |
| 剪贴板 | 上传成功后提示「直链已复制」；多张时为多行链接 |

## 六、可能踩到的坑（已处理）

1. **Android 14 必须前台服务**：调 `MediaProjection` 前必须有 `foregroundServiceType=mediaProjection` 的前台服务，否则抛 `SecurityException`。已加 `ShotService`（通知栏“截屏通道已就绪”常驻，退出 App 自动停）。
2. **必须先注册回调**：`registerCallback` 要在 `createVirtualDisplay` 之前，否则抛 `Must register a callback before starting capture`。已注册。
3. **每次冷启动要重新授权**：授权 token 只在一次会话有效，杀进程后第一次点会再弹系统框。
4. **升级安装签名**：`编译.ps1` 复用目录里的 `shotup.keystore`，覆盖安装不会签名冲突；别删这个 keystore。
5. **截图时 App 在后台**：`VirtualDisplay` 由前台服务撑着，退到后台照样能抓到当前屏幕；抓帧前会先丢掉旧帧再等新帧，避免传上一张旧画面。

## 七、失败时怎么排查（状态栏直接写原因）

- `先填上传接口地址` —— 地址没填或没保存
- `上传失败：HTTP 401/403` —— 鉴权头不对，检查「请求头」
- `上传失败：HTTP 400 ...` —— 字段名不匹配，检查「文件字段名 / 附加表单字段」
- `没解析出直链，返回：...` —— 返回格式和「返回里直链的字段名」对不上，按引号里的原始返回改字段名
- `上传异常：...UnknownHostException / timeout` —— 网络或地址写错（手机访问电脑上的测试服务要用局域网 IP 或 `adb reverse`，不能用 `127.0.0.1`）
- `截图失败：没取到画面` —— 再点一次；若系统停掉了截屏授权，App 会提示重新授权
