← До списку оновлень

Multi Timer v1.3.0

27 вересня 2026

Нещодавно в Play Маркеті мені прилетів перший негативний відгук — хтось без пояснень назвав Multi Timer «вбивцею батареї». Я перевірив застосунок наново на реальному телефоні: доба роботи одного таймера у фоні коштує менш ніж 0,1% заряду, а годинний таймер із відкритим на кілька хвилин екраном — 1,6%. Жодного підтвердження скарзі я не знайшов.

Споживання батареї на реальному пристрої

Samsung A34, Android 16, білий список оптимізації батареї, бездротовий adb (без кабелю, щоб виключити заряджання під час заміру).

  • Один таймер, фон, екран вимкнений, 24 год: 4,3 мА·год (<0,1% від ємності 5000 мА·год), 1 хв 36 с процесорного часу за 10,7 год.
  • Таймер на 1 год, екран відкритий ~4 хв: 8,87 мА·год загалом (1,6% від витрати телефона за цей час), з них 8,21 мА·год — екран, 0,03 мА·год — процесор при вимкненому екрані.
  • Таймер на 10 год 42 хв: 11,7 мА·год загалом (0,6%, 22-ге місце з 179 застосунків за списком витрати), з них 9,80 мА·год екран, 0,47 мА·год процесор при вимкненому екрані.

Історична точка відліку (до 1.2.0)

Емулятор AVD API 36, 20 хв екран вимкнений, активний 60-хв таймер:

  • До 1.2.0: 16,69 с процесорного часу, wakelock NotificationManagerService:post спрацював 1346 разів (≈1/с), утримувався сумарно 4 хв 38 с.
  • 1.3.0: 7,67 с процесорного часу, 0 wakelock-спрацювань post. У білому списку батареї: 8,21 с, так само 0.

Навантаження при великій кількості таймерів, екран увімкнений

120 одночасних таймерів, список відкритий:

  • До виправлення: CPU 86,6% одного ядра, 2,1 кадра/с, p50/p90/p99 кадру 117/200/350 мс, System UI ANR постійно.
  • Проміжне виправлення (один redraw/с замість одного на таймер, коміт bddf259): CPU 21,0%, 2,0 кадра/с, p50/p90/p99 85/150/200 мс, ANR немає.
  • Фінальна серія «спільний тикер» (коміт 471a3c5): CPU 7,3%, ~1,0 кадр/с, p50/p90/p99 65/69/101 мс.
  • Room IO (arch_disk_io) як частка процесорного часу: впала приблизно у 15 разів; частота викликів getById() — з ~120/с до ~2/с (прямий наслідок адаптивних пробуджень, коміт cfac2da).
  • Окремий замір: UI відкритий, 120 таймерів, 5,3 хв — 0,94 кадра/с, CPU 7,56% (24,04 с/318 с). Для порівняння, 3 таймери на попередній версії давали ті самі 1,0 кадр/с при 6% CPU — тобто fps тепер практично не залежить від кількості таймерів (ціль O(1) замість O(N) досягнута).

Фонове споживання процесора, екран вимкнений

120 таймерів, вимірювання на 10-хвилинних вікнах, поточковий знімок /proc//task:

  • База (до серії, bddf259): середнє CPU 0,694% одного ядра (розкид 0,674–0,724%).
  • Проміжна збірка (471a3c5, до фіксу f28aca2): 0,957% (0,943–0,986%) — гірше за базу, попри вдвічі рідші пробудження.
  • Фінальний фікс (f28aca2): 0,482% (0,472–0,489%).

Причина регресу проміжної збірки: waitForLoopWake використовував merge(loopWake.filter{...}, screenOns).first(), що на кожне пробудження виділяло Semaphore + Channel + 2–3 корутини. За simpleperf: 358 семплів ChannelFlowMerge/SemaphoreAndMutex на проміжній збірці проти 0 на базі; на потоці DefaultDispatch — у 2,91 рази більше семплів за той самий час при вдвічі рідших пробудженнях (тобто ~6x дорожче за одне пробудження). Фікс замінив merge() на loopWake.first { it == timerId || it == WAKE_ALL }.

Розкладка за потоками (середнє за 10 хв, база → фікс): - DefaultDispatch: 1,40 с → 1,03 с - DefaultExecutor: 0,59 с → 0,51 с - Room IO (arch_disk_io): 1,07 с → 0,76 с - GC (HeapTaskDaemon): 0,40 с → 0,16 с

Контрольні сценарії без змін

  • 120 таймерів, екран вимкнений, 20 хв: 7,66 с CPU — так само, як з одним таймером; 0 wakelock-post; 241 запланований будильник; без помилок квоти AlarmManager.
  • 30 таймерів з одним дедлайном: 30/30 спрацювали, розкид +3…+1407 мс (платформа доставляє послідовно), без rate-limit і ANR.
  • 10 циклічних таймерів по 5 с, повноекранний стиль: екран вмикається щоцикл, глибокий Doze неможливий, CPU 9,3% ядра, рівно 1,97 циклу/с — це вартість налаштованої поведінки, а не витік.

Точність спрацювання

  • Один таймер, екран вимкнений (3 повтори): лаг +2…+4 мс.
  • Форсований глибокий Doze, один таймер: +11 мс.
  • 30 одночасних, той самий дедлайн, екран вимкнений: 30/30, розкид +5…+1563 мс.
  • 30 одночасних, форсований глибокий Doze: 30/30, розкид до 12,4 с — затримка доставки пачки будильників на рівні платформи Android (виміряно на вході в AlarmReceiver), не код застосунку.
  • Запобіжник: AlarmReceiver вимкнено через pm disable в кожному з 4 контрольних кейсів (екран on/off, перемикання екрана за 56 с до дедлайну, 5 таймерів з дедлайнами через 20 с) — рівно одне спрацювання щоразу, лаг +44…+64 мс; після ввімкнення екрана застиглий відлік на сповіщенні наздоганяє реальний час за ~2,7 с.