#Requires -Version 5.1 <# .SYNOPSIS 清听真机自检:安装最新 debug APK,冷启动,抓 logcat,校验 libmpv 是否只有单实例。 .DESCRIPTION 判定依据: - 冷启动后 "PlaybackService 单例创建" 只出现一次; - 日志里出现过的 serviceId 只有 1 个; - media_kit Player 的 hashCode 只有 1 个。 .EXAMPLE powershell -ExecutionPolicy Bypass -File tools\device-check.ps1 powershell -ExecutionPolicy Bypass -File tools\device-check.ps1 -DeviceId 192.168.3.2:36569 #> [CmdletBinding()] param( [string]$DeviceId = '', [string]$Apk = '', [int]$WaitSeconds = 12, # 默认校验 debug 包(_dev 后缀),与 release 包并存安装 [string]$Package = 'com.lxh.qingting_player.dev' ) $ErrorActionPreference = 'Continue' # adb 会往 stderr 写正常提示,用 Stop 会误判 $root = Split-Path -Parent $PSScriptRoot if (-not $Apk) { $Apk = Join-Path $root 'build\app\outputs\flutter-apk\app-debug.apk' } $pkg = $Package function Write-Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } function Write-Ok($msg) { Write-Host " OK $msg" -ForegroundColor Green } function Write-Bad($msg) { Write-Host " !! $msg" -ForegroundColor Red } if (-not (Get-Command adb -ErrorAction SilentlyContinue)) { throw '未找到 adb,请把 platform-tools 加入 PATH' } if (-not (Test-Path $Apk)) { throw "APK 不存在:$Apk(先执行 gradlew assembleDebug)" } # ---------- 选择设备 ---------- # 同一台手机可能同时存在多条 adb 通道(如无线调试 + mDNS),按 serialno 去重 $devices = @(adb devices | Select-Object -Skip 1 | Where-Object { $_ -match '\sdevice$' } | ForEach-Object { ($_ -split '\s+')[0] }) if ($DeviceId) { if ($devices -notcontains $DeviceId) { throw "设备 $DeviceId 不在线,当前:$($devices -join ', ')" } } elseif ($devices.Count -eq 0) { throw '没有在线设备,请先连接手机并授权 USB/无线调试' } elseif ($devices.Count -eq 1) { $DeviceId = $devices[0] } else { $bySerial = @{} foreach ($entry in $devices) { $serial = (& adb -s $entry shell getprop ro.serialno 2>$null | Out-String).Trim() if ($serial) { $bySerial[$serial] = $entry } } if ($bySerial.Count -eq 1) { $DeviceId = @($bySerial.Values)[0] Write-Host " (同一设备的多条 adb 通道,使用 $DeviceId)" -ForegroundColor DarkGray } else { throw "检测到多台设备,请用 -DeviceId 指定:$($devices -join ', ')" } } $adb = { param($args) & adb -s $DeviceId @args } $model = (& adb -s $DeviceId shell getprop ro.product.model).Trim() $android = (& adb -s $DeviceId shell getprop ro.build.version.release).Trim() Write-Step "设备 $model (Android $android) [$DeviceId]" # ---------- 安装 ---------- Write-Step "安装 $([IO.Path]::GetFileName($Apk))" $install = & adb -s $DeviceId install -r $Apk 2>&1 if (($install -join "`n") -notmatch 'Success') { Write-Bad ($install -join "`n"); exit 1 } Write-Ok '安装成功' # ---------- 冷启动 ---------- Write-Step '冷启动并抓取日志' $logPath = Join-Path $root 'build\device-check-last.log' $creates = 0 # Flyme 有时不会立刻杀掉带前台服务的进程,导致抓不到启动日志:确认进程消失 + 最多重试两次 for ($attempt = 1; $attempt -le 2; $attempt++) { & adb -s $DeviceId shell am force-stop $pkg | Out-Null $deadline = (Get-Date).AddSeconds(10) while ((& adb -s $DeviceId shell pidof $pkg) -and (Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 300 } # 进程还在说明被系统冻结/保活,直接等到超时也无意义,先给它一点时间解冻 Start-Sleep -Seconds 1 & adb -s $DeviceId logcat -c # 解析真正的启动组件再启动:applicationId 带 .dev 后缀时, # "$pkg/.MainActivity" 会把 .MainActivity 拼到 .dev 上导致找不到类; # 而 "am start -p $pkg" 在部分 ROM 上又解析不到,所以直接问 PackageManager。 $component = (& adb -s $DeviceId shell cmd package resolve-activity --brief $pkg 2>&1 | Select-Object -Last 1).Trim() & adb -s $DeviceId shell am start -W -n $component 2>&1 | Out-Null # 第一次按参数等待;第二次重试时多等一会儿(Flyme 解冻可能很慢) $waitThisTime = if ($attempt -eq 1) { $WaitSeconds } else { [Math]::Max($WaitSeconds, 20) } Start-Sleep -Seconds $waitThisTime & adb -s $DeviceId logcat -d -v time > $logPath # 注意:PS 5.1 的 > 重定向写出的是 UTF-16LE(带 BOM),读取时不要强制 utf8 $log = Get-Content $logPath $text = $log -join "`n" $creates = ([regex]::Matches($text, '\[PlaybackService\] 单例创建')).Count if ($creates -ge 1) { break } Write-Host " (第 $attempt 次没抓到冷启动日志,进程可能没退干净,重试)" -ForegroundColor DarkYellow } # ---------- 判定 ---------- Write-Step '实例检查' $serviceIds = @([regex]::Matches($text, 'serviceId=(\d+)') | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique) $players = @([regex]::Matches($text, 'Player 已创建.*?player=(\d+)') | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique) $failed = $false if ($creates -eq 1) { Write-Ok "PlaybackService 单例创建 x$creates" } else { Write-Bad "PlaybackService 单例创建 x$creates(应为 1)"; $failed = $true } if ($serviceIds.Count -eq 1) { Write-Ok "serviceId 唯一:$($serviceIds[0])" } else { Write-Bad "出现 $($serviceIds.Count) 个 serviceId:$($serviceIds -join ', ')"; $failed = $true } if ($players.Count -eq 1) { Write-Ok "Player 唯一:$($players[0])" } else { Write-Bad "出现 $($players.Count) 个 Player:$($players -join ', ')"; $failed = $true } # ---------- 系统侧 ---------- Write-Step '系统 MediaSession' $sessionIds = @(& adb -s $DeviceId shell dumpsys media_session 2>&1 | Select-String ("media-session " + [regex]::Escape($pkg) + "/media-session/(\d+)") | ForEach-Object { $_.Matches[0].Groups[1].Value } | Sort-Object -Unique) if ($sessionIds.Count -eq 1) { Write-Ok "MediaSession 唯一:$($sessionIds[0])" } elseif ($sessionIds.Count -eq 0) { Write-Host ' -- 还没有 MediaSession(未开始播放,属正常)' -ForegroundColor DarkGray } else { Write-Bad "出现 $($sessionIds.Count) 个 MediaSession:$($sessionIds -join ', ')"; $failed = $true } # ---------- 崩溃 / 加载失败 ---------- Write-Step '崩溃与原生库' $bad = $log | Select-String 'FATAL EXCEPTION|UnsatisfiedLinkError|Cannot load MediaKitAndroidHelper|Could not load' if ($bad) { $bad | Select-Object -First 5 | ForEach-Object { Write-Bad $_.Line.Trim() }; $failed = $true } else { Write-Ok '无崩溃、无 so 加载失败' } # ---------- 结果 ---------- Write-Host '' if ($failed) { Write-Host 'RESULT: FAIL —— 仍存在多实例或异常,完整日志:' -ForegroundColor Red Write-Host " $logPath" exit 1 } Write-Host 'RESULT: PASS —— 单引擎单实例' -ForegroundColor Green Write-Host "日志:$logPath" Write-Host '' Write-Host '还需要你手动确认(脚本无法代替耳朵和眼睛):' -ForegroundColor Yellow Write-Host ' 1. 放一首歌,确认只有一路声音(没有重影/叠音)' Write-Host ' 2. 按通知栏暂停/播放,确认播放页状态同步变化' Write-Host ' 3. 拖通知栏进度条,确认播放页进度跟着走'