复用底栏并恢复首页轮播状态,减少页面返回时的重复计算。统一预测返回的跟手、完成及取消动效,保留左右手势方向。增强首页海报主色并衔接下方背景,修复裁剪、暖色和灰度取色,补充回归测试及性能测量脚本。 验证:assembleDebug、testDebugUnitTest(64项)及 git diff --check 通过。
114 lines
5.5 KiB
PowerShell
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."
|