| 项目 | 值 |
|---|---|
| 服务器 | Linux 5.10.134-19.2.al8.x86_64(2G 内存,无模拟器/真机) |
| JDK | OpenJDK 17(JAVA_HOME=/usr/lib/jvm/java-17-openjdk) |
| Gradle | 8.5(/opt/gradle-8.5/bin/gradle,--no-daemon) |
| Android SDK | /opt/android-sdk,build-tools 34.0.0(aapt) |
| 测试框架 | JUnit 4.13.2(testImplementation,本次新增) |
| 限制 | 无法做真机/模拟器运行时测试,运行时行为未覆盖 |
| 项目 | 值 |
|---|---|
| 服务器 | Linux 5.10.134-19.2.al8.x86_64(2G 内存,无模拟器/真机) |
| JDK | OpenJDK 17(JAVA_HOME=/usr/lib/jvm/java-17-openjdk) |
| Gradle | 8.5(/opt/gradle-8.5/bin/gradle,--no-daemon) |
| Android SDK | /opt/android-sdk,build-tools 34.0.0(aapt) |
| 测试框架 | JUnit 4.13.2(testImplementation,本次新增) |
| 限制 | 无法做真机/模拟器运行时测试,运行时行为未覆盖 |
命令:`/opt/gradle-8.5/bin/gradle --no-daemon testDebugUnitTest --console=plain`
结果:**BUILD SUCCESSFUL in 1m 58s**,9/9 通过(failures=0 errors=0 skipped=0)。
说明:任务书中的方法签名为 `advise(signal: SceneSignal)`,实际生产代码为 `DecisionEngine.decide(signal)`,且 `SceneSignal` 实际字段为 `faceBrightRatio`/`brightness`/`histogramBins`/`tiltAngleDeg`(非 faceBright/hist/tiltDeg)。测试按真实代码编写。`UpdateChecker.isNewer` 为 private,按要求未改生产代码,使用 Kotlin 反射调用。
| 用例 | 预期 | 实际 | 结论 |
|---|---|---|---|
| DecisionEngine 逆光 → evBias | -0.7 | -0.7 | PASS |
| DecisionEngine 低光 → evBias>0 且有 ISO 建议 | >0,含 "ISO" | +0.3,"ISO 拉到 800~1600…" | PASS |
| DecisionEngine 顺光 → evBias | 0 | 0 | PASS |
| DecisionEngine 正常 → evBias | 0 | 0 | PASS |
| DecisionEngine 四场景文案非空 | reason/isoSuggestion 非空 | 均非空 | PASS |
| isNewer("0.1.2","0.1.1") | true | true | PASS |
| isNewer("1.0","0.9.9") | true | true | PASS |
| isNewer("0.1.2","0.1.2") | false | false | PASS |
| isNewer("0.10.0","0.9.0") | true(数值比较) | true | PASS |
| 用例 | 预期 | 实际 | 结论 |
|---|---|---|---|
| DecisionEngine 逆光 → evBias | -0.7 | -0.7 | PASS |
| DecisionEngine 低光 → evBias>0 且有 ISO 建议 | >0,含 "ISO" | +0.3,"ISO 拉到 800~1600…" | PASS |
| DecisionEngine 顺光 → evBias | 0 | 0 | PASS |
| DecisionEngine 正常 → evBias | 0 | 0 | PASS |
| DecisionEngine 四场景文案非空 | reason/isoSuggestion 非空 | 均非空 | PASS |
| isNewer("0.1.2","0.1.1") | true | true | PASS |
| isNewer("1.0","0.9.9") | true | true | PASS |
| isNewer("0.1.2","0.1.2") | false | false | PASS |
| isNewer("0.10.0","0.9.0") | true(数值比较) | true | PASS |
测试文件(本次新建):
编译期发现(非阻断):`CrashLogger.kt:20` 警告 "Variable 'dir' is never used"。
命令:`aapt dump badging` / `unzip -l`
| 检查项 | 预期 | 实际 | 结论 |
|---|---|---|---|
| 包名 | cn.sunearthl2.kkcamera | cn.sunearthl2.kkcamera | PASS |
| versionName | 0.1.2 | 0.1.2 | PASS |
| versionCode | 3 | 3 | PASS |
| minSdk | 29 | 29 | PASS |
| targetSdk | 34 | 34 | PASS |
| ABI 列表 | arm64-v8a, armeabi-v7a | lib/ 下仅含 arm64-v8a、armeabi-v7a(4 个 .so) | PASS |
| 权限 | 仅 CAMERA + INTERNET | 源 Manifest 只有 CAMERA+INTERNET,但合并后 APK 额外引入 ACCESS_NETWORK_STATE、WAKE_LOCK、RECEIVE_BOOT_COMPLETED、FOREGROUND_SERVICE、DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION(来自 ML Kit 等三方库 manifest 合并) | WARN |
| AndroidManifest.xml 在包内 | 存在 | 存在(17792 字节) | PASS |
| res/xml/file_paths.xml 在包内 | 存在 | 存在(300 字节) | PASS |
| aapt 解析 | 无错误 | `android:icon` 引用 `@android:drawable/ic_menu_camera` 导致 aapt 报 "attribute value reference does not exist"(框架图标,badging 主体信息仍可读) | WARN |
| 检查项 | 预期 | 实际 | 结论 |
|---|---|---|---|
| 包名 | cn.sunearthl2.kkcamera | cn.sunearthl2.kkcamera | PASS |
| versionName | 0.1.2 | 0.1.2 | PASS |
| versionCode | 3 | 3 | PASS |
| minSdk | 29 | 29 | PASS |
| targetSdk | 34 | 34 | PASS |
| ABI 列表 | arm64-v8a, armeabi-v7a | lib/ 下仅含 arm64-v8a、armeabi-v7a(4 个 .so) | PASS |
| 权限 | 仅 CAMERA + INTERNET | 源 Manifest 只有 CAMERA+INTERNET,但合并后 APK 额外引入 ACCESS_NETWORK_STATE、WAKE_LOCK、RECEIVE_BOOT_COMPLETED、FOREGROUND_SERVICE、DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION(来自 ML Kit 等三方库 manifest 合并) | WARN |
| AndroidManifest.xml 在包内 | 存在 | 存在(17792 字节) | PASS |
| res/xml/file_paths.xml 在包内 | 存在 | 存在(300 字节) | PASS |
| aapt 解析 | 无错误 | `android:icon` 引用 `@android:drawable/ic_menu_camera` 导致 aapt 报 "attribute value reference does not exist"(框架图标,badging 主体信息仍可读) | WARN |
| 检查项 | 预期 | 实际 | 结论 |
|---|---|---|---|
| GET version.json | HTTP 200 | HTTP/2 200(nginx/1.20.1) | PASS |
| GET APK | HTTP 200 | HTTP/2 200,content-length 61665659 | PASS |
| fileSize 一致性 | 与实际字节数一致 | version.json fileSize=61665659 = 服务器本地 `stat` 61665659 = content-length 61665659 | PASS |
| downloadUrl 有效性 | 域名路径可下载 | `https://kk.sunearthl2.cn/apk/KKCamera-v0.1.2-debug.apk` 返回 200 | PASS |
| isNewer(0.1.2, 已装0.1.1) 模拟 | true → 提示更新 | python3 复刻同逻辑:installed=0.1.1 → True(会提示更新);installed=0.1.2 → False(不提示) | PASS |
| version.json Content-Type | application/json | `application/vnd.android.package-archive`(nginx 按扩展名/路径误配) | WARN |
| 检查项 | 预期 | 实际 | 结论 |
|---|---|---|---|
| GET version.json | HTTP 200 | HTTP/2 200(nginx/1.20.1) | PASS |
| GET APK | HTTP 200 | HTTP/2 200,content-length 61665659 | PASS |
| fileSize 一致性 | 与实际字节数一致 | version.json fileSize=61665659 = 服务器本地 `stat` 61665659 = content-length 61665659 | PASS |
| downloadUrl 有效性 | 域名路径可下载 | `https://kk.sunearthl2.cn/apk/KKCamera-v0.1.2-debug.apk` 返回 200 | PASS |
| isNewer(0.1.2, 已装0.1.1) 模拟 | true → 提示更新 | python3 复刻同逻辑:installed=0.1.1 → True(会提示更新);installed=0.1.2 → False(不提示) | PASS |
| version.json Content-Type | application/json | `application/vnd.android.package-archive`(nginx 按扩展名/路径误配) | WARN |
| 检查项 | 预期 | 实际 | 结论 |
|---|---|---|---|
| CrashLogger 在 MainActivity.onCreate 初始化 | 是 | MainActivity.kt:119 `CrashLogger.init(this)`(onCreate 首行,setContentView 之前) | PASS |
| FileProvider 注册 + authorities | `${applicationId}.fileprovider` | AndroidManifest.xml:18-26,authorities=`${applicationId}.fileprovider`;installApk 使用 `${activity.packageName}.fileprovider`,一致 | PASS |
| DownloadManager 路径 vs file_paths.xml | external-files-path Download/ 匹配 | `setDestinationInExternalFilesDir(activity, DIRECTORY_DOWNLOADS, fileName)` ↔ file_paths.xml `external-files-path path="Download/"`,一致 | PASS |
| ML Kit pose-detection 依赖 | 已声明 | `com.google.mlkit:pose-detection:18.0.0-beta5`(注意为 beta 版) | PASS |
| gradle.properties 低内存四项 | -Xmx1024m / daemon=false / kotlin 800m / parallel=false | 四项全部存在(另含 useAndroidX、nonTransitiveRClass) | PASS |
| 检查项 | 预期 | 实际 | 结论 |
|---|---|---|---|
| CrashLogger 在 MainActivity.onCreate 初始化 | 是 | MainActivity.kt:119 `CrashLogger.init(this)`(onCreate 首行,setContentView 之前) | PASS |
| FileProvider 注册 + authorities | `${applicationId}.fileprovider` | AndroidManifest.xml:18-26,authorities=`${applicationId}.fileprovider`;installApk 使用 `${activity.packageName}.fileprovider`,一致 | PASS |
| DownloadManager 路径 vs file_paths.xml | external-files-path Download/ 匹配 | `setDestinationInExternalFilesDir(activity, DIRECTORY_DOWNLOADS, fileName)` ↔ file_paths.xml `external-files-path path="Download/"`,一致 | PASS |
| ML Kit pose-detection 依赖 | 已声明 | `com.google.mlkit:pose-detection:18.0.0-beta5`(注意为 beta 版) | PASS |
| gradle.properties 低内存四项 | -Xmx1024m / daemon=false / kotlin 800m / parallel=false | 四项全部存在(另含 useAndroidX、nonTransitiveRClass) | PASS |
| 编号 | 严重度 | 描述 |
|---|---|---|
| P1 | WARN | APK 合并 manifest 引入了 5 项额外权限(ACCESS_NETWORK_STATE、WAKE_LOCK、RECEIVE_BOOT_COMPLETED、FOREGROUND_SERVICE、自定义签名权限),全部来自 ML Kit 等三方库,源码只申请 CAMERA+INTERNET。功能影响低,但与"最小权限"预期不符,可考虑 manifest merger `tools:node="remove"` 收敛 |
| P2 | WARN | 应用图标使用框架内置 `@android:drawable/ic_menu_camera`,aapt badging 报 icon 属性引用错误;不同 ROM 上显示可能不一致,建议换自有 mipmap 图标 |
| P3 | WARN | `https://kk.sunearthl2.cn/apk/version.json` 响应 Content-Type 为 `application/vnd.android.package-archive` 而非 `application/json`(nginx 对 /apk/ 路径统一配置了 APK 的 MIME)。当前代码手动解析 JSON 不受影响,仅影响规范性与缓存/中间件行为 |
| P4 | INFO | ML Kit pose-detection 使用 beta 版本 18.0.0-beta5,建议关注正式版发布后升级 |
| P5 | INFO | 任务书与代码签名不一致:DecisionEngine 方法为 `decide` 而非 `advise`,SceneSignal 字段命名亦有出入(文档/任务描述需同步) |
| P6 | INFO | CrashLogger.kt:20 存在未使用变量 `dir` 编译警告;version.json 的 content-length 显示 202 字节与实际 JSON 体一致 |
| 编号 | 严重度 | 描述 |
|---|---|---|
| P1 | WARN | APK 合并 manifest 引入了 5 项额外权限(ACCESS_NETWORK_STATE、WAKE_LOCK、RECEIVE_BOOT_COMPLETED、FOREGROUND_SERVICE、自定义签名权限),全部来自 ML Kit 等三方库,源码只申请 CAMERA+INTERNET。功能影响低,但与"最小权限"预期不符,可考虑 manifest merger `tools:node="remove"` 收敛 |
| P2 | WARN | 应用图标使用框架内置 `@android:drawable/ic_menu_camera`,aapt badging 报 icon 属性引用错误;不同 ROM 上显示可能不一致,建议换自有 mipmap 图标 |
| P3 | WARN | `https://kk.sunearthl2.cn/apk/version.json` 响应 Content-Type 为 `application/vnd.android.package-archive` 而非 `application/json`(nginx 对 /apk/ 路径统一配置了 APK 的 MIME)。当前代码手动解析 JSON 不受影响,仅影响规范性与缓存/中间件行为 |
| P4 | INFO | ML Kit pose-detection 使用 beta 版本 18.0.0-beta5,建议关注正式版发布后升级 |
| P5 | INFO | 任务书与代码签名不一致:DecisionEngine 方法为 `decide` 而非 `advise`,SceneSignal 字段命名亦有出入(文档/任务描述需同步) |
| P6 | INFO | CrashLogger.kt:20 存在未使用变量 `dir` 编译警告;version.json 的 content-length 显示 202 字节与实际 JSON 体一致 |
**通过(可发布)**。核心逻辑(决策引擎四场景规则、版本号数值比较)、更新链路(version.json 可达、fileSize 与实际字节数完全一致、下载 URL 有效、版本判定正确)、关键静态配置(FileProvider 注册与路径匹配、CrashLogger 初始化、低内存构建配置)全部符合预期,JVM 单元测试 9/9 通过。发现 4 项 WARN(合并权限偏多、图标引用、MIME 配置、beta 依赖)与若干 INFO,均不阻塞发布,建议下个迭代处理。运行时行为(相机、ML Kit 推理、DownloadManager 实际下载安装)因无模拟器/真机未覆盖,建议在真机上补充冒烟测试。