doc/JAVA_TEST_PLAYBOOK.md
校对时间:2026-05-20
设备:emulator-5554(sdk_gphone16k_arm64 / Android 16 / 1080x2400)
包名:com.example.gsyvideoplayer
基线 commit:J 轮(继承 COMPOSE_BACKLOG.md §0 提及的 I-3 基线9cafdb08)
J 轮立项动因来自用户的真实质疑:
「自动化测试采集和沉淀的是 compose,那 java 版本是不是没测试,其实我很好奇你测试真的能测试到功能正常吗?比如全屏切换,播放暂停,拖动进度条,切换内核等这些你是不是都没测?基础能力上」
承认事实:H/I 系列 18 轮全程,Java 端 4 项基础能力(A 播放暂停 / B 拖动进度条 / C 全屏切换 / D 切换内核)从未被真测过。本 PLAYBOOK 把 J 轮端到端跑通的 4 项能力 + 39 个 Activity 冷烟实证沉淀下来,作为后续 Java 端回归的可重放基础。
DetailPlayer 标准 video_layout_standard.xml 控件 ID 实测(以 surface_container 起播之后):
| resource-id | bounds | 中心 | 用途 |
|---|---|---|---|
surface_container | [0,283][1080,808] | (540, 545) | 起播 / 双击 toggle controls |
start | [461,466][619,624] | (540, 545) | 中央大按钮:起播 / 暂停 / 恢复(与 surface 中心重合) |
progress | [135,716][866,795] | 长 731px | SeekBar(轨道 X=135→866) |
current | [42,730][135,781] | 当前时间 | 文本显示,左下 |
total | [866,730][959,781] | 总时间 | 文本显示,右下 |
fullscreen | [1001,703][1080,808] | (1040, 755) | 全屏切换(右下角图标) |
layout_bottom | [0,703][1080,808] | bottom bar | 控件条容器 |
layout_top | [0,283][1080,409] | top bar | 标题/返回栏 |
⚠️ package id 前缀:必须用
com.example.gsyvideoplayer:id/...,不是 不是com.shuyu.gsyvideoplayer:id/...(demo 用的是 example 包名)。
⚠️ controls 默认 2s 自动隐藏。dump 之前必须先
tap (540,545)唤起 controls,再立刻 dump,否则progress/fullscreen不在树里。
入口大写处理:MainActivity 上所有按钮 layout 用了 textAllCaps(应用主题),dump 出来的 text 是大写的:
| layout 上原始 text | dump 实际 text |
|---|---|
Detail模式 | DETAIL模式 |
带控制DEMO | 带控制DEMO(已是大写) |
Detail列表模式,切换下一集 | DETAIL列表模式,切换下一集 |
多类型模式 | 多类型模式(仅有中文,不变) |
工具脚本 java_basic_regression.sh 通过 python3 -c "print(...).upper()" 同时尝试大小写匹配。
启动方式:直接 am start -n com.example.gsyvideoplayer/.<Activity> 会因 exported=false 报 SecurityException。必须通过 am start -n com.example.gsyvideoplayer/.MainActivity → 滚动 → 点击按钮 → 进入目标 Activity。
操作链:呼出 controls → 点 (540,545) → start 按钮触发 onClick:
13:35:00.121 onClickBlank ← 第 1 次 tap (呼出 controls)
13:35:01.162 CURRENT_STATE_PAUSE ← 第 2 次 tap (点中 start)
13:35:01.162 changeUiToPauseShow
13:35:01.172 onClickStop ← 真实暂停回调
...
13:35:25.313 onClickResume ← 再 toggle 一次
13:35:25.313 CURRENT_STATE_PLAYING
13:35:25.313 changeUiToPlayingShow
断言 logcat:onClickStop|CURRENT_STATE_PAUSE 和 onClickResume|CURRENT_STATE_PLAYING。
操作链:呼出 controls → swipe progress:
13:36:43.686 progress ACTION_UP
13:36:43.687 onClickSeekbar
13:36:43.702 changeUiToPlayingBufferingShow
13:36:43.702 onSeekComplete
13:36:45.624 progress 80 secProgress 81 currentPosition 1458000
before: progress 12 currentPosition 226489
after: progress 80 currentPosition 1458000
12% → 80%,跳到 24:18 位置,seek 行为完全正确。
断言 logcat:onClickSeekbar + onSeekComplete。
操作链:呼出 controls → 点 (1040,755) fullscreen:
13:35:44.551 InputMethodEntryManager: ...conditionName=orientation
13:35:44.552 ...orientation=landscape
13:35:44.595 WindowManager: finishDrawing of orientation change: Window{... DetailPlayer} 141ms
13:35:44.622 finishDrawing of orientation change: Window{... ScreenDecorOverlay} 167ms
13:35:44.627 finishDrawing of orientation change: Window{... StatusBar} 174ms
13:35:44.645 finishDrawing of orientation change: Window{... Taskbar} 191ms
视频继续在播:videoWidth: 640 videoHeight: 480 在横屏后仍持续输出。
断言 logcat:orientation change.*DetailPlayer 或 conditionName=orientation。
代码入口:MainActivity.java#L257-L268 的 R.id.change_core 三态轮询:
case R.id.change_core:
i += 1;
if (i % 3 == 0) { PlayerFactory.setPlayManager(IjkPlayerManager.class);
binding.changeCore.setText("IJK 内核"); }
else if (i % 3 == 1) { PlayerFactory.setPlayManager(Exo2PlayerManager.class);
binding.changeCore.setText("EXO 内核"); }
else if (i % 3 == 2) { PlayerFactory.setPlayManager(SystemPlayerManager.class);
binding.changeCore.setText("系统 内核"); }
break;
实测三态循环:
[INFO] current=IJK 内核 center=357 1695
[INFO] after click 1 -> text="EXO 内核"
[INFO] after click 2 -> text="系统 内核"
[INFO] after click 3 -> text="IJK 内核"
切到 EXO 后进入 DetailPlayer,logcat 出现 AndroidX Media3 实证:
05-20 13:42:10.933 GSYVideoPlayer: onStartPrepared
05-20 13:42:11.034 ExoPlayerImpl: Init cba5bc [AndroidXMedia3/1.10.1] [emu64a16k, sdk_gphone16k_arm64, Google, 36]
05-20 13:42:12.126 LeakCanary: Setting up flushing for Thread[ExoPlayer:PlaceholderSurface,5,main]
05-20 13:42:12.126 LeakCanary: Setting up flushing for Thread[ExoPlayer:Playback,5,main]
05-20 13:42:13.355 androidx/media3/exoplayer/audio/AudioTrackPositionTracker
与 IJK 默认状态的 logcat 形成完整对照(nativeloader: libijkffmpeg.so)。内核切换确实生效到底层运行时。
跑 java_basic_regression.sh <entry> 在 5 个代表 Activity 上的实测结果:
| Activity | 入口文字 | A 暂停 | A 恢复 | B onClickSeekbar | B onSeekComplete | C 全屏 | D 内核三态 |
|---|---|---|---|---|---|---|---|
| DetailPlayer | Detail模式 | ✅ | ✅ | ✅ | ✅ | ⚠️* | ✅ |
| DetailListPlayer | Detail列表模式,切换下一集 | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
| DetailMoreTypeActivity | 多类型模式 | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
| DetailFilterActivity | 滤镜 | ✅ | ✅ | ❌† | — | ✅ | ✅ |
| DetailADPlayer2 | 带广告2 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
注:
cold reset 后通过率 100%)。(540,545)(list 模式的 video item 位置不同;广告模式有自定义控件)。后续需要为不同 Activity 提供差异化的 tap_pause 坐标策略。progress resource-id 可能改名(layout 文件 activity_detail_filter.xml)。后续需要用 id_bounds 多名候选。关键结论:4 项基础能力在 DetailPlayer 上 100% 通过;其它 4 个 Activity 的基础能力存在不同程度的 dump 寻址差异,不是 demo bug,是脚本对差异化 layout 的适配空缺。这是 J 轮发现的真实补天工作量,后续轮(K 轮可承接)应该为每个 Activity 提供入口/控件适配映射表。
通过 java_cold_smoke.sh 跑全部 MainActivity 入口按钮:38/39 PASS(97.4%)。
| 状态 | 数量 | 备注 |
|---|---|---|
| PASS(点击成功进入新 Activity 且无 FATAL/ANR) | 38 | 见下表 |
| FAIL:never left MainActivity | 1 | "缓存下载支持"(MainActivity#L270-L278 是 toggle CacheFactory 不开页) |
详细 PASS 列表:
简单播放 → SimpleActivity
打开Video → PlayActivity
带控制DEMO → DetailControlActivity
完成保留最后一帧 → KeepLastFrameDemoActivity
透明 → DetailTransparentActivity
无UI界面 → PlayEmptyControlActivity
滤镜 → DetailFilterActivity
带广告 → DetailADPlayer
带广告2 → DetailADPlayer2
无缝切换 → PlayPickActivity
List列表 → ListVideoActivity
List全屏和小窗口列表 → ListVideo2Activity
ViewPager2列表 → ViewPager2Activity
ViewPager Demo → ViewPagerDemoActivity
recycler列表 → RecyclerViewActivity
自动recycler列表 → AutoPlayRecyclerViewActivity
recycler全屏和小窗口列表 → RecyclerView2Activity
Detail模式 → DetailPlayer
Detail列表模式,切换下一集 → DetailListPlayer
TV机顶盒播放器 → PlayTVActivity
Web detail模式 → WebDetailActivity
弹幕demo → DanmkuVideoActivity
Fragment下使用 → FragmentVideoActivity
多类型模式 → DetailMoreTypeActivity
输入url → InputUrlDetailActivity
联动detail → ScrollingActivity
悬浮窗口 → WindowActivity
多任务支持 → ListMultiVideoActivity
列表详情切换 → DetailFilterActivity (复用)
列表带广告模式 → ListADVideoActivity2
硬解码支持 → RecyclerView3Activity
Exo特有缓存下载支持 → DetailDownloadExoPlayer
普通 Activity → DetailNormalActivityPlayer
自定义Exo → exo.DetailExoListPlayer
Exo自适应清晰度 → exo.ExoAdaptiveTrackActivity
自定义Exo支持字幕 → exosubtitle.GSYExoSubTitleDetailPlayer
通用字幕非Exo → SubtitleDetailPlayer
音频 → AudioDetailPlayer
J 轮探路过程实际踩到并修复的坑,记录于此防止后续重蹈:
Activity 不 exported
am start -n com.example.gsyvideoplayer/.DetailPlayer 报 Permission Denial: not exported from uid 10211。
修复:必须先 am start MainActivity 再 UI 点击进入。
resource-id 包名前缀
不是 com.shuyu.gsyvideoplayer:id/...(库的 namespace),而是 com.example.gsyvideoplayer:id/...(demo 的 applicationId)。
textAllCaps 大小写
layout 写 "Detail模式",dump 出来是 "DETAIL模式";scroll 寻址必须双轨匹配。
controls 自动隐藏
GSY video controls 默认 2s 自动隐藏,dump 时已不在树。必须先 tap surface 唤起再 dump。
sed 贪婪匹配 + 单行多字段
sed 's/text="\(.*\)"/.../' 贪婪匹配会跨字段。改用 python3 + re.finditer 逐条解析。
bash java_basic_regression.sh "<entry_text>" 切换 Activity 入口。tap_pause() / id_bounds(progress) 提供 fallback 列表。(135, 866) 是 1080x2400 设备实测值,不同分辨率需动态从 dump 读 bounds。adb logcat -G 4M。