feat: 将 profile.sh 重写为场景化版本,支持按交互场景录制 Perfetto trace

- 引入 --scenario 参数,支持 15 种预设交互场景
- 自动场景通过 adb 自动执行操作(滑动翻页、拖拽折叠、切换视图等)
- 手动场景提供操作指引并保留倒计时准备
- 批量录制模式(--all)自动遍历所有场景并生成汇总报告
- 输出文件按场景名命名,支持场景级对比分析
This commit is contained in:
xfy 2026-06-01 18:22:56 +08:00
parent 829f89eb7c
commit d933ca3e41

View File

@ -1,59 +1,549 @@
#!/bin/bash #!/bin/bash
# #
# YaYa 性能追踪脚本 # YaYa 性能追踪脚本(场景化版)
# 使用 Perfetto 抓取应用 trace保存到 logs/ 目录 # 支持按交互场景录制 Perfetto trace精准定位各场景性能
# #
# 用法: # 用法:
# ./scripts/profile.sh # 默认抓取 8 秒 # ./scripts/profile.sh # 默认抓取 8 秒(向后兼容)
# ./scripts/profile.sh 15 # 抓取 15 秒 # ./scripts/profile.sh --scenario month_browse --trace 15
# ./scripts/profile.sh --no-launch # 不自动启动应用(应用已在运行) # ./scripts/profile.sh --list-scenarios # 列出所有场景
# ./scripts/profile.sh --trace # 使用 trace 构建类型(保留自定义 trace 标记) # ./scripts/profile.sh --help # 帮助
# #
set -euo pipefail set -euo pipefail
PACKAGE="plus.rua.project" PACKAGE="plus.rua.project"
ACTIVITY="plus.rua.project.MainActivity" MAIN_ACTIVITY="plus.rua.project.MainActivity"
TOOLS_ACTIVITY="plus.rua.project.ToolsActivity"
DATECHECKER_ACTIVITY="plus.rua.project.DateCheckerActivity"
ABOUT_ACTIVITY="plus.rua.project.AboutActivity"
LICENSES_ACTIVITY="plus.rua.project.LicensesActivity"
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
LOGS_DIR="${PROJECT_ROOT}/logs" LOGS_DIR="${PROJECT_ROOT}/logs"
# 解析参数 # 默认参数
DURATION_SEC=8 DURATION_SEC=8
NO_LAUNCH=false NO_LAUNCH=false
USE_TRACE_BUILD=false USE_TRACE_BUILD=false
SCENARIO=""
SCENARIO_NAME=""
SCENARIO_DESC=""
BG_PID=""
RUN_ALL=false
for arg in "$@"; do # ──────────────────────────────────────────
case "$arg" in # 场景列表
# ──────────────────────────────────────────
list_scenarios() {
cat <<'EOF'
可用场景:
主日历场景:
month_browse - 月视图左右滑动翻页(自动滑动)
date_select - 点击不同日期(需手动:依次点击多个日期)
collapse_expand - 折叠/展开 月视图↔周视图(自动拖拽)
year_view - 年视图切换+年份滑动(自动)
year_select - 年视图中选择月份返回(自动)
today_jump - 跳转到今天(自动)
menu_toggle - FAB 菜单打开关闭(自动)
legal_holiday - 显示调休切换(自动)
cross_month - 跨月日期选择(需手动:点击灰色日期)
其他页面:
tools - 工具页面(自动打开)
date_checker - 日期检查器(需手动:添加行、输入天数、滑动删除)
about - 关于页面(自动打开)
licenses - 开源许可列表(自动打开+滑动)
综合场景:
full_flow - 完整用户流程(需手动:按提示操作所有功能)
all_activities - 遍历所有 Activity自动
批量录制:
--all - 自动依次录制所有自动场景,最后生成汇总报告
默认场景(向后兼容):
(不指定 --scenario启动应用抓取指定时长
EOF
}
# ──────────────────────────────────────────
# 参数解析
# ──────────────────────────────────────────
while [ $# -gt 0 ]; do
case "$1" in
--no-launch) --no-launch)
NO_LAUNCH=true NO_LAUNCH=true
shift
;; ;;
--trace) --trace)
USE_TRACE_BUILD=true USE_TRACE_BUILD=true
shift
;;
--scenario)
if [ $# -lt 2 ]; then
echo "错误: --scenario 需要指定场景名"
exit 1
fi
SCENARIO="$2"
shift 2
;;
--all)
RUN_ALL=true
shift
;;
--list-scenarios)
list_scenarios
exit 0
;; ;;
--help|-h) --help|-h)
echo "用法: $0 [秒数] [--no-launch] [--trace]" cat <<EOF
echo "" 用法: $0 [秒数] [--no-launch] [--trace] [--scenario <场景>] [--all]
echo "选项:"
echo " 秒数 抓取时长(默认 8 秒)" 选项:
echo " --no-launch 不自动启动应用" 秒数 抓取时长(默认 8 秒,--all 时每个场景默认 10 秒)
echo " --trace 使用 trace 构建release 优化 + 保留 trace 标记)" --no-launch 不自动启动应用
echo " --help 显示此帮助" --trace 使用 trace 构建release + trace 标记保留)
--scenario <场景> 指定交互场景,脚本会引导或自动执行对应操作
--all 批量录制所有自动场景,生成汇总报告
--list-scenarios 列出所有可用场景
--help 显示此帮助
示例:
# 录制月视图滑动翻页 15 秒
$0 --scenario month_browse --trace 15
# 录制完整用户流程 30 秒(需手动按提示操作)
$0 --scenario full_flow --trace 30
# 录制日期检查器操作(需手动)
$0 --scenario date_checker --trace 20
# 批量录制所有自动场景(每个 10 秒)
$0 --all --trace 10
# 默认录制(向后兼容)
$0 --trace 15
EOF
exit 0 exit 0
;; ;;
[0-9]*) [0-9]*)
DURATION_SEC="$arg" DURATION_SEC="$1"
shift
;;
*)
echo "未知参数: $1"
echo "使用 --help 查看帮助"
exit 1
;; ;;
esac esac
done done
# ──────────────────────────────────────────
# 工具函数
# ──────────────────────────────────────────
# 获取屏幕尺寸
get_screen_size() {
adb shell wm size 2>/dev/null | awk '{print $3}'
}
# 获取屏幕中心坐标
get_screen_center() {
local size w h
size=$(get_screen_size)
w=$(echo "$size" | cut -dx -f1)
h=$(echo "$size" | cut -dx -f2)
echo "$w $h"
}
# 清理后台进程
cleanup_bg() {
if [ -n "$BG_PID" ] && kill -0 "$BG_PID" 2>/dev/null; then
kill "$BG_PID" 2>/dev/null || true
wait "$BG_PID" 2>/dev/null || true
fi
}
trap cleanup_bg EXIT
# 等待 trace 开始后再执行操作
wait_then_do() {
local delay_sec="$1"
shift
(
sleep "$delay_sec"
"$@"
) &
BG_PID=$!
}
# ──────────────────────────────────────────
# 场景执行
# ──────────────────────────────────────────
run_scenario() {
local scenario="$1"
local screen_size screen_w screen_h cx cy
screen_size=$(get_screen_size)
if [ -z "$screen_size" ]; then
echo "错误: 无法获取屏幕尺寸"
exit 1
fi
screen_w=$(echo "$screen_size" | cut -dx -f1)
screen_h=$(echo "$screen_size" | cut -dx -f2)
cx=$((screen_w / 2))
cy=$((screen_h / 2))
# 日历网格大致区域(屏幕中间偏上)
local grid_y=$((screen_h * 35 / 100))
# FAB 按钮大致位置(右下)
local fab_x=$((screen_w * 85 / 100))
local fab_y=$((screen_h * 85 / 100))
# BottomCard 拖拽区域(屏幕下半部分)
local bottom_y=$((screen_h * 70 / 100))
case "$scenario" in
month_browse)
SCENARIO_NAME="月视图滑动翻页"
SCENARIO_DESC="在月视图中快速左右滑动翻页,测试 CalendarPager 重组和绘制性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行trace 开始后将连续左右滑动翻页"
wait_then_do 2 bash -c "
for i in 1 2 3 4 5 6 7 8; do
adb shell input swipe $((screen_w*80/100)) $grid_y $((screen_w*20/100)) $grid_y 200
sleep 0.4
adb shell input swipe $((screen_w*20/100)) $grid_y $((screen_w*80/100)) $grid_y 200
sleep 0.4
done
"
;;
date_select)
SCENARIO_NAME="点击日期选择"
SCENARIO_DESC="依次点击不同日期,测试 DayCell 重组和 AnimatedGif 动画性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 请手动操作trace 开始后,依次点击日历中不同的日期(建议点击 5-10 个)"
echo " 可尝试点击同一行不同列、跨行点击,观察选中动画"
;;
collapse_expand)
SCENARIO_NAME="折叠/展开 月视图↔周视图"
SCENARIO_DESC="拖拽 BottomCard 上下切换月视图和周视图,测试折叠动画和布局变化性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行trace 开始后将自动拖拽 BottomCard 上下切换"
wait_then_do 2 bash -c "
for i in 1 2 3 4 5; do
# 向上拖拽(月→周)
adb shell input swipe $cx $bottom_y $cx $((screen_h*40/100)) 400
sleep 0.8
# 向下拖拽(周→月)
adb shell input swipe $cx $((screen_h*40/100)) $cx $bottom_y 400
sleep 0.8
done
"
;;
year_view)
SCENARIO_NAME="年视图切换+年份滑动"
SCENARIO_DESC="打开年视图后在年份间左右滑动,测试 YearGridView 重组和 AnimatedContent 转场性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
# 打开年视图(点击 FAB → 年视图)
adb shell input tap "$fab_x" "$fab_y"
sleep 0.5
# 点击"年视图"菜单项(在 FAB 上方约 100px
adb shell input tap "$fab_x" $((fab_y - 120))
sleep 0.8
echo " → 自动执行已打开年视图trace 开始后将滑动浏览年份"
wait_then_do 2 bash -c "
for i in 1 2 3 4; do
adb shell input swipe $((screen_w*80/100)) $cy $((screen_w*20/100)) $cy 200
sleep 0.5
adb shell input swipe $((screen_w*20/100)) $cy $((screen_w*80/100)) $cy 200
sleep 0.5
done
"
;;
year_select)
SCENARIO_NAME="年视图中选择月份"
SCENARIO_DESC="在年视图中点击不同月份返回月视图,测试 MonthView→YearView→MonthView 完整转场链"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
# 打开年视图
adb shell input tap "$fab_x" "$fab_y"
sleep 0.5
adb shell input tap "$fab_x" $((fab_y - 120))
sleep 0.8
echo " → 自动执行trace 开始后将依次点击不同的迷你月份"
wait_then_do 2 bash -c "
# 点击第1行不同月份大致坐标
adb shell input tap $((screen_w*20/100)) $((screen_h*25/100))
sleep 1.0
# 重新打开年视图
adb shell input tap $fab_x $fab_y
sleep 0.3
adb shell input tap $fab_x $((fab_y - 120))
sleep 0.5
adb shell input tap $((screen_w*50/100)) $((screen_h*25/100))
sleep 1.0
# 再试一个
adb shell input tap $fab_x $fab_y
sleep 0.3
adb shell input tap $fab_x $((fab_y - 120))
sleep 0.5
adb shell input tap $((screen_w*80/100)) $((screen_h*50/100))
sleep 1.0
"
;;
today_jump)
SCENARIO_NAME="跳转到今天"
SCENARIO_DESC="先翻到非当月,然后点击"今天"按钮跳转,测试自动翻页和选中动画"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
# 先翻几页离开当月
adb shell input swipe $((screen_w*20/100)) $grid_y $((screen_w*80/100)) $grid_y 200
sleep 0.5
adb shell input swipe $((screen_w*20/100)) $grid_y $((screen_w*80/100)) $grid_y 200
sleep 0.5
echo " → 自动执行trace 开始后将点击"今天"按钮"
# "今天"按钮大致在左上角
wait_then_do 2 bash -c "
adb shell input tap $((screen_w*15/100)) $((screen_h*12/100))
sleep 2.0
adb shell input swipe $((screen_w*20/100)) $grid_y $((screen_w*80/100)) $grid_y 200
sleep 0.5
adb shell input tap $((screen_w*15/100)) $((screen_h*12/100))
"
;;
menu_toggle)
SCENARIO_NAME="FAB 菜单打开关闭"
SCENARIO_DESC="反复打开/关闭 FAB 菜单,测试 AnimatedVisibility 缩放动画性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行trace 开始后将反复点击 FAB"
wait_then_do 2 bash -c "
for i in 1 2 3 4 5 6 7 8 9 10; do
adb shell input tap $fab_x $fab_y
sleep 0.5
done
"
;;
legal_holiday)
SCENARIO_NAME="显示调休切换"
SCENARIO_DESC="切换"显示调休"开关,测试 DayCell 大规模重组和 staggered 动画性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行trace 开始后将反复切换"显示调休""
wait_then_do 2 bash -c "
for i in 1 2 3 4; do
# 打开菜单
adb shell input tap $fab_x $fab_y
sleep 0.4
# 点击"显示调休"(在 FAB 上方约 200px
adb shell input tap $fab_x $((fab_y - 200))
sleep 1.0
done
"
;;
cross_month)
SCENARIO_NAME="跨月日期选择"
SCENARIO_DESC="点击上月或下月的灰色日期,测试跨月自动跳转和 pager 同步性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 请手动操作trace 开始后,点击日历中灰色显示的上月/下月日期"
echo " 建议:先点击右侧灰色日期(下月),再点击左侧灰色日期(上月)"
;;
tools)
SCENARIO_NAME="工具页面"
SCENARIO_DESC="打开工具页面,测试 Activity 转场动画和 ToolsScreen 渲染性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${TOOLS_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行工具页面已打开trace 期间保持静态"
;;
date_checker)
SCENARIO_NAME="日期检查器"
SCENARIO_DESC="在日期检查器中进行添加行、输入天数、滑动删除等操作,测试复杂表单性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${DATECHECKER_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 请手动操作trace 开始后,执行以下操作"
echo " 1. 点击 FAB (+) 添加一行"
echo " 2. 在新行的"天数"输入框中输入数字(如 90"
echo " 3. 点击另一行的日历图标修改到期日期"
echo " 4. 向左滑动某一行删除"
echo " 5. 修改生产日期"
;;
about)
SCENARIO_NAME="关于页面"
SCENARIO_DESC="打开关于页面,测试 Activity 转场和 AboutScreen 渲染性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${ABOUT_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行关于页面已打开trace 期间保持静态"
;;
licenses)
SCENARIO_NAME="开源许可列表"
SCENARIO_DESC="打开开源许可页面并上下滑动,测试 LicensesScreen 列表滑动性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${LICENSES_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 自动执行许可页面已打开trace 开始后将上下滑动"
wait_then_do 2 bash -c "
for i in 1 2 3 4 5; do
adb shell input swipe $cx $((screen_h*70/100)) $cx $((screen_h*30/100)) 300
sleep 0.5
adb shell input swipe $cx $((screen_h*30/100)) $cx $((screen_h*70/100)) 300
sleep 0.5
done
"
;;
full_flow)
SCENARIO_NAME="完整用户流程"
SCENARIO_DESC="覆盖所有主要交互的完整流程,综合评估整体性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1
sleep 1
echo " → 请严格按以下顺序手动操作:"
echo ""
echo " 1. 左右滑动翻页 2-3 次"
echo " 2. 点击几个不同日期"
echo " 3. 向上拖拽 BottomCard 切换到周视图"
echo " 4. 在周视图中左右滑动翻周"
echo " 5. 向下拖拽展开回月视图"
echo " 6. 点击 FAB 打开菜单 → 年视图"
echo " 7. 年视图中滑动浏览年份"
echo " 8. 点击一个月份返回"
echo " 9. 点击 FAB → 显示调休(切换一次)"
echo " 10. 点击"今天"按钮"
echo " 11. 点击 FAB → 工具"
echo " 12. 在工具页点击"日期检查器""
echo " 13. 添加一行、输入天数、滑动删除一行"
echo " 14. 返回 → 返回 → 关于"
echo " 15. 点击"开放源代码许可""
echo " 16. 滑动许可列表"
echo " 17. 返回 → 返回回到主页面"
echo ""
echo " 建议在 trace 期间尽可能流畅地完成上述操作"
;;
all_activities)
SCENARIO_NAME="遍历所有 Activity"
SCENARIO_DESC="自动依次打开所有 Activity测试 Activity 启动和转场动画性能"
echo ""
echo "[场景] ${SCENARIO_NAME}"
echo " ${SCENARIO_DESC}"
echo ""
echo " → 自动执行trace 开始后将依次打开所有页面"
wait_then_do 2 bash -c "
sleep 1
adb shell am start -n ${PACKAGE}/${MAIN_ACTIVITY}
sleep 1.5
adb shell am start -n ${PACKAGE}/${TOOLS_ACTIVITY}
sleep 1.5
adb shell am start -n ${PACKAGE}/${ABOUT_ACTIVITY}
sleep 1.5
adb shell am start -n ${PACKAGE}/${LICENSES_ACTIVITY}
sleep 1.5
adb shell am start -n ${PACKAGE}/${DATECHECKER_ACTIVITY}
sleep 1.5
adb shell am start -n ${PACKAGE}/${MAIN_ACTIVITY}
sleep 1.0
"
;;
*)
echo "错误: 未知场景: $scenario"
echo "使用 --list-scenarios 查看可用场景"
exit 1
;;
esac
}
# ──────────────────────────────────────────
# 主流程
# ──────────────────────────────────────────
DURATION_MS=$((DURATION_SEC * 1000)) DURATION_MS=$((DURATION_SEC * 1000))
TIMESTAMP=$(date +%Y%m%d_%H%M%S) TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 场景后缀
SCENARIO_SUFFIX=""
if [ -n "$SCENARIO" ]; then
SCENARIO_SUFFIX="_${SCENARIO}"
fi
echo "========================================" echo "========================================"
echo " YaYa 性能追踪" echo " YaYa 性能追踪"
echo " 包名: ${PACKAGE}" echo " 包名: ${PACKAGE}"
echo " 时长: ${DURATION_SEC}s" echo " 时长: ${DURATION_SEC}s"
echo " 构建: $([ "$USE_TRACE_BUILD" = true ] && echo "trace (release + trace)" || echo "debug")" echo " 构建: $([ "$USE_TRACE_BUILD" = true ] && echo "trace (release + trace)" || echo "debug")"
if [ -n "$SCENARIO" ]; then
echo " 场景: ${SCENARIO}"
fi
echo " 输出: ${LOGS_DIR}/" echo " 输出: ${LOGS_DIR}/"
echo "========================================" echo "========================================"
@ -85,26 +575,194 @@ if ! adb shell pm list packages | grep -q "${PACKAGE}"; then
exit 1 exit 1
fi fi
# 启动应用(如果未禁用) # ──────────────────────────────────────────
# 批量录制所有自动场景
# ──────────────────────────────────────────
run_all_scenarios() {
local script="$0"
local per_scene_duration="${DURATION_SEC}"
# --all 模式下默认每个场景 10 秒
if [ "$per_scene_duration" -eq 8 ]; then
per_scene_duration=10
fi
local auto_scenarios=(
month_browse
collapse_expand
year_view
year_select
today_jump
menu_toggle
legal_holiday
tools
about
licenses
all_activities
)
local total=${#auto_scenarios[@]}
local build_flag=""
[ "$USE_TRACE_BUILD" = true ] && build_flag="--trace"
echo ""
echo "========================================"
echo " 批量录制模式"
echo "${total} 个自动场景"
echo " 每个场景 ${per_scene_duration}"
echo "========================================"
echo ""
local i=0
for s in "${auto_scenarios[@]}"; do
i=$((i + 1))
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " [${i}/${total}] 录制场景: ${s}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
"$script" --scenario "$s" $build_flag "$per_scene_duration"
if [ "$i" -lt "$total" ]; then
echo ""
echo " 休息 2 秒,准备下一个场景..."
sleep 2
fi
done
# 生成汇总报告
generate_summary_report "$total" "$per_scene_duration"
}
# 生成批量录制汇总报告
generate_summary_report() {
local total="$1"
local duration="$2"
local summary_time
summary_time=$(date +%Y%m%d_%H%M%S)
local summary_file="${LOGS_DIR}/report_${summary_time}_ALL_SUMMARY.md"
echo ""
echo "========================================"
echo " 生成批量录制汇总报告"
echo "========================================"
cat > "$summary_file" <<EOF
# YaYa 批量性能追踪汇总报告
**时间:** $(date '+%Y-%m-%d %H:%M:%S')
**设备:** ${DEVICE_MODEL} (Android ${ANDROID_VERSION})
**应用版本:** ${APP_VERSION}
**构建类型:** $([ "$USE_TRACE_BUILD" = true ] && echo "trace" || echo "debug")
**场景数:** ${total}
**每场景时长:** ${duration}s
## 各场景帧率对比
| 场景 | 总帧数 | 掉帧数 | 掉帧比例 | P50 | P90 | P99 | Slow Draw | Total PSS |
|------|--------|--------|---------|-----|-----|-----|-----------|-----------|
EOF
for s in month_browse collapse_expand year_view year_select today_jump menu_toggle legal_holiday tools about licenses all_activities; do
local report
report=$(ls -t "${LOGS_DIR}"/report_*"_${s}".md 2>/dev/null | head -1)
if [ -n "$report" ]; then
local tf jf jp p50 p90 p99 sd tp
tf=$(awk '/Total frames rendered:/{print $4; exit}' "${report}")
jf=$(awk '/掉帧数/{getline; print $2; exit}' "${report}")
jp=$(awk '/掉帧比例/{getline; print $2; exit}' "${report}")
p50=$(awk '/50th percentile/{getline; print $2; exit}' "${report}")
p90=$(awk '/90th percentile/{getline; print $2; exit}' "${report}")
p99=$(awk '/99th percentile/{getline; print $2; exit}' "${report}")
sd=$(awk '/Slow draw commands/{getline; print $2; exit}' "${report}")
tp=$(awk '/Total PSS/{getline; print $2; exit}' "${report}")
printf "| %s | %s | %s | %s | %s | %s | %s | %s | %s |\n" "$s" "${tf:-N/A}" "${jf:-N/A}" "${jp:-N/A}" "${p50:-N/A}" "${p90:-N/A}" "${p99:-N/A}" "${sd:-N/A}" "${tp:-N/A}" >> "$summary_file"
fi
done
cat >> "$summary_file" <<EOF
## 分析建议
1. **掉帧比例最高**的场景是性能瓶颈所在,优先优化
2. **Slow draw commands** 高的场景说明 GPU 绘制是瓶颈
3. **P99 延迟高**的场景说明存在偶发性卡顿
4. 对比各场景的 **Total PSS**,观察内存波动
## 文件清单
EOF
for s in month_browse collapse_expand year_view year_select today_jump menu_toggle legal_holiday tools about licenses all_activities; do
local trace report
trace=$(ls -t "${LOGS_DIR}"/trace_*"_${s}".perfetto-trace 2>/dev/null | head -1)
report=$(ls -t "${LOGS_DIR}"/report_*"_${s}".md 2>/dev/null | head -1)
if [ -n "$trace" ]; then
echo "- \`$(basename "$trace")\` + \`$(basename "$report")\` — ${s}" >> "$summary_file"
fi
done
echo "" >> "$summary_file"
echo "---" >> "$summary_file"
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')" >> "$summary_file"
echo ""
echo "========================================"
echo " 批量录制完成!"
echo "========================================"
echo ""
echo "汇总报告: ${summary_file}"
echo ""
}
# ──────────────────────────────────────────
# 如果是 --all 模式,直接执行批量录制
# ──────────────────────────────────────────
if [ "$RUN_ALL" = true ]; then
run_all_scenarios
exit 0
fi
# ──────────────────────────────────────────
# 场景准备
# ──────────────────────────────────────────
if [ -n "$SCENARIO" ]; then
run_scenario "$SCENARIO"
else
# 默认场景:仅启动应用
if [ "$NO_LAUNCH" = false ]; then if [ "$NO_LAUNCH" = false ]; then
echo "" echo ""
echo "[1/5] 启动应用..." echo "[1/5] 启动应用..."
adb shell am start -n "${PACKAGE}/${ACTIVITY}" >/dev/null 2>&1 || true adb shell am start -n "${PACKAGE}/${MAIN_ACTIVITY}" >/dev/null 2>&1 || true
sleep 2 sleep 2
else else
echo "" echo ""
echo "[1/5] 跳过启动 (--no-launch)" echo "[1/5] 跳过启动 (--no-launch)"
fi fi
fi
# 如果不是自动场景,给用户一点准备时间
if [ -n "$SCENARIO" ] && [ "$SCENARIO" = "full_flow" ] || [ "$SCENARIO" = "date_select" ] || [ "$SCENARIO" = "cross_month" ] || [ "$SCENARIO" = "date_checker" ]; then
echo ""
echo "准备开始 trace请在倒计时结束后立即操作..."
for i in 3 2 1; do
echo " $i..."
sleep 1
done
fi
# ──────────────────────────────────────────
# 抓取 Perfetto trace # 抓取 Perfetto trace
# ──────────────────────────────────────────
echo "" echo ""
echo "[2/5] 抓取 Perfetto trace (${DURATION_SEC}s)..." echo "[2/5] 抓取 Perfetto trace (${DURATION_SEC}s)..."
if [ -n "$SCENARIO" ]; then
echo " 场景: ${SCENARIO_NAME}"
echo " 自动操作已启动,请按提示完成手动操作..."
else
echo " 请在设备上操作应用trace 正在记录..." echo " 请在设备上操作应用trace 正在记录..."
fi
TRACE_FILE="/data/misc/perfetto-traces/yaya_${TIMESTAMP}.perfetto-trace" TRACE_FILE="/data/misc/perfetto-traces/yaya_${TIMESTAMP}${SCENARIO_SUFFIX}.perfetto-trace"
LOCAL_TRACE="${LOGS_DIR}/trace_${TIMESTAMP}.perfetto-trace" LOCAL_TRACE="${LOGS_DIR}/trace_${TIMESTAMP}${SCENARIO_SUFFIX}.perfetto-trace"
LOCAL_CONFIG="${LOGS_DIR}/.perfetto_config_${TIMESTAMP}.txt" LOCAL_CONFIG="${LOGS_DIR}/.perfetto_config_${TIMESTAMP}${SCENARIO_SUFFIX}.txt"
DEVICE_CONFIG="/data/misc/perfetto-configs/yaya_config_${TIMESTAMP}.txt" DEVICE_CONFIG="/data/misc/perfetto-configs/yaya_config_${TIMESTAMP}${SCENARIO_SUFFIX}.txt"
# 生成本地配置文件,然后 push 到设备 # 生成本地配置文件,然后 push 到设备
cat > "${LOCAL_CONFIG}" <<EOF cat > "${LOCAL_CONFIG}" <<EOF
@ -172,22 +830,27 @@ echo " 拉取 trace 文件..."
adb pull "${TRACE_FILE}" "${LOCAL_TRACE}" adb pull "${TRACE_FILE}" "${LOCAL_TRACE}"
adb shell "rm -f ${TRACE_FILE}" || true adb shell "rm -f ${TRACE_FILE}" || true
# 等待后台操作完成
cleanup_bg
# 抓取帧统计 # 抓取帧统计
echo "" echo ""
echo "[3/5] 抓取帧统计..." echo "[3/5] 抓取帧统计..."
FRAMESTATS_FILE="${LOGS_DIR}/framestats_${TIMESTAMP}.txt" FRAMESTATS_FILE="${LOGS_DIR}/framestats_${TIMESTAMP}${SCENARIO_SUFFIX}.txt"
adb shell dumpsys gfxinfo "${PACKAGE}" framestats > "${FRAMESTATS_FILE}" adb shell dumpsys gfxinfo "${PACKAGE}" framestats > "${FRAMESTATS_FILE}"
# 抓取内存信息 # 抓取内存信息
echo "" echo ""
echo "[4/5] 抓取内存信息..." echo "[4/5] 抓取内存信息..."
MEMINFO_FILE="${LOGS_DIR}/meminfo_${TIMESTAMP}.txt" MEMINFO_FILE="${LOGS_DIR}/meminfo_${TIMESTAMP}${SCENARIO_SUFFIX}.txt"
adb shell dumpsys meminfo "${PACKAGE}" > "${MEMINFO_FILE}" adb shell dumpsys meminfo "${PACKAGE}" > "${MEMINFO_FILE}"
# ──────────────────────────────────────────
# 生成报告摘要 # 生成报告摘要
# ──────────────────────────────────────────
echo "" echo ""
echo "[5/5] 生成摘要..." echo "[5/5] 生成摘要..."
REPORT_FILE="${LOGS_DIR}/report_${TIMESTAMP}.md" REPORT_FILE="${LOGS_DIR}/report_${TIMESTAMP}${SCENARIO_SUFFIX}.md"
# 计算帧率相关数据 # 计算帧率相关数据
FRAME_COUNT=$(grep -c "FrameTimeline" "${FRAMESTATS_FILE}" 2>/dev/null || echo "0") FRAME_COUNT=$(grep -c "FrameTimeline" "${FRAMESTATS_FILE}" 2>/dev/null || echo "0")
@ -210,7 +873,7 @@ APP_VERSION=$(adb shell dumpsys package "${PACKAGE}" | grep versionName | head -
DEVICE_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r') DEVICE_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r')
ANDROID_VERSION=$(adb shell getprop ro.build.version.release 2>/dev/null | tr -d '\r') ANDROID_VERSION=$(adb shell getprop ro.build.version.release 2>/dev/null | tr -d '\r')
# 获取内存摘要$NF 取最后一个字段,避免中间空格导致的列错位) # 获取内存摘要
TOTAL_PSS=$(awk '/TOTAL PSS:/{print $3; exit}' "${MEMINFO_FILE}") TOTAL_PSS=$(awk '/TOTAL PSS:/{print $3; exit}' "${MEMINFO_FILE}")
JAVA_HEAP=$(awk '/^ *Java Heap:/{print $3; exit}' "${MEMINFO_FILE}") JAVA_HEAP=$(awk '/^ *Java Heap:/{print $3; exit}' "${MEMINFO_FILE}")
NATIVE_HEAP=$(awk '/^ *Native Heap:/{print $3; exit}' "${MEMINFO_FILE}") NATIVE_HEAP=$(awk '/^ *Native Heap:/{print $3; exit}' "${MEMINFO_FILE}")
@ -224,15 +887,27 @@ cat > "${REPORT_FILE}" <<EOF
**应用版本:** ${APP_VERSION} **应用版本:** ${APP_VERSION}
**构建类型:** $([ "$USE_TRACE_BUILD" = true ] && echo "trace" || echo "debug") **构建类型:** $([ "$USE_TRACE_BUILD" = true ] && echo "trace" || echo "debug")
**追踪时长:** ${DURATION_SEC}s **追踪时长:** ${DURATION_SEC}s
EOF
# 场景信息
if [ -n "$SCENARIO" ]; then
cat >> "${REPORT_FILE}" <<EOF
**场景:** ${SCENARIO}
**场景名称:** ${SCENARIO_NAME}
**场景说明:** ${SCENARIO_DESC}
EOF
fi
cat >> "${REPORT_FILE}" <<EOF
## 文件清单 ## 文件清单
| 文件 | 说明 | | 文件 | 说明 |
|------|------| |------|------|
| \`trace_${TIMESTAMP}.perfetto-trace\` | Perfetto trace在 https://ui.perfetto.dev 中打开) | | \`trace_${TIMESTAMP}${SCENARIO_SUFFIX}.perfetto-trace\` | Perfetto trace在 https://ui.perfetto.dev 中打开) |
| \`framestats_${TIMESTAMP}.txt\` | GPU 帧统计 | | \`framestats_${TIMESTAMP}${SCENARIO_SUFFIX}.txt\` | GPU 帧统计 |
| \`meminfo_${TIMESTAMP}.txt\` | 内存快照 | | \`meminfo_${TIMESTAMP}${SCENARIO_SUFFIX}.txt\` | 内存快照 |
| \`report_${TIMESTAMP}.md\` | 本报告 | | \`report_${TIMESTAMP}${SCENARIO_SUFFIX}.md\` | 本报告 |
## 帧率摘要 ## 帧率摘要
@ -256,8 +931,101 @@ cat > "${REPORT_FILE}" <<EOF
| Java Heap | ${JAVA_HEAP} | | Java Heap | ${JAVA_HEAP} |
| Native Heap | ${NATIVE_HEAP} | | Native Heap | ${NATIVE_HEAP} |
| Graphics | ${GRAPHICS} | | Graphics | ${GRAPHICS} |
EOF
## Perfetto 分析指南 # 场景特有分析指南
if [ -n "$SCENARIO" ]; then
cat >> "${REPORT_FILE}" <<EOF
## 场景分析指南
本报告对应场景: **${SCENARIO_NAME}**
### 在 Perfetto UI 中重点查看:
EOF
case "$SCENARIO" in
month_browse)
cat >> "${REPORT_FILE}" <<EOF
- \`CalendarPager:Page:*\` — 各月分页重组耗时分布
- \`MonthView:Compose\` — 月视图整体重组频率
- \`getMonthDays:*\` — 月份网格计算耗时
- 对比不同月份页面的 \`max_ms\`,找出重组最慢的月份
EOF
;;
date_select)
cat >> "${REPORT_FILE}" <<EOF
- \`DayCell\` — 单个日期单元格重组(通过 transition label
- \`MonthView:Compose\` — 日期选中触发的整页重组
- \`AnimatedGif\` 相关 trace如果存在— 选中动画
- 查找是否有连续多帧超过 16.67ms(选中动画期间)
EOF
;;
collapse_expand)
cat >> "${REPORT_FILE}" <<EOF
- \`VM:collapseProgress\` — 折叠动画状态更新频率和耗时
- \`CalendarPagerArea\` — 区域重组(月↔周切换时的布局变化)
- \`WeekPager:Page:*\` — 周视图出现时的重组
- 观察 \`collapseProgress\` 与帧数据的对应关系
EOF
;;
year_view|year_select)
cat >> "${REPORT_FILE}" <<EOF
- \`MonthView→YearView\` / \`YearView→MonthView\` — 转场 trace
- \`YearView:Compose\` — 年视图整体重组
- \`YearGridView:*\` — 年视图网格重组
- \`generateMiniMonthDays:*\` — 迷你月日期计算
- 对比转场前后 500ms 的帧率变化
EOF
;;
menu_toggle)
cat >> "${REPORT_FILE}" <<EOF
- 查找 AnimatedVisibility 相关的重组 slice
- \`MonthView:Compose\` — 菜单开关是否触发整页重组
- 观察菜单动画期间的帧率是否稳定
EOF
;;
legal_holiday)
cat >> "${REPORT_FILE}" <<EOF
- \`DayCell\` — 大规模重组(所有日期单元格可能同时重组)
- 查找 staggered 动画相关的 tracedelay=cellIndex*15
- 对比开关前后的 \`MonthView:Compose\` 重组次数
EOF
;;
tools|about|licenses|date_checker)
cat >> "${REPORT_FILE}" <<EOF
- 查看对应 Activity 启动时的 Choreographer#doFrame 帧数据
- \`MonthView:Compose\`(如从主页面跳转)— 转场期间的重组
- Activity 转场动画期间的帧率稳定性
EOF
;;
full_flow)
cat >> "${REPORT_FILE}" <<EOF
- 分段分析:按时间轴对应不同操作阶段
- 标记出各阶段切换时的帧率变化
- \`MonthView→YearView\`\`YearView→MonthView\` 等转场标记
- 整体评估哪些操作阶段掉帧最严重
EOF
;;
all_activities)
cat >> "${REPORT_FILE}" <<EOF
- 按时间分段,对应不同 Activity 启动阶段
- 比较各 Activity 启动时的首帧耗时
- 观察 Activity 转场动画slide_in_right期间的帧率
EOF
;;
*)
cat >> "${REPORT_FILE}" <<EOF
- 查看本场景相关的自定义 trace 标记
- 分析对应时间段的帧率数据
EOF
;;
esac
fi
cat >> "${REPORT_FILE}" <<EOF
## 通用 Perfetto 分析指南
打开 [Perfetto UI](https://ui.perfetto.dev),上传 trace 文件: 打开 [Perfetto UI](https://ui.perfetto.dev),上传 trace 文件:
@ -286,40 +1054,29 @@ cat > "${REPORT_FILE}" <<EOF
- \`DayCell\` — 单个日期单元格(通过 transition label - \`DayCell\` — 单个日期单元格(通过 transition label
### 2. 分析帧率 ### 2. 分析帧率
\`framestats_${TIMESTAMP}.txt\` 中查看: \`framestats_${TIMESTAMP}${SCENARIO_SUFFIX}.txt\` 中查看:
- \`FrameTimeline\` 行 — 每帧的 CPU/GPU 耗时 - \`FrameTimeline\` 行 — 每帧的 CPU/GPU 耗时
- \`jank\` 标记 — 掉帧情况 - \`jank\` 标记 — 掉帧情况
### 3. 内存分析 ### 3. 内存分析
\`meminfo_${TIMESTAMP}.txt\` 中关注: \`meminfo_${TIMESTAMP}${SCENARIO_SUFFIX}.txt\` 中关注:
- \`TOTAL\` 行 — 应用总内存占用 - \`TOTAL\` 行 — 应用总内存占用
- \`Graphics\` 行 — GPU 内存使用 - \`Graphics\` 行 — GPU 内存使用
- \`Native Heap\` 行 — 原生堆内存 - \`Native Heap\` 行 — 原生堆内存
### 4. 年月视图切换专项分析
在 Perfetto 中按以下步骤分析转场性能:
1. 找到 \`MonthView→YearView\`\`YearView→MonthView\` 标记
2. 查看标记前后 500ms 的帧数据:
- 查找超过 16.67ms 的帧Choreographer#doFrame
- 检查是否有连续多帧超过预算
3. 同时搜索 \`MonthView:Compose\`\`YearView:Compose\`,观察重组重叠情况
4. 查看 \`YearGridView:*\` 的耗时,年视图 12 个月网格的计算和绘制成本
## 基线对比方法 ## 基线对比方法
要对比优化前后的性能: 要对比优化前后的性能:
\`\`\`bash \`\`\`bash
# 1. 记录当前数据作为基线 # 1. 记录当前数据作为基线
./scripts/profile.sh --trace 15 $0 --scenario ${SCENARIO:-month_browse} --trace 15
# 2. 修改代码后重新编译 # 2. 修改代码后重新编译
./gradlew :app:installTrace ./gradlew :app:installTrace
# 3. 再次记录 # 3. 再次记录
./scripts/profile.sh --trace 15 $0 --scenario ${SCENARIO:-month_browse} --trace 15
# 4. 对比两个 report 中的帧率摘要表格 # 4. 对比两个 report 中的帧率摘要表格
\`\`\` \`\`\`
@ -336,8 +1093,16 @@ echo " framestats: ${FRAMESTATS_FILE}"
echo " meminfo: ${MEMINFO_FILE}" echo " meminfo: ${MEMINFO_FILE}"
echo " report: ${REPORT_FILE}" echo " report: ${REPORT_FILE}"
echo "" echo ""
if [ -n "$SCENARIO" ]; then
echo "场景: ${SCENARIO_NAME}"
echo ""
fi
echo "下一步:" echo "下一步:"
echo " 1. 打开 https://ui.perfetto.dev" echo " 1. 打开 https://ui.perfetto.dev"
echo " 2. 上传 trace_${TIMESTAMP}.perfetto-trace" echo " 2. 上传 trace_${TIMESTAMP}${SCENARIO_SUFFIX}.perfetto-trace"
if [ -n "$SCENARIO" ]; then
echo " 3. 搜索场景相关的 trace 标记进行分析"
else
echo " 3. 搜索 'MonthView→YearView' 查看转场 trace" echo " 3. 搜索 'MonthView→YearView' 查看转场 trace"
fi
echo "" echo ""