În Android, numai firul principal al aplicației are dreptul de a reîmprospăta ecranul și de a gestiona atingerile de pe ecran. Aceasta înseamnă că atunci când aplicația dvs. efectuează lucrări complexe în firul principal, nu are șansa de a răspunde la clicuri. Pentru utilizator, aplicația va părea a fi suspendată. În acest caz, încă o dată, eliminarea operațiunilor complexe pentru fire separate va ajuta.
Există, totuși, un punct mult mai subtil și neevident. Android reîmprospătează ecranul la 60 FPS. Aceasta înseamnă că atunci când afișați animații sau derulați prin liste, are doar 16,6 ms pentru a afișa fiecare cadru. În cele mai multe cazuri, Android face față acestei lucrări și nu pierde cadre. Dar o aplicație scrisă incorect o poate încetini.
Hopa, am ratat un cadruUn exemplu simplu: RecyclerView este un element de interfață care vă permite să creați liste extrem de lungi care pot derula, care ocupă aceeași cantitate de memorie, indiferent de lungimea listei în sine. Acest lucru este posibil datorită reutilizarii acelorași seturi de elemente de interfață (ViewHolder) pentru a afișa diferite elemente de listă. Când un element din listă este ascuns de pe ecran, ViewHolder-ul său este mutat în cache și apoi folosit pentru a afișa elementele ulterioare din listă.
Când RecyclerView preia ViewHolder din cache, apelează metoda onBindViewHolder() a adaptorului dvs. pentru a-l popula cu datele articolului specific din listă. Și aici se întâmplă ceva interesant: dacă metoda onBindViewHolder() va funcționa prea mult, RecyclerView nu va avea timp să formeze următorul element pentru afișare la timp și lista va începe să încetinească în timpul derulării.
Alt exemplu. Puteți conecta un RecyclerView.OnScrollListener() personalizat la RecyclerView, a cărui metodă OnScrolled() va fi apelată când lista este derulată. De obiceieste folosit pentru a ascunde și afișa în mod dinamic butonul de acțiune rotund în colțul ecranului (FAB — Buton de acțiune plutitor). Dar dacă implementați un cod mai complex în această metodă, lista va încetini din nou.
Și al treilea exemplu. Să presupunem că interfața programului dvs. este formată din multe fragmente, între care puteți comuta folosind meniul lateral (Drawer). Se pare că cea mai evidentă soluție la această problemă este să puneți ceva de genul acestui cod în elementul de meniu click handler:
// Comutați fragmentul getSupportFragmentManager .beginTransaction() .replace(R.id.container, fragment, „fragment”) .commit()
// Închide sertarul sertar.closeDrawer(GravityCompat.START)
Totul este logic, dar numai dacă lansați aplicația și o testați, veți vedea că animația de închidere a meniului încetinește. Problema constă în faptul că metoda commit() este asincronă, adică comutarea între fragmente și închiderea meniului vor avea loc simultan, iar smartphone-ul pur și simplu nu va avea timp să proceseze toate operațiunile de actualizare a ecranului la timp.
Pentru a preveni acest lucru, trebuie să comutați fragmentul după ce animația de închidere a meniului s-a terminat. Puteți face acest lucru conectând un DrawerListener personalizat la meniu:
mDrawerLayout.addDrawerListener(nou DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(Vizualizare drawerView, float slideOffset) {} @Override public void onDrawerOpened(Vizualizare drawerView) {} @Override public void onDrawerStateChanged(int newState) {}
@Override public void onDrawerClosed(Vizualizare sertarView) { dacă (mFragmentToSet != null) { getSupportFragmentManager() .beginTransaction() .replace(R. id.container, mFragmentToSet) .commit(); mFragmentToSet = null; });
Mai multun punct deloc evident. Începând cu Android 3.0, redarea interfeței aplicației are loc pe procesorul grafic. Aceasta înseamnă că toate hărțile de bit, desenele și resursele specificate în tema programului sunt încărcate în memoria GPU și, prin urmare, accesul la ele este foarte rapid.
Orice element al interfeței afișat pe ecran este convertit într-un set de poligoane și instrucțiuni GPU și, prin urmare, este afișat în cazul, de exemplu, ștergerea rapidă. Vizualizarea va fi ascunsă și afișată la fel de rapid prin modificarea atributului de vizibilitate (button.setVisibility(View.GONE) și button.setVisibility(View.VISIBLE)).
Dar la schimbarea Vizualizării, chiar și a celei mai mici, sistemul va trebui să creeze View-ul din nou de la zero, încărcând noi poligoane și instrucțiuni în GPU. Mai mult, la schimbarea TextView, această operațiune va deveni și mai costisitoare, deoarece Android va trebui mai întâi să rasterizeze fontul, adică să transforme textul într-un set de imagini dreptunghiulare, apoi să facă toate măsurătorile și să genereze instrucțiuni pentru GPU. Și există, de asemenea, o operație pentru a calcula poziția elementului curent și a altor elemente de aspect. Este necesar să țineți cont de toate acestea și să schimbați View-ul doar atunci când este cu adevărat necesar.
Overdraw este o altă problemă serioasă. După cum am spus mai sus, analizarea layout-urilor complexe cu multe elemente imbricate va fi lentă în sine, dar cu siguranță va aduce cu sine și problema redesenării frecvente a ecranului.
Imaginați-vă că aveți mai multe LinearLayouts imbricate, iar unele dintre ele au și proprietatea de fundal, adică nu numai că conțin alte elemente de interfață, ci au și un fundal sub forma unei imagini sau umplut cu culoare. Ca urmare, în etapa de redare a interfeței, GPU-ul va face următoarele: umple zona ocupată de rădăcina LinearLayout cu pixeli de culoarea dorită, apoi umplepartea de ecran ocupată de aspectul imbricat, altă culoare (sau aceeași) și așa mai departe. Ca rezultat, în timpul redării unui cadru, mulți pixeli de pe ecran vor fi actualizați de mai multe ori. Și asta nu are niciun sens.
Este imposibil să evitați complet overdraw. De exemplu, dacă trebuie să afișați un buton pe un fundal roșu, va trebui totuși să umpleți mai întâi ecranul cu roșu și apoi să redesenați pixelii care reprezintă butonul. În plus, Android știe cum să optimizeze randarea, astfel încât să nu apară overdraw (de exemplu, dacă două elemente de aceeași dimensiune sunt unul deasupra celuilalt, iar al doilea este opac, primul pur și simplu nu va fi desenat). Totuși, multe depind de programator, care trebuie să încerce cu toată puterea să minimizeze overdraw.
Instrumentul de depanare a suprapunerii va ajuta în acest sens. Este încorporat în Android și se află aici: Setări ? Opțiuni pentru dezvoltatori? Depanați suprasolicitarea GPU-ului? Afișați zonele de supraîncărcare. După pornire, ecranul va fi revopsit în diferite culori, ceea ce înseamnă următoarele:
- culoare normală - o singură suprapunere;
- albastru - suprapunere dubla;
- verde - triplu;
- roșu - al patrulea și mai mult.
Regula aici este simplă: dacă cea mai mare parte a interfeței programului tău a devenit verde sau roșie, ai probleme. Dacă vedeți în principal albastru (sau culoarea nativă a programului) cu mici stropi de verde și roșu unde sunt afișate diverse comutatoare sau alte elemente dinamice ale interfeței, totul este în regulă.
Supratrage un program sănătos și exagerează un fumător
Și câteva sfaturi:
- Încercați să nu utilizați proprietatea de fundal în machete.
- Reduceți numărul de aspecte imbricate.
- Introduceți următoarea linie la începutul codului de activitate: getWindow().setBackgroundDrawable(null);.
- Nufolosiți transparența acolo unde vă puteți descurca fără ea.
- Utilizați instrumentul Vizualizare ierarhie pentru a analiza ierarhia machetelor dvs., a relațiilor dintre ele, a estima viteza de redare și a calcula dimensiunile.