A képek aszinkron betöltése a listákra - megközelítések és bevált gyakorlatok (2. rész) SIC! szoftver
Utolsó bejegyzésemben már általános nehézségeket mutattam be az aszinkron betöltési folyamatokkal kapcsolatban a listákban. Egyszerű forgatókönyv segítségével kidolgoztam ennek a felhasználási esetnek a bonyolultságát anélkül, hogy túlságosan részletezném.

Másrészt szeretném ezt a cikket valamivel technikai irányba terelni - és ezzel teljesíteni ígéretemet, hogy felfedek néhány bevált gyakorlatot és trükköt, amelyek beváltak a projektjeinkben. Szeretném bemutatni, hogy a görgetési listák milyen gördülékenyen valósíthatók meg viszonylag egyszerűen, és bemutatok néhány hasznos eszközt, amelyek segíthetnek ebben. A sorok között néhány, általánosan ajánlott, de nem teljesen jelentéktelen problémával járó, eltérő megközelítést tárgyalok.
A rosszul teljesítő lista rángatózó görgetési viselkedésben, vagy akár a teljes felhasználói interakció elhúzódó blokkolásában nyilvánul meg. Ennek oka a fő szál hívásainak blokkolása, amely néha felelős az UI elemek rajzolásáért vagy az események diszpécseréért. Ezeket a hívásokat nem mindig kell kifejezetten megtenni, hanem gondatlan memóriakezelés eredményei is lehetnek, amint az az 1. ábrán látható. Természetesen ez sokkal jobb lett az egyidejű szemétgyűjtő Android 2.3-as bevezetésével - de még mindig érdemes csak akkor létrehozni tárgyakat, amikor ez valóban szükséges, mivel ez köztudottan drága művelet.
A feleslegesen kiosztott objektumok felkutatása érdekében bebizonyosodott az Android Allocation Tracker használata, amely az Android SDK - pontosabban a Dalvik Debug Monitor Server (DDMS) eszköz része. Ez lehetővé teszi az összes objektumgeneráció rögzítését egy szabadon választható időablakban.
A 2. ábra egy lista görgetése közben készített felvételt mutatja. A kiemelt bejegyzés például azt mondja, hogy egy 92 bájtos (allokációs méret) BitmapFactory.Options objektum (Allocated Class) munkásszálból (Thread Id) jött létre, miközben képet töltöttek a cache-ből (Allocated in). Az egyes oszlopok megfelelő rendezésével a felesleges többszörös példányok viszonylag könnyen észlelhetők.
Az optimalizálásnak mindig van értelme, ha egy kódblokkot gyakran hívnak, vagy nagyobb objektumokat, például puffereket hoznak létre többször. Esetünkben különös figyelmet fordítunk a listaadapter getView () metódusára. Mivel ezt egyébként csak a fő szálból hívják meg, az objektumok tagváltozóként tarthatók - és így újra felhasználhatók. A ViewHolder minta már hasonló elvet követ. Ebben az összefüggésben azt is ellenőrizni kell, hogy hatékonyabb adatstruktúrákat használnak-e. lehet: A primer adattípusokat általában előnyben kell részesíteni a megfelelő burkolóosztályokkal szemben. Különös gondot kell fordítani az implicit és drága autoboxolásra is. De a fejlesztőnek ismernie kell az újonnan hozzáadott adatstruktúrákat is, például a ritka tömböt vagy az LRU gyorsítótárát (kompatibilitási osztályként is elérhető).
Néhány további javaslat a memóriakezeléssel kapcsolatban:
Ahhoz, hogy egy lista gördülékenyen gördüljön, másodpercenként legalább 25 képet kell megjeleníteni. Ez egy képkockánként legfeljebb 40 milliszekundumú időablaknak felel meg. Némileg leegyszerűsítve: a listaadapter getView () metódusának meghívása, beleértve a nézet hierarchiájának összes elrendezési és megjelenítési műveletét, legfeljebb 40 milliszekundumig tarthat, amíg folyékonynak érzékelhető. Bármely túllépés enyhe vagy akár kezdő dadogásban nyilvánul meg.
Első pillantásra ez kezelhető feladatnak tűnik a modern mobil processzorok számára. Jobban megnézve azonban hamar rájön, hogy ez az időablak több mint szűk. A natív függvényekhez, például egy egyszerű (()) fájlművelethez való hozzáférés ennek az időnek a 25% -át (= 10 ms) emésztheti fel. Ugyanez vonatkozik tehát az adatbázis-hozzáférésre is. Lassú az állandó tárhely olvasási és írási hozzáférése. Ezenkívül a hívások válaszideje időnként rendkívül ingadozik. A fájlrendszerhez ugyanaz az írási hozzáférés 20 ms és 2 másodpercet vehet igénybe. Ezért a legjobb gyakorlat az ilyen műveletek kiszervezése működő szálakra. Az Android SDK által biztosított szigorú mód nagyon odafigyel a szabály betartására (ha szükséges).
A drága hívások felkutatásához a fő szálon belül a Traceview használata bevált. Az Android SDK Tools része is.
A 3. ábra egy példa egy nyomrészletre, amelyet egy lista görgetése közben rögzítettünk. Itt láthatja, hogy a fő szálat néhány munka már feloldotta a munka egy részéről. Például a Thread-15 jelenleg adatokat olvas le egy adatfolyamról, miközben a fő szál továbbra is képes továbbítani az eseményeket, és így továbbra is reagál. A hívások tovább bonthatók szükség szerint, így nagyon pontos képet kap arról, hogy melyik szál melyik műveletet melyik időpontban hajtja végre, vagy ezek mennyire drágák részletesen.
Általában alaposabban meg kell vizsgálnia a fő szál időzítését. Minden műveletet, amely ezt feleslegesen hosszan blokkolja, működő szálakra kell kiszervezni. Egyrészt elérhetőek erre a célra az AsyncTasks, amelyek már mentesítik a szálkészlet kezelésétől és a fő szálral való szinkronizálástól. De az ExecutorService számos megvalósítása, amelyek az Executors Factory osztályon keresztül példányosíthatók, nagyon jó és alkalmazkodó alternatíva. Másrészt tartózkodnia kell a szálak manuális létrehozásától és indításától, mivel ez túl magas rezsit és a legrosszabb esetben is A bukás akár összeomláshoz is vezethet (gyors áthaladás 10 000 lista bejegyzésén).
Görgetés közben a számítási idő nagy részét a listanézet és annak gyermeknézeteinek elrendezési és megjelenítési műveleteire fordítják. Mivel ezt az egyszálas modell miatt a főszálból kell megtenni, itt is jelentős optimalizálási lehetőség rejlik. Ennek feltárásához a Hirarchy Viewer-t szeretnénk használni, amely szintén az Android SDK Tools része.
A 4. ábra egy példát mutat a ListView fa-szerű felépítésére, amelynek elemei mindegyike tartalmaz képet és szöveget. Ebben a nézetben szükségtelen elrendezési konténerek, valamint drága elrendezési és rajzolási műveletek azonosíthatók a színes indikátorok segítségével. Meg kell azonban jegyezni, hogy a színeket relatív, és nem abszolút értékként kell érteni: A kép elrendezésének itt a képen van a mérete (mérése), elrendezése és igazítása (elrendezés), valamint önmagának és önmagának a rajzolása A gyermek nézetek (rajzok) mindegyikének piros a mutatója, mivel ehhez a szülő nézeten belüli számítási idő 100% -a szükséges - és nem az abszolút idő miatt.
Saját elrendezésének optimalizálása érdekében a következő alapszabályokat kell betartani:
- A nézet hierarchia faszerkezetének a lehető leglaposabbnak kell lennie. Minden fészkeléskor a mérés + elrendezés számítási ideje jelentősen megnő.
- A RelativeLayout erőteljesebb és teljesítőbb, mint például a beágyazott lineáris elrendezések, ezért mindig előnyben kell részesíteni őket.
- Ha a nézetek mérete már a fordítás idején ismert, akkor a sűrűségtől független képpontokban (dip) rögzített méretű specifikációkat kell előnyben részesíteni a wrap_content helyett. Ez némileg felgyorsítja az intézkedés folyamatát.
- Minél kevesebb nézetet használnak, annál jobb. Gyakran elegendő egy vagy több összetett rajzot beállítani a TextView számára mélyen beágyazott elrendezés használata helyett!
A gördülékenyen gördülő listák teljes megjelenése érdekében javasoljuk az animált aszinkron kiváltott ImageViews vagy ProgressViews be- és kikapcsolását. A teljes felhasználói interakció gördülékenyebbnek tűnik a lágyabb átmenetek miatt. Az alábbi kódrészlet megmutatja, hogyan lehet ezt nagyon egyszerűen megtenni:
A keretrendszer előírja, hogy az illesztő értesítenie kell az DataDataSetChangedet az adatok módosításakor (ideértve például egy kép betöltését is). Ez jelzés számukra, hogy a jövőben egy határozatlan időpontban újrarajzolják a teljes listát. Bár ez a megközelítés elvileg nem kifogásolható, általában más utat járunk be: Visszahívást adunk át az aszinkron betöltési folyamatnak a getView módszeren belül. Ez egy belső osztály megvalósítása, és így hivatkozást tartalmaz a megfelelő ConvertedView-ra vagy annak ViewHolder-jére. Ily módon biztosítjuk, hogy a rendszernek csak át kell rajzolnia a tényleges változásokat. Ez a megközelítés ráadásul jobban kompatibilis a ProgressView megjelenítésével, amint azt gyorsan meg fogja látni.
Ebben a bejegyzésben bemutattam a „rángatózó listák” leggyakoribb okait. Mivel a tapasztalatok azt mutatják, hogy ezeket nem mindig lehet közvetlenül a kódban lokalizálni, bemutattam két eszközt, amelyek támogathatják Önt a keresésben: Az Allocation Tracker a felesleges objektum-előállítás felkutatásához és a Traceview a drága, blokkoló hívások megtalálásához. Annak érdekében, hogy ezek a problémák ne fordulhassanak elő a jövőben, megfogalmaztam néhány általános tanácsot, amely bevált projektjeink kidolgozásában.
Alapvetően sok mindent el lehet érni átgondolt memóriakezeléssel, a munkaszálak következetes használatával, nagy teljesítményű elrendezésekkel és a kapcsolatok megfelelő háttérismeretével. Még ha csak nagyon sokat is tudtam érinteni ebben a cikkben, akkor is remélem, hogy teljesítettem az elvárásait az általam részletesen említett ígérettel kapcsolatban, és új benyomásokat tudtam adni nektek.!