| name | mobile-resource-test |
| description | 端側(裝置上)效能測試專屬流程,覆蓋冷/熱啟動時間、記憶體洩漏、電量消耗、ANR、掉幀/卡頓(jank)、CPU/GPU。整合 iOS(XCTest Metrics + Instruments + MetricKit)/ Android(Macrobenchmark + Perfetto + LeakCanary + JankStats)/ Flutter(DevTools + profile mode)。產出含 SLA 門檻、迴歸偵測、CI 整合的端側效能報告。當使用者提到「端側效能 / 冷啟動 / app 啟動時間 / 記憶體洩漏 / memory leak / 電量測試 / ANR / 掉幀 / jank / 卡頓 / 掉幀率 / Instruments / Macrobenchmark / LeakCanary / 幀率 / fps 測試」時觸發。配套:performance-test-gen(server 端 API 壓測,本 skill 是端側補強)、test-automation(把效能測試掛進 UI test)、smoke-test-analyzer(效能迴歸屬 T2 release)、bug-report(追效能 bug)。 |
| disable-model-invocation | false |
| allowed-tools | Read, Grep, Glob, Write, Edit, Bash |
| argument-hint | [平台 / 場景] [--metric=startup|memory|jank|battery] [--platform=ios|android|flutter] |
mobile-resource-test
⚙️ 執行前先讀 modules/config-loader.md。
為什麼需要這個 skill
performance-test-gen 做的是 server 端壓測(k6 / JMeter / Locust 打 API,量 p95 / error rate)——那是「後端扛不扛得住」。
但使用者實際握在手上的體感是端側的:
- App 點開要等幾秒(冷啟動)
- 滑動會不會卡(掉幀 / jank)
- 用久了會不會越來越燙、耗電、記憶體爆掉被系統殺掉
- 按下去沒反應(ANR / 主執行緒卡死)
你以 iOS / Android 為主,但 24 個 skill 裡端側效能完全空白——本 skill 補這個缺口。
→ 本 skill 專做 on-device 效能量測 + 洩漏偵測 + 迴歸守門。
適用場景
- ✅ Release 前端側效能守門(啟動 / 卡頓 / 記憶體不退步)
- ✅ 重構 / 加大功能後驗證沒拖慢 app
- ✅ 收到「app 好慢 / 好耗電 / 會閃退」客訴要量化重現
- ✅ 把效能 metric 掛進 CI,PR 時就擋住退步
不適用場景
- ❌ Server / API 壓測 — 用
performance-test-gen
- ❌ 功能對不對 — 用
test-master / test-automation
- ❌ 視覺有沒有跑掉 — 用
visual-regression-gen
5 大端側 metric + 工具對應
| Metric | 它代表 | iOS | Android | Flutter |
|---|
| 啟動時間 | 點開到可互動 | XCTApplicationLaunchMetric / Organizer | Macrobenchmark StartupTimingMetric | flutter run --profile timeline |
| 記憶體 | 洩漏 / 峰值 | Instruments Leaks/Allocations · XCTMemoryMetric | LeakCanary · Android Profiler · StrictMode | DevTools Memory |
| 掉幀 / jank | 滑動順不順 | XCTOSSignpostMetric · Hitches (Instruments) | Macrobenchmark FrameTimingMetric · JankStats | DevTools Timeline · frame stats |
| 電量 | 耗不耗電 | Energy Log · MetricKit | Battery Historian · Profiler Energy | (走原生工具) |
| ANR / 卡死 | 按了沒反應 | Main Thread Checker · watchdog | ANR trace · StrictMode | (平台層) |
SLA 門檻(預設,可由 config 覆蓋)
| Metric | 目標 | 來源 |
|---|
| 冷啟動 (cold) | < 2000 ms | Google Play vitals 建議 < 5s,優秀 < 2s |
| 熱啟動 (warm) | < 1000 ms | |
| Jank rate | < 1%(frozen frames < 0.1%) | Android vitals |
| 幀率 | 穩定 60fps(高刷 120fps 不掉) | |
| 記憶體洩漏 | 0 leak(重複進出畫面記憶體回穩) | |
| 記憶體峰值 | < config 上限(依裝置) | |
| ANR | 0 | |
執行流程
Phase 1: 偵測平台 + 工具
ls *.xcodeproj *.xcworkspace 2>/dev/null
ls build.gradle* settings.gradle* 2>/dev/null
ls pubspec.yaml 2>/dev/null
grep -rl "androidx.benchmark.macro" . 2>/dev/null
grep -rl "XCTApplicationLaunchMetric\|measure(metrics" . 2>/dev/null
Phase 2: 量測(依 metric 生對應碼)
iOS — XCTest Metrics(可進 CI)
import XCTest
final class LaunchPerformanceTests: XCTestCase {
func testColdLaunchTime() throws {
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
func testScrollHitches() throws {
let app = XCUIApplication(); app.launch()
measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric]) {
app.tables.firstMatch.swipeUp(velocity: .fast)
}
}
func testMemoryFootprint() throws {
measure(metrics: [XCTMemoryMetric()]) {
}
}
}
配 MetricKit(MXMetricManager)收集真實使用者的啟動 / hang / 電量資料。
Android — Macrobenchmark(可進 CI)
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun coldStartup() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD
) { pressHome(); startActivityAndWait() }
@Test fun scrollJank() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(FrameTimingMetric()),
iterations = 10
) { }
}
記憶體洩漏用 LeakCanary(debug build 自動偵測 retained instance);主執行緒 IO 用 StrictMode 抓。
Flutter — profile mode + DevTools
flutter run --profile
flutter drive --profile --target=integration_test/perf_test.dart
// 收集 frame timing
await binding.traceAction(() async {
await tester.fling(find.byType(ListView), const Offset(0, -500), 3000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_timeline');
// 解析 → 看 build/raster 是否超過 16ms (60fps)
Phase 3: 洩漏 / ANR 深度偵測
| 問題 | iOS | Android |
|---|
| 記憶體洩漏 | Instruments Leaks + Allocations(Generation 標記,重複操作看是否成長) | LeakCanary heap dump |
| Retain cycle | Memory Graph Debugger(看強引用環) | LeakCanary 指出 leak trace |
| 主執行緒卡死 | Main Thread Checker / Time Profiler | ANR trace + StrictMode |
Phase 4: 統一報告 + 迴歸偵測
合併輸出 → mobile-resource-report.md:
# Mobile Resource Report · my-app · 2026-06-02
## 📊 端側效能總覽
| Metric | 本次 | 門檻 | 上次 | 判定 |
|--------|------|------|------|------|
| 冷啟動 (iOS) | 1840ms | <2000 | 1620ms | ⚠️ 退步 +220ms |
| 冷啟動 (Android) | 1210ms | <2000 | 1190ms | ✅ |
| Jank rate | 0.6% | <1% | 0.4% | ✅ |
| 記憶體峰值 | 412MB | <450 | 398MB | ✅ |
| Leak | 1 | 0 | 0 | 🔴 新增洩漏 |
| ANR | 0 | 0 | 0 | ✅ |
## 🔴 必修
### 記憶體洩漏 — ProfileViewController
- **工具**: Instruments Leaks
- **trace**: `ImageCache` 強引用 `self`,detail 頁退出未釋放
- **重現**: 進出 profile 頁 10 次 → 記憶體 +38MB 不回收
- **修法**: closure 用 `[weak self]`,或 cache 改 NSCache 自動回收
### 冷啟動退步 +220ms
- **疑因**: 新增的 SDK 在 `application(_:didFinishLaunching:)` 同步初始化
- **修法**: 延後 / 背景初始化,或用 baseline profile (Android)
## 📋 修復清單 + 估時
## 📈 趨勢(近 3 次)
Phase 5: CI 整合 + 守門
- iOS:XCTest Metrics 設 baseline,超過容忍度 CI fail
- Android:Macrobenchmark +
baselineprofile gradle,迴歸擋 PR
- Flutter:解析 timeline summary,build/raster p90 超標 fail
- 把效能迴歸標到
smoke-test-analyzer 的 T2(release 才全跑,PR 只跑啟動)
⚠️ 安全護欄
- ❌ 絕不用 debug build 量效能(debug 有 assertion / 無最佳化,數字失真)→ iOS Release、Android
benchmark buildType、Flutter --profile
- ✅ 每個數字標裝置 + OS 版本(不同機差很大,跨機比較無意義)
- ✅ 多次取樣取中位數 / p90,不用單次(端側噪音大)
- ✅ 區分「真退步」與「量測噪音」——退步需超過容忍度才報
♿ a11y 必檢(本 skill 專屬)
無障礙設定會放大效能負擔,要一起量:
設定依賴
| 設定 Key | 用途 | 預設 |
|---|
mobile_resource.cold_start_ms | 冷啟動門檻 | 2000 |
mobile_resource.warm_start_ms | 熱啟動門檻 | 1000 |
mobile_resource.jank_threshold | 掉幀率上限 | 0.01 |
mobile_resource.memory_peak_mb | 記憶體峰值上限 | 450 |
mobile_resource.frame_rate_target | 目標幀率 | 60 |
mobile_resource.tools | 啟用工具 | 依平台自動 |
platforms.ios.repo / platforms.android.repo | 量測迴歸的版本對應 | — |
範例
詳見 examples.md