Asynchrónne načítanie obrázkov do zoznamov - prístupy a osvedčené postupy (časť 2) SIC! softvér

V mojom poslednom príspevku som už v zoznamoch predstavil všeobecné ťažkosti spojené s procesmi asynchrónneho načítania. Pomocou jednoduchého scenára som bez prílišných podrobností vypracoval zložitosť tohto prípadu použitia.

načítanie

Na druhej strane by som chcel smerovať tento článok trochu technickejším smerom - a tým splniť svoj sľub odhaliť niektoré osvedčené postupy a triky, ktoré sa osvedčili v našich projektoch. Chcel by som ukázať, ako je možné pomerne ľahko implementovať plynulé posúvanie zoznamov, a predstaviť niekoľko užitočných nástrojov, ktoré vám s tým môžu pomôcť. Medzi riadkami sa budem venovať niekoľkým rozdielnym prístupom, ktoré sa všeobecne odporúčajú, ale sú plné nie úplne nepodstatných problémov.

Zoznam s nízkou výkonnosťou sa prejavuje trhaným posúvaním alebo dokonca dlhodobým blokovaním celej interakcie používateľa. Dôvodom je blokovanie hovorov do hlavného vlákna, ktoré je niekedy zodpovedné za vykreslenie prvkov používateľského rozhrania alebo odoslanie udalosti. Tieto hovory nemusia byť vždy uskutočňované explicitne, ale môžu byť tiež výsledkom neopatrnej správy pamäte, ako je zrejmé z obrázku 1. Zavedením súbežného zberača odpadu v systéme Android 2.3 sa to určite oveľa zlepšilo - stále však stojí za to vytvárať objekty iba v nevyhnutných prípadoch, pretože je známe, že ide o nákladnú operáciu.

Aby bolo možné vypátrať zbytočne pridelené objekty, osvedčilo sa použitie Android Allocation Tracker, ktorý je súčasťou Android SDK - alebo presnejšie nástroja Dalvik Debug Monitor Server (DDMS). To umožňuje zaznamenávanie všetkých generácií objektov vo voľne nastaviteľnom časovom okne.

Obrázok 2 zobrazuje záznam vykonaný pri listovaní zoznamom. Zvýraznená položka napríklad hovorí, že objekt 92 BitmapFactory.Options (veľkosť pridelenia) (Allocation Size) bol vytvorený z pracovného vlákna (Id vlákna), zatiaľ čo sa obraz načítaval z medzipamäte (Allocation in). Zoradením jednotlivých stĺpcov podľa toho možno pomerne ľahko zistiť zbytočné viacnásobné inštancie.

Optimalizácia má vždy zmysel, ak sa blok kódu volá často alebo sa viackrát vytvárajú väčšie objekty, napríklad vyrovnávacie pamäte. V našom prípade sa osobitná pozornosť venuje metóde getView () adaptéra zoznamu. Pretože sa to aj tak volá iba z hlavného vlákna, objekty môžu byť držané ako členské premenné - a teda znova použité. Vzor ViewHolder už sleduje podobný princíp. V tejto súvislosti by sa malo tiež skontrolovať, či sa používajú efektívnejšie dátové štruktúry môže: Primitívne dátové typy majú byť všeobecne uprednostňované pred zodpovedajúcimi triedami obalov. Špeciálna pozornosť musí byť venovaná aj implicitnému a nákladnému autoboxovaniu. Vývojár by však mal poznať aj novo pridané dátové štruktúry, ako je riedke pole alebo vyrovnávacia pamäť LRU (k dispozícii aj ako trieda kompatibility).

Niektoré ďalšie odporúčania týkajúce sa správy pamäte:

Aby sa zoznam mohol vôbec pohybovať plynulo, musí sa vykresliť najmenej 25 snímok za sekundu. To zodpovedá časovému oknu maximálne 40 milisekúnd na snímku. Zjednodušene povedané, vyvolanie metódy getView () adaptéra zoznamu vrátane všetkých operácií rozloženia a vykreslenia v hierarchii zobrazenia môže trvať maximálne 40 milisekúnd, kým bude vnímané ako plynulé. Akékoľvek prekročenie by sa prejavilo miernym alebo dokonca začínajúcim koktaním.

Na prvý pohľad sa to javí ako zvládnuteľná úloha pre moderné mobilné procesory. Pri bližšom skúmaní si však človek rýchlo uvedomí, že toto časové okno je viac ako úzke. Prístup k natívnym funkciám, ako je napríklad jednoduchá operácia so súborom exist (), môže z tohto času spotrebovať 25% (= 10 ms). To isté teda platí aj pre prístup do databázy. Prístup na čítanie a najmä na zápis na trvalé úložisko je pomalý. Navyše, reakčné časy hovorov niekedy enormne kolíšu. Rovnaký prístup na zápis do súborového systému môže trvať 20 ms a 2 sekundy. Z tohto dôvodu je najlepším postupom outsourcovať tieto operácie na pracovné vlákna. Prísny režim, ktorý poskytuje sada Android SDK, venuje veľkú pozornosť dodržiavaniu tohto pravidla (ak je to požadované).

Na sledovanie drahých hovorov v hlavnom vlákne sa osvedčilo použitie Traceview. Je tiež súčasťou nástrojov Android SDK.

Obrázok 3 zobrazuje príklad výpisu stopy, ktorý bol zaznamenaný pri prechádzaní zoznamom. Tu vidíte, že hlavné vlákno už bolo uvoľnené z nejakej práce inými vláknami. Napríklad vlákno-15 momentálne číta dáta z toku, zatiaľ čo hlavné vlákno môže stále odosielať udalosti, a tak zostáva responzívne. Hovory je možné podľa potreby ďalej rozdeliť, aby ste získali veľmi presný obraz o tom, ktoré vlákno vykonáva ktorú operáciu v akom čase, alebo aké podrobné sú drahé.

Všeobecne by ste sa mali bližšie pozrieť na načasovanie hlavného vlákna. Všetky operácie, ktoré to zbytočne dlho blokujú, by sa mali outsourcovať na pracovné vlákna. K tomu sú na jednej strane k dispozícii AsyncTasks, ktoré vás už odbremenia od správy fondu vlákien, ako aj od synchronizácie s hlavným vláknom. Ale tiež početné implementácie ExecutorService, ktoré je možné vytvoriť prostredníctvom triedy Executors Factory, sú veľmi dobrou a prispôsobivou alternatívou. Na druhej strane by ste sa mali zdržať manuálneho vytvárania a spúšťania vlákien, pretože to vytvára príliš vysokú réžiu a v najhoršom prípade Pád môže dokonca viesť k havárii (rýchle prelistovanie 10 000 položiek v zozname).

Pri rolovaní sa veľká časť výpočtového času strávi operáciami rozloženia a vykreslenia zoznamu a jeho podriadených zobrazení. Pretože to musí byť vykonané z hlavného vlákna kvôli modelu s jedným vláknom, existuje tu aj značný potenciál pre optimalizáciu. Aby sme to odhalili, radi by sme použili prehliadač Hirarchy, ktorý je tiež súčasťou nástrojov Android SDK.

Obrázok 4 zobrazuje príklad stromovej štruktúry ListView, ktorej jednotlivé prvky obsahujú obrázok a text. V tomto zobrazení je možné pomocou farebných indikátorov identifikovať nepotrebné kontajnery rozloženia, ako aj nákladné operácie rozloženia a kreslenia. Treba však poznamenať, že farby treba chápať ako relatívne a nie absolútne hodnoty: Rozloženie rámu tu na obrázku má na meranie jeho veľkosti (mieru), usporiadanie a zarovnanie (rozloženie) a samotné kreslenie Pohľady dieťaťa (kreslenie) majú každý červený indikátor, pretože na to bolo potrebných 100% výpočtového času v rámci nadradeného pohľadu - a nie kvôli absolútnemu času.

S cieľom optimalizovať výkonnosť vlastného rozloženia by sa mali dodržiavať nasledujúce základné pravidlá:

  • Stromová štruktúra hierarchie pohľadu by mala byť čo najrovnejšia. S každým vnorením sa výpočtový čas pre opatrenie + rozloženie výrazne zvyšuje.
  • RelativeLayout je výkonnejší a výkonnejší ako napríklad vnorené lineárne rozloženia a vždy by sa im malo dať prednosť.
  • Ak je veľkosť zobrazení už v čase kompilácie známa, mali by sa pred wrap_content uprednostniť špecifikácie pevnej veľkosti v pixeloch nezávislých od hustoty (dip). To trochu zrýchľuje proces merania.
  • Čím menej zobrazení sa použije, tým lepšie. Často stačí nastaviť jeden alebo viac zložených výkresov pre TextView namiesto použitia hlboko vnoreného rozloženia!

Na dokončenie celkového vzhľadu zoznamu s plynulým posúvaním odporúčame roztiahnutie a oddialenie animovaných asynchrónnych spustených obrazov ImageView alebo ProgressViews. Vďaka jemnejším prechodom sa celá interakcia používateľa javí plynulejšia. Fragment kódu nižšie ukazuje, ako sa to dá veľmi ľahko urobiť:

Rámec umožňuje, aby sa pri zmene údajov (tzv. Pri načítaní obrázka) zavolalo notifyDataSetChanged adaptéra. Toto je signál pre nich, aby v budúcnosti prekreslili celý zoznam na neurčitý čas. Aj keď tento prístup nie je v zásade nežiaduci, zvyčajne ideme iným spôsobom: Spätné volanie sa odovzdá procesu asynchrónneho načítania v rámci metódy getView. Toto je implementácia vnútornej triedy, a teda obsahuje odkaz na príslušný ConvertedView alebo jeho ViewHolder. Týmto spôsobom zabezpečíme, že systém musí prekresliť iba skutočné zmeny. Tento prístup je navyše kompatibilnejší s displejom ProgressView, ako rýchlo uvidíte.

V tomto príspevku som ukázal najbežnejšie príčiny „trhaných zoznamov“. Pretože skúsenosti ukazujú, že tieto nie je možné vždy lokalizovať priamo v kóde, predstavil som dva nástroje, ktoré vás môžu podporiť pri hľadaní: The Allocation Tracker na sledovanie generovania nepotrebných objektov a Traceview na vyhľadanie drahých blokujúcich hovorov. Aby som zabránil vzniku týchto problémov v budúcnosti, sformuloval som niekoľko všeobecných rád, ktoré sa osvedčili pri vývoji našich projektov.

Veľa možno v zásade dosiahnuť premyslenou správou pamäte, dôsledným používaním pracovných vlákien, vysokovýkonným rozložením a príslušnými znalosťami vzťahov na pozadí. Aj keď som sa v tomto článku mohol toho veľa dotknúť, stále dúfam, že som splnil vaše očakávania týkajúce sa sľubu, ktorý som dal podrobne, a že som vám mohol poskytnúť zopár nových dojmov.!