Android rant

Вчера за един ден успях да се натъкна на три бъга в Андроид, от които два са супер сериозни и неоправени, третият е просто демонстрация за мърлявщината на създателите на Андроид.
Бъг 1
Най-най-най елементарният компонент във всяка библиотека и среда за визуални компоненти е етикетът (label). Това е статичен текст, който не се редактира от потребителя и единствената му функция е да бъде текст на екрана. Тези етикети във всички езици, които съм виждал са обекти, каквито са и всички останали компоненти. Обектите имат свойства като размер, цвят, позиция и т.н. и напълно очаквано едно от свойствата на етикетите да бъде шрифтът на текста. Тук трябва да отворим една скоба и да кажем, че в Андроид създаването на интерфейса става по един от два начина. Единият е от програмният текст, другият е чрез описание в XML файл. Това също е проблем, защото двата метода за създаване на интерфейса не са напълно заменяеми. Има неща които могат да бъдат направени само от кода. И се получава много неприятно, когато си започнал с XML и на 25 компонент разбираш, че нещо можеш да го направиш, само ако го създаваш този компонент в кода. Та един такъв статичен компонент като етикета, който не връща обратна връзка от потребителя, е напълно логично да се създава от XML и там да са описани всички свойства, нали? Е да, ама не. Шрифтът на етикета може да се определи само програмно, с няколко реда малоумен текст от типа на:
Typeface font = Typeface.createFromAsset(getAssets(), "robotolight.ttf");
txt.setTypeface(font);

Но това не е причината за възмущението ми, неее. Това, че ако имаш 20 етикета трябва 20 пъти да настройваш шрифта на всеки от тях програмно е горчив, но преглъщаем хап. Това, което ми дойде в повече е когато създадох ListView от 25 елемента, установявайки шрифта на елементите в списъка на определения от дизайнерката и забелязах, че като го скролирам нагоре-надолу (пак повтарям, списъкът е от 25 статични елемента), при всяко превъртане на списъка заеманата памет се увеличава с 50 MB! След кратко четене установих, че това е известен бъг, който Гугъл очевидно не са си направили труда да поправят. Всеки път като установявате шрифта с горния текст се заделя отново памет за Typeface-а или там нещо друго и така ако имаш 8 бутона и 25 етикета в списък ще заделиш тая памет 33 пъти. И приложението ти почва да заема памет като че ще изстрелва ракети в космоса. (N.B. Това всъщност е невярно сравнение. Хората са изстрелвали ракети в космоса с изчислителна мощ колкото електронен ръчен часовник. Не смартфон. Не тъп телефон. Не калкулатор. Електронен часовник!!!) Всъщност имало някакъв много завъртян начин да установяваш шрифта на компонентите и от XML, но той не е препоръчителен, тъй като тогава със сигурност за всеки компонент ще се заделя тази памет и програмистът няма контрол върху процеса. Известен бъг, за който от Гугъл са си го преместили от левия в десния…
Вторият МЕГА БРУТАЛЕН бъг, който е СУПЕР известен, е свързан с изображенията. Сега тук забавното започва с това, че в Java приложението казва на виртуалната машина предварително колко памет максимално ще заеме. Но в Андроид тези стойности (на heap-а) са установени от Гугъл и/или от разработчиците на съответното устройство и не може да бъде променян без да си прекомпилираш андроид-а или да си root-неш телефона (Cyanogen позволявал потребителя да задава отделна стойност на heap-а на всяко приложение, уау, бахти гъзарията!). И размерите на heap-а не са никак големи, меко казано. Стойностите са определяни предимно на база размера на екрана (което най-вероятно е свързано със самия бъг, за който става въпрос), като на най-малките устройства тази памет е само 16 MB! При условие, че не съм чувал за устройство с Андроид с по-малко от 256 MB RAM и повечето са с 512, 768, 1024 и вече има и с 2048. Максималната стойност, установявана е 64 MB, което е предимно на таблети. (Samsung Galaxy Tab 2 e с heap 48 MB). Защо са такива малки размерите на heap-а ли? Ами обясненията са, че:
А. Като ти дадат повече памет, използваш повече памет! А като използваш повече памет, Garbage collector-а работи повече по освобождаването й. И тъй като garbage collector-ът не е най-ефективното нещо на света, то по-добре малко памет. Така де, по-добре да се гърчат милионите разработчици на андроид, отколкото петимата капута, които работят в Гугъл и пишат GC-то на андроид, нали? (Не твърдя, че това е официалната позиция на Гугъл за размерите на heap-а, но е аргумент, използван из форумите)
Б. Защото гениалният Андроид по принцип е направен така, че спирането на приложението е трудно. Андроид не иска да спираш приложения. Той иска само да ги пращаш назад в стека, във фонов режим, дори когато нямаш намерение да ги ползваш следващата година. И защото това пълни паметта с много работещи едновременно приложения, нека им дадем малко количество максимално допустима памет. Така де, вместо да имаме три активни приложения, които могат да работят като хората, нека имаме 30, от които 29 всъщност не се използват в момента, но заради лукса да ги имаме някъде там отдолу в стека, 30-тото приложение, което е на фокус и потребителят ползва в момента ще се гърчи, защото трябва да се ограничава от използването на RAM! Не е ли гениално?
И сега идва интересната част. Във вашето приложение искате да сложите картинки? Чудесно, поставяте ги като ресурси в проекта и си ги четете от там. Но независимо какъв тип са (jpg, png, и т.н.) веднъж заредени, андроид ги обръща в bitmap, и ги зарежда в паметта. В heap-а. Некомпресирани. И така, ако имате изображение 300×300, с 24 бита за цвят, то от паметта за приложението ви са изядени малко повече от 2 MB. А, и тъй като не вие заделяте паметта (все пак java е автоматично управление на паметта), то не можете да я освободите. Поне не директно. Тоест всяко изображение, заредено на което е да е място във вашето приложение седи в ограничената heap памет, независимо дали ползвате това изображение или не. И ако имате много изображения, много бързо изяждате тази силно ограничена памет, отредена на вашето приложение. А като се препълни паметта, приложението гърми с out of memory error. Ако не ми вярвате колко сериозен е проблемът, просто напишете out of memory error android в Гугъл и ще видите, че на първата страница почти всички сайтове коментират именно изображенията. Дефинитивно решение на проблема няма. Препоръчва се да се използват по-малки като размер изображения (компресията на jpg или png няма значение, само размерът), както и ако има възможност след използването на изображение да се извиква .recycle() , или .recycle() да се допълни с
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];
options.inPurgeable = true;

или някакви други врътки.

Можете да направите и като мен – да разберете колко е наличната памет със следния код:
ActivityManager activityManager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
mem=activityManager.getMemoryClass();

и после на разни места да зареждате различни изображения или различни layout-и в зависимост от размера на heap-а:

if (heap>20){
resourceId = res.getIdentifier(recipePicsBig[position], "drawable", "com.xxxxxxx");
holder.recipeImage.setImageResource(resourceId);

или
super.onCreate(savedInstanceState);
extras = getIntent().getExtras();
int heap=extras.getInt("heapSize");
if (heap>20)
this.setContentView(R.layout.single_list_item_view);
else
this.setContentView(R.layout.simple_single_list_item_view);

В обобщение Андроид заема памет за изображенията колкото е некомпресираният им размер и не я освобождава, а програмистът трябва да прави някакви фокуси, за да го излъже да освободи тази памет. Или за по-просто да не ползва изображения, очевидно в 21 век и 8 ядрените телефони според Гугъл те са скъп лукс.
Третият бъг (вече оправен), открих докато четях за управлението на паметта в Андроид и техниките за дебъгване на използваната памет. Един пич е открил, че в gmail приложението се създават няколко стотин!!! празни правоъгълника, които са с нулеви размери. Не заемат много памет, но все пак са стотици. И очевидно не се ползват за нищо, няма и как, защото са с нулеви размери. След публикуването на откритието си, около два часа след това, от Гугъл оправят нещата, но все пак това е седяло (най-вероятно) с години. В андроидско приложение, написано от хората, които притежават и разработват андроид!
И така, в обобщение, за да направя нещо толкова елементарно като скролируем списък с 25 елемента с малка картинка и текст до нея трябва да създам нов клас и да разширявам стандартния скролируем списък, тъй като той не поддържа изображения, този списък не може да има custom шрифт, защото това ще яде паметта като тасманийски дявол, и на всичкото отгоре изображенията в него ще изяждат още повече памет, като няма достъпен начин за освобождаване на паметта от непоказаните в даден момент изображения или нещо такова.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.