В Android само основната нишка на приложението има право да опреснява екрана и да обработва докосванията на екрана. Това означава, че когато приложението ви върши сложна работа в основната нишка, то няма шанс да реагира на кликвания. За потребителя приложението ще изглежда като увиснало. В този случай отново премахването на сложни операции към отделни нишки ще помогне.
Има обаче един много по-фин и неочевиден момент. Android опреснява екрана при 60 FPS. Това означава, че когато показва анимации или превърта през списъци, има само 16,6 ms за показване на всеки кадър. В повечето случаи Android се справя с тази работа и не губи рамки. Но едно неправилно написано приложение може да го забави.
Ами сега, пропуснахме кадърПрост пример: RecyclerView е интерфейсен елемент, който ви позволява да създавате изключително дълги списъци с възможност за превъртане, които заемат едно и също количество памет, независимо от дължината на самия списък. Това е възможно поради повторното използване на едни и същи набори от интерфейсни елементи (ViewHolder) за показване на различни елементи от списъка. Когато елемент от списък е скрит от екрана, неговият ViewHolder се премества в кеша и след това се използва за показване на следващите елементи от списъка.
Когато RecyclerView извлече ViewHolder от кеша, той извиква метода onBindViewHolder() на вашия адаптер, за да го попълни с данните на конкретния елемент от списъка. И тук се случва нещо интересно: ако методът onBindViewHolder() свърши твърде много работа, RecyclerView няма да има време да формира следващия елемент за показване навреме и списъкът ще започне да се забавя по време на превъртане.
Друг пример. Можете да свържете персонализиран RecyclerView.OnScrollListener() към RecyclerView, чийто метод OnScrolled() ще бъде извикан, когато списъкът се превърта. Обикновеноизползва се за динамично скриване и показване на кръглия бутон за действие в ъгъла на екрана (FAB — плаващ бутон за действие). Но ако внедрите по-сложен код в този метод, списъкът отново ще се забави.
И третият пример. Да предположим, че интерфейсът на вашата програма се състои от много фрагменти, между които можете да превключвате с помощта на страничното меню (Чекмедже). Изглежда, че най-очевидното решение на този проблем е да поставите нещо като този код в манипулатора за щракване на елемент от менюто:
// Превключване на фрагмент getSupportFragmentManager .beginTransaction() .replace(R.id.container, fragment, “fragment”) .commit()
// Затваряне на чекмеджето drawer.closeDrawer(GravityCompat.START)
Всичко е логично, но само ако стартирате приложението и го тествате, ще видите, че анимацията на затваряне на менюто се забавя. Проблемът се крие във факта, че методът commit() е асинхронен, тоест превключването между фрагменти и затварянето на менюто ще се случи едновременно и смартфонът просто няма да има време да обработи всички операции за актуализиране на екрана навреме.
За да предотвратите това да се случи, трябва да превключите фрагмента, след като приключи анимацията за затваряне на менюто. Можете да направите това, като свържете персонализиран DrawerListener към менюто:
mDrawerLayout.addDrawerListener(нов DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(View drawerView, float slideOffset) {} @Override public void onDrawerOpened(View drawerView) {} @Override public void onDrawerStateChanged(int newState) {}
@Override public void onDrawerClosed(View drawerView) { if (mFragmentToSet != null) { getSupportFragmentManager() .beginTransaction() .replace(R. id.container, mFragmentToSet) .commit(); mFragmentToSet = null; });
| Повече ▼един съвсем не очевиден момент. Започвайки с Android 3.0, изобразяването на интерфейса на приложението се извършва от графичния процесор. Това означава, че всички растерни изображения, чертежи и ресурси, посочени в темата на програмата, се зареждат в GPU паметта и следователно достъпът до тях е много бърз.
Всеки елемент от интерфейса, показан на екрана, се преобразува в набор от полигони и GPU-инструкции и следователно се показва в случай на, например, бързо изтриване. Изгледът ще бъде скрит и показан също толкова бързо чрез промяна на атрибута за видимост (button.setVisibility(View.GONE) и button.setVisibility(View.VISIBLE)).
Но когато промените изгледа, дори и най-малкия, системата ще трябва да създаде изгледа отново от нулата, зареждайки нови полигони и инструкции в GPU. Освен това, когато промените TextView, тази операция ще стане още по-скъпа, тъй като Android първо ще трябва да растеризира шрифта, тоест да преобразува текста в набор от правоъгълни изображения, след това да направи всички измервания и да генерира инструкции за GPU. Освен това има операция за изчисляване на позицията на текущия елемент и други елементи на оформлението. Необходимо е да вземете предвид всичко това и да промените изгледа само когато наистина е необходимо.
Превишаването е друг сериозен проблем. Както казахме по-горе, анализирането на сложни оформления с много вложени елементи ще бъде бавно само по себе си, но също така със сигурност ще доведе до проблема с честото преначертаване на екрана.
Представете си, че имате няколко вложени LinearLayouts и някои от тях също имат свойството background, тоест те не само съдържат други елементи на интерфейса, но също така имат фон под формата на картина или изпълнен с цвят. В резултат на това на етапа на изобразяване на интерфейса графичният процесор ще направи следното: запълва областта, заета от основния LinearLayout, с пиксели от желания цвят, след което запълвачастта от екрана, заета от вложеното оформление, друг цвят (или същия) и т.н. В резултат на това по време на изобразяването на един кадър много пиксели на екрана ще бъдат актуализирани няколко пъти. И това няма никакъв смисъл.
Невъзможно е напълно да се избегне овърдрайв. Например, ако трябва да покажете бутон на червен фон, пак ще трябва първо да запълните екрана с червено и след това да преначертаете пикселите, които представляват бутона. Освен това Android знае как да оптимизира изобразяването, така че да не се получава преначертаване (например, ако два елемента с еднакъв размер са един върху друг и вторият е непрозрачен, първият просто няма да бъде изчертан). Много обаче зависи от програмиста, който трябва да се опита с всички сили да минимизира преизчислението.
Инструментът за отстраняване на грешки при наслагване ще помогне с това. Той е вграден в Android и се намира тук: Настройки ? Опции за разработчици? Отстраняване на грешки при надчертаване на GPU? Показване на надчертани области. След като го включите, екранът ще бъде пребоядисан в различни цветове, което означава следното:
- нормален цвят — едно наслагване;
- синьо - двойно наслагване;
- зелено - тройно;
- червено - четвъртото и повече.
Правилото тук е просто: ако по-голямата част от интерфейса на вашата програма е станала зелена или червена, имате проблеми. Ако виждате предимно синьо (или родния цвят на програмата) с малки петна от зелено и червено, където се показват различни превключватели или други динамични елементи на интерфейса, всичко е наред.
Преувеличете здравословната програма и преувеличете пушача
И няколко съвета:
- Опитайте се да не използвате свойството background в оформленията.
- Намалете броя на вложените оформления.
- Вмъкнете следния ред в началото на кода на дейността: getWindow().setBackgroundDrawable(null);.
- Неизползвайте прозрачност там, където можете и без нея.
- Използвайте инструмента за преглед на йерархията, за да анализирате йерархията на вашите оформления, техните връзки помежду си, да оцените скоростта на изобразяване и да изчислите размерите.