Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 16 — Battery Drain Testing

Đã đọc 0/13 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa release một app đặt đồ ăn ở Việt Nam. Chức năng chạy mượt, UI đẹp, test case pass hết. Nhưng ba tuần sau, đội chăm sóc khách hàng gửi cho bạn một loạt review 1 sao trên Google Play: "App tụt pin kinh khủng, mở buổi sáng đầy pin, trưa hết sạch", "Điện thoại nóng như lò sưởi khi bật app này", "Đã gỡ, hao pin quá". Không một dòng crash log nào. Không một test chức năng nào fail. Nhưng app của bạn đang bị người dùng ghét bỏ vì một lý do mà đội QA gần như không bao giờ đo: tiêu thụ pin (battery drain).

Đây là một trong những mảng bị bỏ quên nhất trong QA mobile, và cũng là mảng có tác động thương mại trực tiếp nhất. Người dùng Việt Nam đặc biệt nhạy cảm với pin: rất nhiều người dùng điện thoại tầm trung (pin nhỏ, chip nóng), sạc một lần dùng cả ngày, và họ chủ động đi tìm "app nào ngốn pin nhất" trong phần cài đặt của máy. iOS có màn hình Settings → Battery liệt kê rõ tên app và phần trăm pin tiêu thụ; Android còn khắc nghiệt hơn — từ Android 8 (Oreo) trở lên, hệ điều hành tự động kill hoặc bóp (throttle) những app chạy nền ngốn pin, khiến chính chức năng của bạn (nhận thông báo, đồng bộ dữ liệu) bị hỏng.

Nói cách khác: pin không chỉ là "trải nghiệm", nó là vấn đề chức năng và giữ chân người dùng (retention). Bài này sẽ dạy bạn cách nghĩ, cách đo, và cách báo cáo về battery drain một cách có kỷ luật — biến một chủ đề mơ hồ thành những con số có thể hành động.

Khái niệm cốt lõi

Pin bị tiêu tốn ở đâu

Về bản chất, mọi mAh (mili-ampe-giờ) pin bị tiêu tốn đều quy về việc app khiến phần cứng phải "thức dậy" và làm việc. Có bốn nhóm phần cứng chính là "thủ phạm" ăn pin, và QA cần nhận diện app của mình thuộc nhóm nào:

  • CPU/GPU: tính toán nặng, render animation phức tạp, vòng lặp bận (busy loop), parse JSON khổng lồ, hoặc bug khiến app chạy 100% CPU liên tục. GPU tốn pin khi render đồ hoạ, video, animation 60fps liên tục.
  • Màn hình (display): là bộ phận ngốn pin nhất khi app đang mở foreground. Màn hình sáng, nội dung nhiều màu trắng (trên OLED), giữ màn luôn bật (wake lock giữ screen-on) đều tốn pin.
  • Radio — mạng (network radio): đây là "sát thủ ngầm". Sóng di động (cellular) tốn pin gấp nhiều lần Wi-Fi. Điều quan trọng ít người biết: sau mỗi lần truyền dữ liệu, radio không tắt ngay mà giữ ở trạng thái năng lượng cao thêm vài giây (gọi là tail energy). App gọi API 100 lần rời rạc tốn pin hơn nhiều so với gộp 100 request thành một lần (batching).
  • GPS & cảm biến: định vị độ chính xác cao (high-accuracy GPS) là một trong những thứ ngốn pin khủng khiếp nhất. App bản đồ, giao hàng, thể thao rất dễ dính.

Wake lock và background — nguồn gốc của "ngốn pin ngầm"

Khái niệm quan trọng nhất bạn cần nắm là wake lock (Android) và background execution (cả hai nền tảng). Bình thường khi bạn tắt màn hình, CPU chuyển sang trạng thái ngủ (doze/sleep) để tiết kiệm pin. Wake lock là cơ chế cho phép app "giữ" CPU (hoặc màn hình) thức dù người dùng không tương tác. Nếu lập trình viên quên release wake lock — ví dụ giữ CPU thức để chờ một callback không bao giờ tới — app sẽ âm thầm rút pin cả đêm dù trông như đã đóng.

Trên Android 8+, Google đưa ra background execution limits và Doze mode / App Standby Buckets: khi máy đứng yên (để trên bàn, màn tắt), hệ thống gom các tác vụ nền lại thành các "cửa sổ bảo trì" (maintenance windows) và cấm app tự do chạy nền. App nào cố lách (ví dụ lạm dụng foreground service, hoặc gọi network liên tục) sẽ bị đánh vào battery restriction — người dùng thấy cảnh báo "App này đang tiêu tốn pin", và có thể bị hệ thống hạn chế.

Ba loại kịch bản battery cần phân biệt

Khi test, đừng gộp tất cả làm một. Hãy chia rõ:

  • Foreground drain: app đang mở, người dùng đang dùng. Đây là mức tiêu thụ "chấp nhận được" cao nhất, nhưng vẫn phải hợp lý (một app đọc tin không được ngốn pin như một game 3D).
  • Background drain: app ở nền (đã chuyển sang app khác nhưng chưa kill). Đây là nơi 80% bug pin ẩn náu. Mục tiêu lý tưởng: gần bằng 0.
  • Idle drain: app đóng hoàn toàn (đã swipe khỏi recent apps) nhưng vẫn có thể có service/wakelock chạy ngầm. Bất kỳ mức tiêu thụ đáng kể nào ở đây đều là bug nghiêm trọng.

Tình huống thực tế

Tình huống 1: App gọi xe với GPS "cắm điện" (bối cảnh Đông Nam Á)

Một startup gọi xe tại TP.HCM (giả định tên XeNhanh) nhận hàng loạt phàn nàn tài xế: điện thoại của tài xế nóng ran và tụt pin ~18%/giờ ngay cả khi đang chờ khách (không có chuyến). Đội QA ban đầu bối rối vì test chức năng đều pass.

Khi dựng bài test đo có kỷ luật, họ phát hiện: khi app ở trạng thái "online chờ chuyến", nó vẫn yêu cầu GPS ở chế độ PRIORITY_HIGH_ACCURACY với tần suất update 1 giây/lần, cộng thêm một wake lock giữ CPU thức để "phản ứng nhanh khi có chuyến". Kết quả: GPS + wake lock + radio gửi vị trí liên tục = pin bốc hơi.

Bài học: Giải pháp là hạ độ chính xác GPS khi đang chờ (dùng BALANCED_POWER_ACCURACY, giãn tần suất lên 5-10 giây), gộp cập nhật vị trí theo batch, và bỏ wake lock — thay bằng push notification (FCM) để đánh thức app khi có chuyến. Drain giảm còn ~6%/giờ. Điều đội QA rút ra: luôn test trạng thái "idle nhưng active" — trạng thái mà app trông như không làm gì nhưng thực chất đang cắm điện vào phần cứng.

Tình huống 2: App tin tức bị Android tự động kill (bối cảnh Việt Nam)

Một app đọc báo (giả định BáoNhanh) có tính năng thông báo tin nóng. Người dùng phản ánh: "Cài xong nhận thông báo bình thường, vài ngày sau tự nhiên không thấy tin nóng nữa." Đây là bug rất khó tái hiện bằng test thông thường vì nó chỉ xảy ra sau vài ngày máy đứng yên.

Nguyên nhân: app dùng một cơ chế polling — cứ 15 phút lại tự thức dậy gọi API kiểm tra tin mới, kèm partial wake lock. Android theo dõi thấy app này thức quá thường xuyên, đẩy nó vào App Standby Bucket "rare/restricted" và cắt quyền chạy nền. Từ đó thông báo không tới nữa.

Bài học: Đội QA phải học cách dùng lệnh adb để mô phỏng doze mode (ép máy vào trạng thái ngủ sâu ngay lập tức thay vì chờ vài ngày), và kiểm tra app có sống sót không. Giải pháp kỹ thuật là bỏ polling, chuyển sang push notification server-side (FCM data message) — đúng cách mà Google khuyến nghị. Con số: số wake-up giảm từ ~96 lần/ngày xuống gần 0, và app thoát khỏi danh sách bị hạn chế. Bài học QA: battery bug và functional bug thường là một — hao pin dẫn tới bị OS phạt, bị phạt dẫn tới mất chức năng.

Tình huống 3: Animation "long lanh" ngốn GPU (fintech)

Một ví điện tử có màn hình chờ (splash/loading) với animation gradient chuyển màu liên tục và một vòng loading xoay 60fps. Thiết kế rất đẹp. Nhưng QA đo thấy: chỉ cần để app ở màn này 10 phút, pin tụt nhanh gấp 3 lần màn hình tĩnh, và máy tầm trung bắt đầu nóng.

Vấn đề: animation chạy vô hạn (infinite loop) không bao giờ dừng, giữ GPU render 60fps liên tục kể cả khi dữ liệu đã tải xong nhưng có lỗi mạng khiến người dùng kẹt ở màn này. Bài học: animation trang trí phải có giới hạn, phải dừng khi màn hình không thay đổi, và QA cần test cả kịch bản "kẹt ở màn loading do mạng chậm" — một điều kiện rất thật với mạng 3G/4G chập chờn ở vùng ngoại thành Việt Nam.

Hướng dẫn từng bước

Đây là quy trình đo battery drain cơ bản mà bạn có thể áp dụng ngay, không cần thiết bị đắt tiền.

Bước 1 — Chuẩn bị môi trường sạch. Dùng thiết bị thật (không phải emulator — emulator không mô phỏng pin chính xác). Sạc đầy hoặc về một mức pin cố định (ví dụ 80%). Đóng các app khác, tắt các đồng bộ nền không liên quan, ghi lại: model máy, phiên bản OS, độ sáng màn hình (đặt cố định, ví dụ 50%), Wi-Fi hay cellular. Ghi lại điều kiện là bắt buộc — battery test không thể tái hiện nếu điều kiện khác nhau.

Bước 2 — Xác định kịch bản đo. Chọn rõ một trong ba loại: foreground / background / idle, và một hành động cụ thể (ví dụ: "để app ở background 1 giờ sau khi login"). Test một biến tại một thời điểm.

Bước 3 — Đo bằng công cụ có sẵn của hệ điều hành:

  • Android: Reset thống kê pin bằng adb shell dumpsys batterystats --reset, chạy kịch bản, rồi lấy báo cáo bằng adb shell dumpsys batterystats. Dùng Battery Historian (công cụ mã nguồn mở của Google) để trực quan hoá: nó vẽ biểu đồ wake lock, số lần radio bật, GPS, CPU theo thời gian. Đây là công cụ vàng để tìm "app thức dậy bao nhiêu lần".
  • iOS: Dùng Instruments trong Xcode với template Energy Log hoặc Time Profiler; trên máy thật xem Settings → Battery để thấy % app tiêu thụ. Trong quá trình dev, bật MetricKit để thu thập dữ liệu pin từ người dùng thật.
Bước 4 — Mô phỏng điều kiện khắc nghiệt bằng adb (Android):
  • Ép doze mode ngay: adb shell dumpsys deviceidle force-idle (rồi kiểm tra app còn nhận thông báo / còn chạy đúng không).
  • Kiểm tra wake lock đang giữ: tìm mục Wake lock trong output dumpsys batterystats hoặc dumpsys power.
Bước 5 — Đo drain theo thời gian. Ghi mức pin đầu và cuối sau một khoảng cố định (30 phút hoặc 1 giờ), quy đổi ra %/giờ. So sánh với baseline: cùng máy, cùng thời gian, nhưng app đóng hoàn toàn. Con số bạn quan tâm là phần chênh lệch — phần pin do app của bạn gây ra.

Bước 6 — Đối chiếu với ngưỡng. Thiết lập ngưỡng chấp nhận (acceptance threshold) cho từng kịch bản, ví dụ: background drain không vượt quá 1%/giờ; idle drain ~0%. Nếu vượt, mở bug với đầy đủ số liệu và điều kiện.

Lỗi thường gặp & mẹo

Lỗi 1 — Test trên emulator. Emulator/simulator không có pin thật, mọi con số battery đều vô nghĩa. Battery test bắt buộc dùng thiết bị thật.

Lỗi 2 — Không có baseline. Báo cáo "app tụt 5% trong 1 giờ" là vô nghĩa nếu bạn không biết máy để không cũng tụt 3%. Luôn đo phần chênh lệch so với trạng thái không chạy app.

Lỗi 3 — Chỉ đo một lần. Battery drain có độ nhiễu (noise) cao — nhiệt độ phòng, sóng mạng, tiến trình hệ thống đều ảnh hưởng. Lặp lại phép đo ít nhất 2-3 lần và lấy xu hướng, đừng tin một con số đơn lẻ.

Lỗi 4 — Bỏ quên trạng thái background và idle. Đa số QA chỉ test khi app mở. Nhưng như đã thấy ở các tình huống trên, bug pin nghiêm trọng nhất nằm ở nền và idle. Hãy luôn hỏi: "Sau khi tôi chuyển sang app khác, app này còn làm gì?"

Lỗi 5 — Không test với mạng yếu. Radio ở vùng sóng yếu phải phát công suất cao hơn để giữ kết nối → tốn pin gấp bội. Một app ổn ở văn phòng Wi-Fi mạnh có thể ngốn pin khủng khiếp trên 4G chập chờn ở quê.

Mẹo:

  • Chú ý nhiệt độ máy như một chỉ báo nhanh: máy nóng bất thường gần như luôn đồng nghĩa với CPU/GPU/radio đang bị lạm dụng. Đây là "linh cảm" QA rẻ tiền mà hiệu quả.
  • Trên Android, luôn kiểm tra số lần app thức dậy (wakeups) trong Battery Historian — con số này thường lộ vấn đề rõ hơn cả phần trăm pin.
  • Phối hợp với dev để bật MetricKit (iOS) và các công cụ theo dõi pin từ production — dữ liệu thật từ hàng nghìn máy quý hơn phòng lab.
  • Đưa battery test vào regression cho các build lớn, đặc biệt sau khi thay đổi logic đồng bộ, GPS, hoặc thêm SDK bên thứ ba (SDK quảng cáo/analytics là thủ phạm ngốn pin ngầm rất phổ biến).

Bài tập thực hành

  • Đo baseline và foreground drain. Chọn một app bất kỳ trên máy Android thật của bạn. Reset batterystats, mở app và dùng bình thường 30 phút, sau đó lấy báo cáo. Tính ra %/giờ. Rồi lặp lại với máy để không (app đóng) 30 phút để có baseline. So sánh hai con số.
  • Săn wake lock. Cài Battery Historian (hoặc dùng trực tiếp output dumpsys batterystats), tìm trong một app xem có wake lock nào giữ lâu bất thường không. Ghi lại tên wake lock và thời lượng.
  • Mô phỏng doze. Với một app có thông báo, chạy adb shell dumpsys deviceidle force-idle, rồi kiểm tra: app còn nhận được thông báo/đồng bộ không? Viết lại kết quả — đây chính là dạng bug ở Tình huống 2.
  • Viết acceptance threshold. Cho một app giao hàng giả định, hãy soạn bảng ngưỡng pin cho ba trạng thái foreground/background/idle, kèm điều kiện đo (model máy, độ sáng, mạng). Đây là loại tài liệu bạn sẽ thực sự bàn giao trong dự án thật.

Tóm tắt

Battery drain là mảng QA "vô hình" nhưng có tác động thương mại trực tiếp: nó tạo ra review 1 sao, uninstall, và trên Android 8+ còn khiến app bị OS phạt, mất luôn chức năng nền. Pin bị tiêu tốn bởi bốn nhóm phần cứng — CPU/GPU, màn hình, radio mạng, và GPS/cảm biến — trong đó wake lock và hoạt động nền là nguồn gốc của phần lớn bug pin ẩn.

Nguyên tắc cốt lõi khi test: luôn phân biệt ba trạng thái foreground / background / idle; luôn đo trên thiết bị thật với baseline để lấy phần chênh lệch; lặp lại phép đo để trừ nhiễu; và mô phỏng điều kiện khắc nghiệt (doze mode, mạng yếu) thay vì chờ đời thực xảy ra. Công cụ chính là adb dumpsys batterystats + Battery Historian trên Android, và Instruments/MetricKit trên iOS. Và hãy nhớ bài học lớn nhất qua các tình huống thực tế: hao pin và lỗi chức năng thường là cùng một vấn đề — một app ngốn pin không chỉ bị người dùng ghét, mà còn tự tay phá hỏng chính tính năng của mình.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi