Files
MAG160C/docs/android_app/HANDOFF_DEVELOPMENT.md
T

183 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MAG160C 统一安卓APP — 完整开发交接文档(换模型用)
> 生成时间:2026-09-07。给接手模型读取本文件即可恢复全部上下文。
> 结合 `docs/android_app/session_state.md`(心跳)与 `docs/android_app/reverse_apk_features.md`(逆向总表)。
---
## 0. 给新模型的读取提示词
> 你是本项目的续任开发者。请先完整阅读 `C:\Project\MAG160C\docs\android_app\HANDOFF_DEVELOPMENT.md`
> 和 `C:\Project\MAG160C\docs\android_app\session_state.md`、`C:\Project\MAG160C\docs\android_app\reverse_apk_features.md`、
> `C:\Project\MAG160C\analysis\protocol_spec.md`,再继续开发。项目根目录 `C:\Project\MAG160C` 是 git 仓库,
> 源码在 `android\` 子目录(Gradle 工程,package `com.mag160c.thermal`)。当前会话状态与待办见 session_state.md。
---
## 1. 项目目标与背景
- 把 MAG160C 热像仪(160×120, 15fps, USB VID 0x833C)的官方 4 个安卓APP
(普通版 MAG-Cx / 专业版 MAG-Mx / ThermoScope 数据分析 / CoreSdkSample)逆向整合
为一款现代化安卓APP。
- 适配 **Android 16API 36**;用户反馈旧专业版在 Android 16 上花屏,重写根治。
- 逆向证据:`docs/android_app/reverse_apk_features.md`4 APP 功能总表)、
`analysis/protocol_spec.md`USB 协议/温度算法全逆向)。
- 用户要求:现代 UI、**构图钉死竖屏框架**(竖屏锁定,见 §4.3)、**不建立流氓文件夹**(媒体→MediaStore DCIM/MAG160C)、
无 32 位库(纯 Kotlin,无 NDK)。
## 2. 技术栈与工具链(已部署)
- **纯 Kotlin + Jetpack Compose (Material 3)**,无 NDK、无 .so、无 native。
- Gradle 8.14.3`C:\Tools\gradle-8.14.3\bin\gradle.bat`),JDK Zulu 21`C:\Program Files\Zulu\zulu-21`)。
- Android SDK`C:\Users\ZXC\AppData\Local\Android\Sdk`platforms/android-36、build-tools 36.1.0、
platform-tools/adb、模拟器 system-images android-36.1 google_apis_playstore x86_64、AVD `Medium_Phone_API_36.1`)。
- 工程:`C:\Project\MAG160C\android\`;版本目录 `gradle\libs.versions.toml`
AGP 8.11.1 / Kotlin 2.2.0 / Compose BOM 2025.06.01 / lifecycle 2.9.1 / junit 4.13.2)。
- **磁盘轻量约定**:不装 NDK/CMake/zig(曾占用 2.4GB+,已卸载);全 Kotlin。
- 模拟器实测命令(无窗口):
```powershell
Start-Process "$env:LOCALAPPDATA\Android\Sdk\emulator\emulator.exe" -ArgumentList @('-avd','Medium_Phone_API_36.1','-no-window','-no-audio','-gpu','swiftshader_indirect') -WindowStyle Hidden
# 安装/启动/截图:
adb -s emulator-5554 install -r build-artifacts\mag160c-app-debug.apk
adb -s emulator-5554 shell am force-stop com.mag160c.thermal
adb -s emulator-5554 shell am start -n com.mag160c.thermal/.MainActivity
adb -s emulator-5554 exec-out screencap -p > analysis\preview\xxx.png # 注意:exec-out 才二进制安全
adb -s emulator-5554 emu rotate # 旋转模拟器(每次90°)
```
## 3. 源码结构与核心模块
```
android/app/src/main/kotlin/com/mag160c/thermal/
├─ MainActivity.kt 启动,enableEdgeToEdge + 隐藏状态栏(沉浸)
├─ ui/AppRoot.kt App壳:底部导航(实时/相册/分析/设置)恒贴竖屏底边(Activity 竖屏锁定)
├─ ui/DeviceOrientation.kt 加速度计→手机物理朝向(条栏/OSD 文字补偿旋转用)
├─ ui/UiInsets.kt 单例:底部导航高度 px(渲染器用它留白)
├─ ui/theme/Theme.kt M3 动态取色(Android12+
├─ ui/live/ ★实时画面(最核心)
│ ├─ LiveViewModel.kt 状态机/演示模式/多点测温/追踪开关/拍照/录像/inset上报
│ ├─ LiveScreen.kt 稳定单分支布局:顶栏控制条(恒顶部,适配挖孔屏)+SurfaceView
│ └─ LiveRenderer.kt SurfaceView 软件渲染:图像恒90°旋转(3:4竖)钉在竖屏框架固定区域,
│ 文字恒屏幕水平;色标条/圆圈标记/中心温OSD
├─ ui/gallery/ 相册页(MediaStore DCIM/MAG160C 扫描 + 内嵌JPEG缩略图 + 运行时媒体权限)
├─ ui/analyze/ MDT 离线分析(缩放/调色板重渲染/备注回写/PDF报告)
├─ ui/settings/ 设置页(默认调色板/发射率/报警温度/语言/关于,SharedPreferences私有)
├─ media/
│ ├─ Mdt.kt MDT 容器格式(JPG + DDT段[info块+原始帧] + 152B Tail
│ ├─ PhotoSaver.kt MediaStore 保存(DCIM/MAG160C,无权限也可写自有文件)
│ ├─ Mp4Recorder.kt MP4录像(Surface 输入 H.264MediaCodec+MediaMuxer
│ └─ PdfReport.kt PDF 巡检报告(PdfDocument
├─ usb/
│ ├─ UsbTransport.kt UsbManager 枚举(VID 0x833C)/权限/claim/EP
│ ├─ MagProtocol.kt 命令码 0x6BB6B6xx、响应 0x5BB5B5xx
│ └─ IrSession.kt 连接/启动流/读线程→FrameStream→RenderPipeline→回调;FFC回调
├─ core/
│ ├─ OfficialTables.kt 自动生成:PALETTE256_ARGB(官方铁虹)/T2E(646)/T2E274/E2T
│ ├─ Palettes.kt 12调色板(铁虹=官方,其余标准曲线近似)
│ ├─ RenderPipeline.kt ★官方渲染管线 Kotlin 移植(与C参考逐字节一致)
│ ├─ TempMath.kt 温度换算(counts→毫度,T2E)
│ └─ FrameStream.kt 流帧重组(0x1BB1B11B 搜索)
└─ task/TaskParser.kt 任务巡检 sqlite/xml 解析
assets/mag160c.ddt 官方 DDT 标定文件(1.8MB,渲染必需)
res/drawable/*.xml 自绘矢量图标(双弧圆等可靠几何图形)
```
## 4. 核心知识(必须知道)
### 4.1 渲染管线(已逐字节验证)
- `core/RenderPipeline.kt` 移植自 `csdk/src/mag160c_render.c`(官方管线,像素级验证)。
- 输入:USB 帧(0x38 头 + 38400B u16 小端 + 尾部);输出 320×240 ARGB。
- FFC 状态机:暖机20帧内暂停;第75帧强制FFCFFC(0)→5隐藏→FFC(1)→4参考帧→4隐藏→正常。
- **移植陷阱(已解决,改代码时务必保持)**:
1. C 的 32 位无符号回绕:所有 `u32()` 掩码必须保留(cdf*denom、0xffc0000-iv7*0x40000、(v-lo)*S 等)。
2. lutRebuild 里 `u12` 的基址是常量 `iv7*0x10+0x10`**不是**链式 u21v。
3. ByteArray 存 255=-1:判 0xFF 必须 `(b.toInt() and 0xFF)`。
4. 暖机窗口检查在 ffc_step **之前**(顺序不能改)。
- 验证方式:`android/app/src/test/.../RenderPipelineTest.kt`JVM 差分测试,参照 `analysis/render_offline.c` 的 C 输出,60帧序列与官方DDT)。
### 4.2 USB 协议(analysis/protocol_spec.md 全文)
- 命令:普通 4 字节 magicFFC 8 字节 {magic, param}。
- 初始化:66b→66c→66f4B)→FFC(0)×2→300ms→START(73);停止 STOP(74)。
- 帧流:EP 0x81 bulk0x1BB1B11B 头 + 0x1C 起像素 + 尾部 shutter 于 +0x24。
- 相机信息块 0x5BB5B55B+0x00 pid、+0x08 序列号、+0x10 宽、+0x14 高、+0x18 fps。
### 4.3 实时画面渲染(最终约定:构图钉死竖屏框架)
- **Activity 锁定竖屏**manifest `screenOrientation="portrait"`):屏幕相对手机框架
永不旋转。顶栏、热像区域、底栏的**绝对位置**永远贴着手机竖屏的物理顶边(挖孔侧)、
中间、物理底边;手机怎么物理旋转,构图都不动(用户明确要求:屏显方向必须始终
与热像镜头实际方向对应,横屏后显示区域跟着屏幕转是错的)。**不要改回 fullSensor。**
- **图标/文字按物理持机朝向补偿旋转**(第六轮定稿):`ui/DeviceOrientation.kt` 用
加速度计得出手机相对竖屏的顺时针物理转角 φ(0/90/180/270,带滞回;竖屏锁定下
Display.rotation 恒 0 不可用)。顶栏/底部导航条目 `graphicsLayer rotationZ=-φ`
原位预旋转;渲染器 OSD 文字 `canvas.rotate(-φ)` 绕锚点旋转(标记圆点、色标条
几何仍钉死在图像上)。对话框与其他页签暂不补偿。
- **加速度计符号约定(第七轮教训,勿改回)**:真机 TYPE_ACCELEROMETER 静止读数
指向世界上方(竖屏正持 y=+9.81);模拟器虚拟传感器是反的重力约定(y=-9.81)。
DeviceOrientation 映射按**真机约定**写,模拟器测试须用反号值驱动
(φ=0→`adb emu sensor set acceleration 0:9.81:0`)。
- **图像恒 90°CW 绘制为 3:4 竖向**,填满可用区域(顶栏下~底导航上)。
- **文字/图标恒屏幕水平**(可读);标记文字位置自动跟随(probeToScreen 固定 90° 映射:
`fx=1-sy/120, fy=sx/160`)。
- 顶栏=4 个相机控制项(FFC / 变倍×N / 追踪·开 / 调色板+名称),恒在顶部并带小字
标签,`safeDrawing` 顶部inset 适配挖孔屏。
- 底部(仅实时页)导航栏上方为**相机快门区**:相册快捷入口 / 大快门拍照 /
录像-停止;快门区高度并入 `vm.uiBottomPx`= 导航高+快门区高)。
- 顶栏高度经 `onSizeChanged`→`vm.uiTopPx`;底导航高度经 `UiInsets.navPx`→`vm.uiBottomPx`,渲染器据此留白。
- 演示模式:无设备时用真实管线+DDT渲染合成帧(热块场景),demo温度用局部线性显示映射
(真机温度走管线数学;绝对温度标定待真机)。
### 4.4 温度
- 温度 = 毫度 int(÷1000 = ℃)。`counts_to_temp_mc`(NUC域→T2E逆映射)用于探针/OSD,
在真机上需按 DDT 标定核对绝对值(待办)。
- 离线 MDT 温度解码(ConvertResponse2Temperature + 标定参数)未实现(待真机文件对照)。
## 5. 构建 / 测试 / 提交
```powershell
# 构建(无 --daemon 单次,约1分钟)
cd C:\Project\MAG160C\android
$env:JAVA_HOME="C:\Program Files\Zulu\zulu-21"
& "C:\Tools\gradle-8.14.3\bin\gradle.bat" :app:assembleDebug --no-daemon
# 单元测试(管线差分验证)
& "C:\Tools\gradle-8.14.3\bin\gradle.bat" test --no-daemon
# 产物
Copy-Item app\build\outputs\apk\debug\app-debug.apk C:\Project\MAG160C\build-artifacts\mag160c-app-debug.apk
# 提交(每里程碑一个 commit,不 push
git -C C:\Project\MAG160C add -A
git -C C:\Project\MAG160C commit -m "android: ..."
```
## 6. 严重教训(反复踩坑,务必遵守)
1. **编码**:所有源码必须 UTF-8。本机默认编码是 GBK!
- **python 读写文件必须显式 `encoding='utf-8'`**`open(p,'w')` 裸写会变 GBK→编译出的中文全乱码)。
- 检测/修复脚本:`C:\Users\ZXC\AppData\Local\Temp\opencode\fix_encoding.py`、
`check_mojibake.py`。改完必须跑一遍确认全 OK,再构建。
- gradle.properties 已有 `-Dfile.encoding=UTF-8`gradle + kotlin daemon)。
2. **图标**:手写 pathData 的弧线易坏,一律用双弧圆 `M x,y a r,r 0 1,0 2r,0 a r,r 0 1,0 -2r,0z` + 直线/矩形。
3. **SurfaceView 与旋转**:不要在横竖屏间切换不同布局分支(SurfaceView 会被销毁且 surface 不重建)。
用"稳定单分支 + 覆盖层"布局(当前实现即如此)。
4. **写代码要谨慎**:本会话多次因急于成稿写出语法错误/残留死代码。每写一个文件后立即编译。
## 7. 当前状态与待办
**已完成**:核心管线移植(字节级验证)、USB 层、实时画面(竖图固定+顶栏控制+底栏导航+
圆圈标记+大色标+演示模式)、拍照 MDT 容器→MediaStore、MP4 录像、媒体库、离线分析查看器、
PDF 报告、任务解析、设置页、Android16 沉浸(状态栏隐藏+挖孔适配+fullSensor)、乱码多次修复。
**待办**(记录在 session_state.md):
1. 真机 USB 实测:温度绝对值标定、FFC/录像/MDT 保存端到端。
2. 网络互连远程预览(用户需求:UDP 自动发现 + 手动内网 IP 连另一台插热像仪的手机)。
3. 离线 MDT 温度解码(ConvertResponse2Temperature + 标定参数)。
4. 厂商 12 调色板精确提取(运行时抓取或逆向)。
5. 可见光 PIP 融合、云模块(预留结构)。
## 8. 文件路径速查
- 心跳/状态:`docs/android_app/session_state.md`
- 逆向功能总表:`docs/android_app/reverse_apk_features.md`
- USB 协议/温度算法:`analysis/protocol_spec.md`
- C 参考实现(Kotlin 管线的蓝本):`csdk/src/mag160c_render.c`、`mag160c_temp.c`、`mag160c_ir.c`
- PC 参考工具:`analysis/render_offline.c`、`analysis/gen_kotlin_tables.py`
- 预览截图:`analysis/preview/preview_*.png`
- APK 产物:`build-artifacts/mag160c-app-debug.apk`