Files
lumaplayer/scripts/Measure-NavigationPerformance.ps1
beyond 21f347e588 优化页面转场、预测返回与首页沉浸取色
复用底栏并恢复首页轮播状态,减少页面返回时的重复计算。统一预测返回的跟手、完成及取消动效,保留左右手势方向。增强首页海报主色并衔接下方背景,修复裁剪、暖色和灰度取色,补充回归测试及性能测量脚本。

验证:assembleDebug、testDebugUnitTest(64项)及 git diff --check 通过。
2026-10-26 16:34:22 +08:00

114 lines
5.5 KiB
PowerShell

param(
[ValidateSet('DetailReturn', 'Tabs', 'RapidTabs')]
[string]$Scenario = 'DetailReturn',
[int]$Iterations = 3,
[int]$WarmupIterations = 3,
[double]$MaxP95Ms = 32,
[string]$Label = 'navigation',
[string]$Serial,
[string]$AdbPath = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe"
)
# Run on an unlocked device with the app's home page and media already loaded.
# Use the same device, build type, refresh rate, and scenario for before/after runs.
$ErrorActionPreference = 'Stop'
if ($Iterations -lt 1 -or $WarmupIterations -lt 1) { throw 'Iteration counts must be positive.' }
if ($Label -notmatch '^[a-zA-Z0-9_-]+$') { throw 'Label must be a simple filename.' }
$deviceArgs = if ($Serial) { @('-s', $Serial) } else { @() }
$outputDirectory = Join-Path $PSScriptRoot '..\build\perf'
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
function Invoke-Adb([string[]]$AdbArguments) {
$output = & $AdbPath @deviceArgs @AdbArguments
if ($LASTEXITCODE -ne 0) { throw "adb failed: $($AdbArguments -join ' ')" }
return $output
}
function Get-Window {
Invoke-Adb @('shell', 'uiautomator', 'dump', '/sdcard/luma-perf-window.xml') | Out-Null
return [xml](Invoke-Adb @('shell', 'cat', '/sdcard/luma-perf-window.xml'))
}
function Get-Center($Node) {
if (-not $Node -or $Node.bounds -notmatch '^\[(\d+),(\d+)\]\[(\d+),(\d+)\]$') {
throw 'Required control is not visible. Open the loaded home page first.'
}
return @([int](([int]$Matches[1] + [int]$Matches[3]) / 2),
[int](([int]$Matches[2] + [int]$Matches[4]) / 2))
}
function Tap($Point) {
Invoke-Adb @('shell', 'input', 'tap', "$($Point[0])", "$($Point[1])") | Out-Null
}
$window = Get-Window
$homeNode = $window.SelectSingleNode('//node[@content-desc="首页" or @content-desc="Home"]')
$homePoint = Get-Center $homeNode
$homeMarker = $window.SelectSingleNode('//node[@text="继续观看" or @text="Continue Watching" or @text="媒体库" or @text="My Media"]')
if (-not $homeMarker) { throw 'A loaded home media section must be visible before measuring.' }
if ($Scenario -eq 'DetailReturn') {
$resumeLabel = $window.SelectSingleNode('//node[@text="继续观看" or @text="Continue Watching"]')
if (-not $resumeLabel) { throw 'A visible Continue Watching row is required for the detail return scenario.' }
$resumeBottom = (Get-Center $resumeLabel)[1]
$mediaNode = $window.SelectNodes('//node[@clickable="true" and descendant::node[@content-desc!=""]]') |
Where-Object { (Get-Center $_)[1] -gt $resumeBottom -and (Get-Center $_)[1] -lt $homePoint[1] } |
Select-Object -First 1
$media = Get-Center $mediaNode
} else {
$tabs = @('记录', '服务器', '设置', '首页') | ForEach-Object {
,(Get-Center $window.SelectSingleNode("//node[@content-desc='$_']"))
}
}
$samples = @()
# Warm the same navigation paths before comparing builds.
for ($iteration = 1 - $WarmupIterations; $iteration -le $Iterations; $iteration++) {
if ($Scenario -eq 'DetailReturn') {
Tap $media
$detailReady = $false
for ($attempt = 0; $attempt -lt 5; $attempt++) {
$detail = Get-Window
if ($detail.SelectSingleNode('//node[@content-desc="返回" or @content-desc="Back"]')) {
$detailReady = $true
break
}
}
if (-not $detailReady) {
throw 'Media detail did not open; refusing to measure another screen.'
}
}
Invoke-Adb @('shell', 'dumpsys', 'gfxinfo', 'com.example.myapplication', 'reset') | Out-Null
if ($Scenario -eq 'DetailReturn') {
Invoke-Adb @('shell', 'input', 'keyevent', '4') | Out-Null
Start-Sleep -Milliseconds 900
} else {
$pause = if ($Scenario -eq 'RapidTabs') { '0.12' } else { '0.6' }
$commands = $tabs | ForEach-Object { "input tap $($_[0]) $($_[1]); sleep $pause" }
Invoke-Adb @('shell', ($commands -join '; ')) | Out-Null
Start-Sleep -Milliseconds 900
}
$stats = Invoke-Adb @('shell', 'dumpsys', 'gfxinfo', 'com.example.myapplication', 'framestats')
$text = $stats -join "`n"
$frames = [int]([regex]::Match($text, 'Total frames rendered: (\d+)').Groups[1].Value)
if ($frames -lt 10) { throw "Only $frames frames captured; the scenario did not run." }
$returned = Get-Window
if (-not $returned.SelectSingleNode("//node[@text='$($homeMarker.text)']")) {
throw 'The scenario did not return to the original home media section.'
}
if ($iteration -le 0) { continue }
$stats | Set-Content -LiteralPath (Join-Path $outputDirectory "$Label-$Scenario-$iteration.txt")
$samples += [pscustomobject]@{
Iteration = $iteration
Frames = $frames
P95Ms = [int]([regex]::Match($text, '(?m)^95th percentile: (\d+)ms').Groups[1].Value)
JankyFrames = [int]([regex]::Match($text, '(?m)^Janky frames: (\d+)').Groups[1].Value)
JankPercent = [double]([regex]::Match($text, '(?m)^Janky frames: \d+ \(([\d.]+)%\)').Groups[1].Value)
}
}
$samples | Format-Table -AutoSize
$medianP95 = ($samples.P95Ms | Sort-Object)[[int][math]::Floor($samples.Count / 2)]
$summary = [pscustomobject]@{ Scenario = $Scenario; MedianP95Ms = $medianP95; MaxP95Ms = $MaxP95Ms; Samples = $samples }
$summary | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $outputDirectory "$Label-$Scenario.json")
if ($medianP95 -gt $MaxP95Ms) { throw "Median P95 ${medianP95}ms exceeds ${MaxP95Ms}ms." }
Write-Output "PASS: median P95 ${medianP95}ms <= ${MaxP95Ms}ms."