Akadozik — ez a felhasználói leírása annak a helyzetnek, amikor a mobilalkalmazás lassan és instabilan működik: néha normálisan reagál, néha hirtelen több másodpercre lefagy. Technikai kontextusban az „akadozik” a gyakori GC szünetek, a főszál szinkron műveletekkel történő blokkolása és nem optimális adatszerkezetek által okozott lagok és mikro-lefagyások kombinációját jelenti. A Android Performance Benchmarking Guide szerint a válaszidő 300 ms-ról 100 ms-ra csökkentése 25%-kal növeli a felhasználók megtartását. Az akadozás diagnosztizálása a CPU és Memory profilozás kombinációját igényli a szemétgyűjtés gyakoriságának elemzésével.
Főbb pontok
Akadozik — egy informális kifejezés, amellyel a felhasználók a szubjektíven lassú alkalmazás-működést írják le. Ellentétben a lag-gal, amely állandó késleltetésként jelentkezik, az akadozás szabálytalan lefagyás: az alkalmazás tökéletesen működhet néhány másodpercig, majd 1–3 másodpercre „elgondolkodik”.
Profilozási szempontból az akadozik kihagyott képkockák sorozataként (jank) jelentkezik, 100 ms-nál nagyobb csúcskésleltetésekkel. Az FPS grafikonon ez éles zuhanásokként látszik: 60 → 20 → 55 → 10 képkocka másodpercenként. Ellentétben az egyenletesen alacsony FPS-sel járó lag-gal, az akadozás egyértelmű változékonysággal rendelkezik.
Amikor az alkalmazás akadozik, a felhasználó nem érti a lassulások logikáját: a képernyő simán görgethető, majd hirtelen megáll egy másodpercre. Ez frusztrációt okoz és csökkenti az alkalmazásba vetett bizalmat. A Google szerint a felhasználók 53%-a elhagyja a webhelyet vagy alkalmazást, ha a betöltés 3 másodpercnél tovább tart.
Az akadozás szabálytalan jellege arra utal, hogy a problémát eseménytényezők okozzák, nem állandó túlterhelés. Tekintsük át a tipikus forgatókönyveket.
Android-on az ART környezetben a szemétgyűjtés leállítja az alkalmazás összes szálát. Ha a kódban sok ideiglenes objektum jön létre — például minden onBindViewHolder hívásnál új String jön létre konkatenációval — a GC gyakrabban indul. A szünet 5–50 ms-ig tarthat a heap méretétől és az objektumok generációjától függően. A felhasználó ezt hirtelen „gondolkodásként” érzékeli.
Room Androidon és Core Data iOS-en támogatják az aszinkron lekérdezéseket, de a fejlesztők gyakran hívják a getValue()-t vagy hajtanak végre lekérdezést runBlocking-en keresztül az egyszerűség kedvéért. Egy nehéz SELECT join-okkal egy 10 000 soros táblán 200–500 ms-ig tarthat, teljesen blokkolva az UI-t ez idő alatt.
Kamerakép (12 Mp, 4000x3000 px) betöltése skálázás nélkül akár 200 ms-ig is eltarthat a Bitmap-pé dekódoláshoz. Ha a képek aszinkron módon töltődnek be, de korlátozott szálpool nélkül, 5–6 dekódolás egyidejű elindítása túlterhelheti a CPU-t, vándorló lassulásokat okozva.
A szabálytalan lassulások diagnosztizálása nehezebb, mint az állandó lag-oké, mert a probléma nem feltétlenül jelentkezik minden indításkor. Hosszabb időn keresztül történő statisztikagyűjtés szükséges.
Android Studio Memory Profiler nemcsak a memóriahasználatot mutatja, hanem a GC eseményeket is: gyakoriság, típus (Concurrent, Full), időtartam. Ha a GC nyugalmi állapotban 5 másodpercenként többször fordul elő — ez a túlzott allokáció jele. Heap dump rögzítése az akadozás pillanatában lehetővé teszi, hogy lássuk, mely objektumok foglalják a memóriát.
iOS-en használja az Allocations sablont az Instruments-ben az objektumok létrehozásának és felszabadításának nyomon követéséhez. Kapcsolja be a Generációkat (Generations) – ezek lehetővé teszik a heap pillanatfelvételeinek készítését a műveletek között, és annak megtekintését, hogy mely objektumok maradnak a memóriában. A fel nem szabaduló perzisztens objektumok — a memóriafelhalmozódás és a későbbi szünetek forrása.
JankStats — Android könyvtár, amely valós időben gyűjti a kihagyott képkockák metrikáit. Minden jank-ot a jelenlegi forgatókönyvhöz kapcsol (pl. „lista görgetése”, „képernyő megnyitása”), ami lehetővé teszi annak megértését, hogy pontosan melyik műveletnél következik be az akadozás.
Példa a JankStats integrációjára a lefagyások nyomon követéséhez Androidon:
class MainActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
jankStats = JankStats.create(this.window.decorView) { frameData ->
if (frameData.isJank()) {
Log.w("Jank", "Duration=${frameData.durationMs}ms")
}
}
}
}
Az akadozás megszüntetése célzott munkát igényel minden egyes okkal. Nincs univerzális megoldás — konkrét teljesítményprofilok elemzése szükséges.
Ha a lista 1000+ elemet tartalmaz, és mindegyik egyszerre töltődik be — ez garantált akadozás. A Paging 3 Androidon és az NSFetchedResultsController iOS-en adatokat adagokban tölt be görgetés közben. A felhasználó csak az első 10–20 elemet látja, a többi a háttérben töltődik.
Room lehetővé teszi a lekérdezések profilozását az Inspection Tool segítségével az Android Studio-ban: látható a végrehajtási idő, a visszaadott sorok száma és a lekérdezési terv. Indexek hozzáadása a WHERE és ORDER BY oszlopokhoz 300 ms-ról 5 ms-ra csökkentheti a lekérdezési időt. iOS-en hasonló ellenőrzést végez a Core Data Profiler az Instruments-ben.
Háttér-szinkronizációk, fájlfeltöltések, adatfeldolgozás — mindezt a WorkManager (Android) vagy a Background Tasks (iOS) segítségével kell végrehajtani. Ha a szinkronizáció az UI szálban indul el, az alkalmazás akadozni fog a végrehajtás ideje alatt. A WorkManager garantálja a végrehajtást a háttérszálban, figyelembe véve az akkumulátor és a hálózat állapotát.
Példa háttér-szinkronizációra WorkManager segítségével Androidon:
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Log.d("Sync", "Adatok szinkronizálása háttérszálban")
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
Az akadozás megelőzhető a kódírás szakaszában a hatékony memória- és szálkezelés elveinek követésével.
Baseline Profiles — azon osztályok és metódusok listája, amelyeket az Android előre (AOT) lefordít, nem JIT-ben. Profil nélkül minden új képernyő az első megnyitáskor fordul le, 100–500 ms késleltetést okozva. Készítsen Baseline Profile-t a kulcsfontosságú képernyőkhöz, és kapcsolja be a generálást a Gradle-ben a baseline-profile-gradle-plugin segítségével.
Hot path — kód, amely minden képkockánál végrehajtódik: onBindViewHolder, draw, layoutSubviews. Kerülje az objektumok létrehozását ezekben a metódusokban: használjon objektumpoolt, StringBuilder-et konkatenáció helyett, gyorsítótárazza a formázott karakterláncokat és formázókat. Minden további allokáció közelebb hozza a következő GC-t.
Adja hozzá a CI pipeline-hoz a Macrobenchmark futtatását lista görgetése és képernyő megnyitása forgatókönyvvel. Állítson be küszöböt: a képkockaidő 99. percentilisének nem szabad meghaladnia a 16 ms-ot. Ha a küszöb túllépésre kerül — a build elutasításra kerül az optimalizálásig.
Gyakran ismételt kérdések
Lag — állandó késleltetés (pl. 200 ms minden érintésre). Akadozik — szabálytalan: az alkalmazás normálisan működik, majd hirtelen 1–3 másodpercre lelassul, majd újra normális. Ok — eseménytényezők, mint a GC szünetek vagy a szinkron adatbázis-lekérdezések.
Használja a Memory Profiler-t az Android Studio-ban: a Memory fül megjeleníti a GC eseményeket időtartammal. Éles rendszerű megfigyeléshez csatlakoztassa a Firebase Performance Monitoring-ot egyedi nyomkövetésekkel. iOS-en kapcsolja be a Malloc Debug-ot, és jelölje meg az allokációs generációkat az Instruments-ben.
Közvetve — igen. Ha a szerver válasza késéssel érkezik, és az UI szinkron módon vár rá, az alkalmazás lefagy. Ha a kérés aszinkron, de a válasz feldolgozása az UI szálban történik — ez is akadozást okoz. Megoldás — aszinkron feldolgozás korutinekkel és folyamatjelzőkkel.
Helytelen használat esetén a KMP felesleges wrapper objektumokat generálhat az együttműködéshez. iOS-en ez növeli az allokációk gyakoriságát és következésképpen az ARC szüneteket. Használja a @ObjCName-t, optimalizálja az expect/actual-t, és kerülje a megosztott kód gyakori meghívását az UI forró útvonalairól.
A heap növelése az android:largeHeap="true" segítségével késlelteti a GC-t, de nem szünteti meg az allokációk okát. Amikor a GC végül elindul, a szünet hosszabb lesz, mert több objektumot kell bejárni. Megoldás — csökkentse az allokációk számát, ne növelje a heap-et.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is