Асинхроно вчитување слики во списоци - пристапи и најдобри практики (Дел 2) ПИК! софтвер
Во мојот последен пост веќе презентирав општи потешкотии во врска со процесите на асинхроно вчитување во списоците. Користејќи едноставно сценарио, ја разработив комплексноста на овој случај за употреба без да навлегувам во премногу детали.

Од друга страна, би сакал да го насочам овој напис во нешто повеќе техничка насока - и со тоа да го исполнам моето ветување за откривање на некои најдобри практики и трикови што се докажаа во нашите проекти. Би сакал да покажам колку непречено може да се спроведат списоци за лизгање и да воведам неколку корисни алатки кои можат да ви помогнат во ова. Меѓу редови, ќе разгледам неколку дивергентни пристапи кои обично се препорачуваат, но се исполнети со не баш незначителни проблеми.
Списокот со слаба изведба се манифестира во несакано однесување при скролување, па дури и во продолжено блокирање на целата интеракција на корисникот. Причините за ова се блокирање на повиците кон главната нишка, која понекогаш е одговорна за цртање на елементите за интерфејс или испраќање на настанот. Овие повици не мора секогаш да се прават експлицитно, но исто така можат да бидат резултат на невнимателно управување со меморијата, како што може да се види на слика 1. Секако ова стана многу подобро со воведувањето на Истовремениот колектор за ѓубре во Андроид 2.3 - но сепак е пожелно да се создаваат предмети само кога тоа е навистина потребно, бидејќи се знае дека ова е скапа операција.
Со цел да се пронајдат непотребно распределени објекти, се покажа употребата на Android Alocation Tracker, што е дел од Android SDK - или поточно алатката Dalvik Debug Monitor Server (DDMS). Ова овозможува снимање на сите генерации на објекти во временски прозорец што може да се избере.
На слика 2 е прикажана снимка направена при движење низ список. Означениот запис вели, на пример, дека BitmapFactory 92-бајт (големина на алокација). Објекти со опции (алоцирана класа) е создаден од работна нишка (идентификација на нишка) додека се вчитува слика од кешот (алоциран во). Со соодветно сортирање на одделните колони, непотребните повеќекратни инстанци можат да се откријат релативно лесно.
Оптимизацијата секогаш има смисла ако блок со кодови се повикува често или повеќе пати се создаваат поголеми објекти, како што се тампони. Во нашиот случај, посебно внимание се посветува на методот getView () на адаптерот за списоци. Како и да е, ова се повикува само од главната нишка, предметите можат да се чуваат како варијабли на член - и со тоа повторно да се користат. Моделот ViewHolder веќе следи сличен принцип. Во овој контекст, исто така треба да се провери дали се користат поефикасни структури на податоци може: Примитивните типови на податоци обично се претпочитаат пред соодветните класи на завиткување. Посебно внимание треба да се води и со имплицитно и скапо автоматско боксирање. Сепак, инвеститорот исто така треба да биде запознаен со ново додадените структури на податоци како што се редок низа или кешот LRU (исто така достапен и како класа на компатибилност).
Уште неколку препораки во врска со управувањето со меморијата:
За да може списокот да се движи непречено, мора да се дадат најмалку 25 слики во секунда. Ова одговара на временски прозорец од максимум 40 милисекунди по рамка. Да бидеме малку поедноставно, повикувањето на методот getView () на адаптерот на списокот, вклучувајќи ги сите операции за распоред и рендерирање на хиерархијата на прегледот, може да потрае најмногу 40 милисекунди за да се сфати како флуид. Секое надминување ќе се манифестира во мало, па дури и започнување на пелтечење.
На прв поглед, ова изгледа како управувана задача за современите мобилни процесори. При поблиска проверка, сепак, брзо се сфаќа дека овој пат прозорецот е повеќе од тесен. Пристапот до природните функции, како што е едноставната операција за постоење () на датотеки, може да потроши 25% (= 10ms) од ова време. Истото, исто така, важи и за пристап до базата на податоци. Читањето и особено пристапот до пишување до постојаното складирање е бавен. Покрај тоа, времето на одговор на повиците понекогаш варира енормно. Истиот пристап за пишување до датотечниот систем може да трае 20ms и 2 секунди. Од оваа причина, најдобра практика е да ги пренасочите таквите операции на работни нишки. Строгиот режим, обезбеден од Android SDK, посветува многу големо внимание на усогласеноста со ова правило (ако сакате).
За да пронајдете скапи повици во рамките на главната нишка, користењето на Traceview се докажа. Исто така е дел од Андроид SDK алатките.
Слика 3 покажува пример на извадок од трага што е снимен додека се лизга низ список. Овде можете да видите дека главната нишка веќе е ослободена од дел од работата заради некои други нишки. На пример, Темата-15 моментално чита податоци од поток, додека главната нишка сè уште може да испраќа настани и на тој начин останува реагирачка. Повиците може дополнително да се поделат по потреба, така што ќе добиете прецизна слика за тоа која нишка изведува која операција во кое време или колку се скапи, детално.
Во принцип, треба внимателно да го разгледате времето на главната нишка. Сите операции што го блокираат ова непотребно долго, треба да бидат ангажирани во работни нишки. Од една страна, AsyncTasks се достапни за оваа намена, што веќе ве ослободува од управувањето со базенот со нишки и синхронизацијата со главната нишка. Но, исто така, бројните имплементации на ExecutorService, кои можат да се инстанцираат преку фабриката за извршители, се многу добра и прилагодлива алтернатива. Од друга страна, треба да се воздржите од рачно креирање и стартување на нишки, бидејќи тоа создава превисоки надземни и во најлош случај Падот може дури да доведе до несреќа (брз преглед на 10.000 записи во списокот).
Додека скролувате, голем дел од времето за пресметување се троши на распоредот и операциите за рендерирање на приказот на списокот и неговите прегледи за деца. Бидејќи ова треба да се направи од главната нишка заради моделот со еден конец, тука има и значителен потенцијал за оптимизација. За да го откриеме ова, ние сакаме да го користиме Прегледникот Хирархи, кој исто така е дел од Андроид SDK алатките.
Слика 4 прикажува пример на структура слична на дрво на ListView, чии елементи содржат слика и текст. Во овој поглед, непотребните контејнери за распоред, како и скапите операции за распоред и цртање може да се идентификуваат со помош на индикаторите во боја. Сепак, треба да се забележи дека боите треба да се разберат како релативни, а не апсолутни вредности: Распоредот на рамката овде на сликата има за мерење на нејзината големина (мерка), уредување и усогласување (распоред) и цртање на себе и себе Детските погледи (нацртајте) имаат црвен индикатор, бидејќи за тоа беше потребно 100% од времето за пресметување во рамките на родителскиот приказ - и не поради апсолутно време.
Со цел да се оптимизира изведбата на вашиот сопствен распоред, треба да се почитуваат следниве правила:
- Структурата на дрвото во хиерархијата на прегледот треба да биде што е можно рамна. Со секое гнездење, времето на пресметување за мерка + распоред значително се зголемува.
- Релативниот распоред е помоќен и подобар од, на пример, вгнезден линеарен распоред и секогаш треба да се претпочита пред нив.
- Доколку големината на прегледите е веќе позната во времето на компајлирање, треба да се претпочитаат спецификациите за фиксни димензии во независни густини на пикселите (натопи) од завитканата содржина. Ова донекаде го забрзува процесот на мерење.
- Колку помалку прегледи се користат, толку подобро. Честопати е доволно да поставите една или повеќе сложени цртежи за TextView наместо да користите длабоко вгнезден распоред!
Со цел да се заврши целокупниот изглед на непречено лизгање со списоци, препорачуваме исчезнување и анимирање на анимираните асинхрони активирани ImageViews или ProgressViews. Целокупната интеракција на корисникот се чини неспокојот поради помеките премини. Делот од кодот подолу покажува како тоа може да се направи многу лесно:
Рамката предвидува notifyDataSetChanged за адаптерот што треба да се повика кога ќе се направат промени во податоците (вклучително, на пример, кога е вчитана слика). Ова е сигнал за нив да го прецртаат целиот список на неодредено време во иднина. Иако овој пристап во принцип не е замерен, ние обично одиме на поинаков начин: повратен повик се пренесува на процесот на асинхроно вчитување во рамките на методот getView. Ова е имплементација на внатрешна класа и со тоа се повикува на соодветниот ConvertedView или неговиот ViewHolder. На овој начин гарантираме дека системот треба само да ги прецрта вистинските промени. Покрај тоа, овој пристап е покомпатибилен со приказот на ProgressView, како што брзо ќе видите.
Во овој пост ги покажав најчестите причини за „непредвидливите списоци“. Бидејќи искуството покажа дека овие не можат секогаш да бидат локализирани директно во кодот, јас претставив две алатки што можат да ве поддржат во пребарувањето: „Алокациониот тракер“, за пронаоѓање на непотребно генерирање на предмети и „Трасивју“, за да најдете скапи блокирачки повици Со цел да се спречат овие проблеми да се појават во иднина, јас формулирав некои општи совети кои се докажаа во развојот на нашите проекти.
Во суштина, многу може да се постигне преку внимателно управување со меморијата, доследна употреба на работни нишки, распоред со високи перформанси и соодветно познавање на позадината на односите. Дури и ако бев во можност да допрам само многу во оваа статија, сепак се надевам дека ги исполнив вашите очекувања за ветувањето што го дадов детално и дека можев да ви дадам некои нови впечатоци!