Як я знизив споживання батареї з 15% до 1%: технічна історія оптимізації AdBlockerV2 на C++ NDK
Привіт, DOU!
Мене звати Леонід, я незалежний розробник. Нещодавно я завершив роботу над технічно складним етапом свого проекту AdBlockerV2 для Android. Хочу поділитися досвідом, як перехід з Java на C++ NDK допоміг вирішити проблему, яка здавалася нездоланною для VPN-сервісів у фоновому режимі.
Проблема: Попередження від Android 15 та «гарячий» смартфон
Коли я починав розробку AdBlockerV2, моєю метою було створити локальний фільтр трафіку, який працює без віддалених серверів. Перші версії були написані на Java. Все працювало, але був один величезний «факап» — енергоспоживання.
Додаток використовував близько 15% ресурсів процесора. Через 16 годин роботи Android 15 видавав суворе попередження: «High system resource usage». Смартфон відчутно грівся, а користувачі (і я сам) не хотіли жертвувати автономністю заради приватності.
Чому Java не «вивезла»
Обробка тисяч мережевих пакетів на секунду всередині JVM створює величезне навантаження:
- Garbage Collector (GC): Постійне створення та видалення масивів байтів для кожного пакета змушувало GC працювати без зупину.
- JNI Overhead: Постійні переходи між системними викликами та керованим кодом Java «з’їдали» дорогоцінні цикли процесора.
Я зрозумів: щоб зробити професійний інструмент, потрібно спускатися на рівень нижче.
Рішення: Перехід на Native C++ Engine
Я вирішив повністю переписати «серце» додатка — двигун фільтрації трафіку — на C++ з використанням Android NDK. Ось ключові технічні рішення, які змінили все:
1. Прямий доступ до TUN-інтерфейсу
Ми почали читати та записувати дані безпосередньо у файловий дескриптор TUN-інтерфейсу в C++. Це дозволило оминути JVM і працювати з пам’яттю максимально близько до ядра ОС.
2. Thread Pool (Пул потоків)
Замість того, щоб створювати новий потік для кожного DNS-запиту (що вбиває батарею), я впровадив фіксований пул із
3. Оптимізація пошуку: Trie-дерево та O(L)
Для фільтрації доменів я побудував кастомну структуру даних — Trie (префіксне дерево). Завдяки використанню std::string_view, ми проводимо пошук без виділення нової пам’яті (zero-allocation). Складність пошуку тепер залежить лише від довжини домену (O(L)), а не від кількості правил. Чи у вас 1 000 правил, чи 100 000 — швидкість фільтрації залишається однаковою.
4. Захист сокетів (Socket Protection)
Однією з причин розряду була нескінченна петля трафіку. Ми реалізували нативний механізм protect(), який виводить сокети самого фільтра з-під дії VPN-тунелю.
Результат: < 1% батареї
Результати після
Для ознакомленя з додатком перейдть: softdevelop-ua.itch.io/adblockerv2
Буду радий почути фідбек від колег щодо архітектури та використання NDK у подібних проектах.
Додаток можна знайти на itch.io за назвою AdBlockerV2.
Дякую за увагу та підтримуйте український софт! 🇺🇦
12 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівОце я розумію нормальний підхід до розробки.
Доречі, хотів протестувати на пікселі але не встановлюється. Я так розумію там потрібно підтримка arm64-v8a але не розбираюсь в цьому
Так, ви праві. Сучасні Pixel (починаючи з7-ї серії) підтримують виключно 64-бітну архітектуру (arm64-v8a). Додаток зібраний із підтримкою цієї архітектури, але оскільки це інсталяція через APK, Android 14/15 може блокувати її через налаштування безпеки або Play Protect.64-бітних систем. Дякую за фідбек!
Спробуйте під час встановлення натиснути «More details» -> «Install anyway». Також я перевірю цілісність білда саме для
Та я звісно поставив потрібні налаштування безпеки. Але при встановленні просто помилка без будь-яких подробиць, тому і думаю що то може бути проблема саме в зборці
Оновив на 1.0.1, я нажаль не можу це затестити
зачем аж четыре потока для DNS? разве это не задача под один поток, которые будет делать любое количество запросов асинхронно?
Пул потоків обрано через використання блокуючого recv: поки один потік чекає відповіді від DNS-сервера, інші продовжують миттєво обробляти нові запити, що виключає затримки в браузері. Це надійніше і простіше в реалізації, ніж складний асинхронний Event Loop на базі epoll, а в стані спокою воркери «сплять» і споживають 0% CPU
блокирующий recv в 2026м году? под андроид нет ни одной нормальной реализации асинхронных сокетов? boost ASIO?
Поточна архітектура з пулом потоків проста, надійна і вже забезпечує <1% споживання батареї. Оскільки я переважно C# розробник, я обрав перевірений патерн (знайшов у інеті), який дає реальний результат без зайвого ускладнення. Також С++ для мене більш хобі (hello world написав ще у 94). Якщо б не було результату то копав би ще, та мабуть прийшов до іншого рішеня
Java не тормозить. Java не тормозить. C++ давно помер. Ну, а так—то круто. Чому програма важить аж 53 мБ?
про С++ не хочу споричатись.
А про вагу могу казати таке. По дефолту він був 20метрів та працював лише на компі, пришлось у файл-прожект додати <RuntimeIdentifiers>android-arm;android-arm64;android-x86;android-x64</RuntimeIdentifiers>
Додаток містить у собі рантайм .NET та бібліотеки для кросплатформеності. Також ми пакуємо нативні бібліотеки (.so) під 4 архітектури (arm64, v7, x86), щоб додаток працював на будь-якому пристрої.
Як щодо Sony Xperia M2 dual з Android Lollipop?