# 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 16(API 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/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.264,MediaCodec+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帧强制FFC;FFC(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 字节 magic;FFC 8 字节 {magic, param}。 - 初始化:66b→66c→66f(4B)→FFC(0)×2→300ms→START(73);停止 STOP(74)。 - 帧流:EP 0x81 bulk,0x1BB1B11B 头 + 0x1C 起像素 + 尾部 shutter 于 +0x24。 - 相机信息块 0x5BB5B55B:+0x00 pid、+0x08 序列号、+0x10 宽、+0x14 高、+0x18 fps。 ### 4.3 实时画面渲染(最终约定:构图钉死竖屏框架) - **Activity 锁定竖屏**(manifest `screenOrientation="portrait"`):屏幕相对手机框架 永不旋转。顶栏、热像区域、底栏的**绝对位置**永远贴着手机竖屏的物理顶边(挖孔侧)、 中间、物理底边;手机怎么物理旋转,构图都不动(用户明确要求:屏显方向必须始终 与热像镜头实际方向对应,横屏后显示区域跟着屏幕转是错的)。**不要改回 fullSensor。** - **图像恒 90°CW 绘制为 3:4 竖向**,填满可用区域(顶栏下~底导航上)。 - **文字/图标恒屏幕水平**(可读);标记文字位置自动跟随(probeToScreen 固定 90° 映射: `fx=1-sy/120, fy=sx/160`)。 - 顶栏=控制项(FFC/变倍/追踪·开/调色板/拍照/录像/调色板名),恒在顶部,`safeDrawing` 顶部inset 适配挖孔屏。 - 底栏=导航恒在底部。 - 顶栏高度经 `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`