Sfaturi și instrumente pentru optimizarea aplicațiilor Android

Publicat: 2022-03-11

Dispozitivele Android au o mulțime de nuclee, așa că scrierea de aplicații fluide este o sarcină simplă pentru oricine, nu? Gresit. Deoarece totul pe Android se poate face în multe moduri diferite, alegerea celei mai bune opțiuni poate fi dificilă. Dacă vrei să alegi cea mai eficientă metodă, trebuie să știi ce se întâmplă sub capotă. Din fericire, nu trebuie să vă bazați pe sentimentele sau simțul mirosului, deoarece există o mulțime de instrumente care vă pot ajuta să găsiți blocajele măsurând și descriind ceea ce se întâmplă. Aplicațiile optimizate și fluide în mod corespunzător îmbunătățesc considerabil experiența utilizatorului și, de asemenea, consumă mai puțină baterie.

Să vedem mai întâi câteva numere pentru a ne gândi cât de importantă este optimizarea. Potrivit unei postări Nimbledroid, 86% dintre utilizatori (inclusiv eu) au dezinstalat aplicații după ce le-au folosit o singură dată din cauza performanței slabe. Dacă încărcați conținut, aveți mai puțin de 11 secunde pentru a-l afișa utilizatorului. Doar fiecare al treilea utilizator vă va oferi mai mult timp. De asemenea, s-ar putea să primiți o mulțime de recenzii proaste pe Google Play din cauza asta.

Creați aplicații mai bune: modele de performanță Android

Testarea răbdării utilizatorilor este o scurtătură către dezinstalare.
Tweet

Primul lucru pe care fiecare utilizator îl observă din nou și din nou este timpul de pornire a aplicației. Potrivit unei alte postări Nimbledroid, din cele 100 de aplicații de top, 40 pornesc în mai puțin de 2 secunde și 70 pornesc în mai puțin de 3 secunde. Deci, dacă este posibil, în general, ar trebui să afișați un anumit conținut cât mai curând posibil și să întârziați puțin verificările de fundal și actualizările.

Amintiți-vă întotdeauna, optimizarea prematură este rădăcina tuturor relelor. De asemenea, nu ar trebui să pierdeți prea mult timp cu micro-optimizarea. Veți vedea cel mai mare beneficiu al optimizării codului care rulează des. De exemplu, aceasta include funcția onDraw() , care rulează fiecare cadru, în mod ideal de 60 de ori pe secundă. Desenarea este cea mai lentă operațiune de acolo, așa că încercați să redesenați doar ceea ce trebuie. Mai multe despre asta vor veni mai târziu.

Sfaturi de performanță

Destul de teorie, iată o listă cu câteva dintre lucrurile pe care ar trebui să le iei în considerare dacă performanța contează pentru tine.

1. String vs. StringBuilder

Să presupunem că aveți un String și, dintr-un motiv oarecare, doriți să adăugați mai multe șiruri la acesta de 10 mii de ori. Codul ar putea arăta cam așa.

 String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }

Puteți vedea pe monitoarele Android Studio cât de ineficientă poate fi o concatenare String. Sunt tone de colectări de gunoi (GC).

String vs StringBuilder

Această operațiune durează aproximativ 8 secunde pe dispozitivul meu destul de bun, care are Android 5.1.1. Cea mai eficientă modalitate de a atinge același obiectiv este utilizarea unui StringBuilder, ca acesta.

 StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();

Pe același dispozitiv acest lucru se întâmplă aproape instantaneu, în mai puțin de 5 ms. Vizualizările CPU și memorie sunt aproape în totalitate plate, așa că vă puteți imagina cât de mare este această îmbunătățire. Observați totuși că pentru a obține această diferență, a trebuit să anexăm 10 mii de șiruri, ceea ce probabil nu faceți des. Deci, în cazul în care adăugați doar câteva șiruri o dată, nu veți vedea nicio îmbunătățire. Apropo, dacă faci:

 String string = "hello" + " world";

Este convertit intern într-un StringBuilder, așa că va funcționa bine.

S-ar putea să vă întrebați, de ce concatenarea șirurilor prima modalitate este atât de lentă? Este cauzată de faptul că șirurile de caractere sunt imuabile, așa că odată ce sunt create, nu pot fi modificate. Chiar dacă credeți că schimbați valoarea unui șir, de fapt creați un șir nou cu noua valoare. Într-un exemplu ca:

 String myString = "hello"; myString += " world";

Ceea ce vei primi în memorie nu este 1 șir „bună lume”, ci de fapt 2 șiruri. String myString va conține „hello world”, așa cum v-ați aștepta. Cu toate acestea, șirul original cu valoarea „hello” este încă viu, fără nicio referire la el, așteaptă să fie colectat gunoiul. Acesta este și motivul pentru care ar trebui să stocați parolele într-o matrice char în loc de un șir. Dacă stocați o parolă ca șir de caractere, aceasta va rămâne în memorie în format care poate fi citit de om până la următorul GC pentru o perioadă de timp imprevizibilă. Revenind la imuabilitatea descrisă mai sus, șirul va rămâne în memorie chiar dacă îi atribuiți o altă valoare după ce îl utilizați. Dacă, totuși, goliți matricea de caractere după ce ați folosit parola, aceasta va dispărea de peste tot.

2. Alegerea tipului de date corect

Înainte de a începe să scrieți cod, ar trebui să decideți ce tipuri de date veți folosi pentru colecția dvs. De exemplu, ar trebui să utilizați un Vector sau un ArrayList ? Ei bine, depinde de cazul tău de utilizare. Dacă aveți nevoie de o colecție sigură pentru fire, care va permite doar unui fir de execuție să lucreze cu ea, ar trebui să alegeți un Vector , deoarece este sincronizat. În alte cazuri, probabil că ar trebui să rămâneți la o ArrayList , cu excepția cazului în care aveți cu adevărat un motiv anume pentru a utiliza vectori.

Ce zici de cazul în care vrei o colecție cu obiecte unice? Ei bine, probabil că ar trebui să alegi un Set . Ele nu pot conține duplicate prin design, așa că nu va trebui să aveți grijă de ele singur. Există mai multe tipuri de seturi, așa că alegeți unul care se potrivește cazului dvs. de utilizare. Pentru un grup simplu de articole unice, puteți utiliza un HashSet . Dacă doriți să păstrați ordinea elementelor în care au fost inserate, alegeți un LinkedHashSet . Un TreeSet sortează automat elementele, astfel încât nu va trebui să apelați nicio metodă de sortare pe el. De asemenea, ar trebui să sorteze elementele în mod eficient, fără să fii nevoit să te gândești la algoritmi de sortare.

Datele domină. Dacă ați ales structurile de date potrivite și ați organizat bine lucrurile, algoritmii vor fi aproape întotdeauna de la sine înțeleși. Structurile de date, nu algoritmii, sunt esențiale pentru programare.
— Cele 5 reguli de programare ale lui Rob Pike

Sortarea numerelor întregi sau a șirurilor de caractere este destul de simplă. Totuși, ce se întâmplă dacă doriți să sortați o clasă după o anumită proprietate? Să presupunem că scrieți o listă cu mesele pe care le consumați și le stocați numele și marcajele de timp. Cum ați sorta mesele după marcaj de timp de la cel mai mic la cel mai mare? Din fericire, este destul de simplu. Este suficient să implementați interfața Comparable în clasa Meal și să suprascrieți funcția compareTo() . Pentru a sorta mesele după marcajul de timp minim la cel mai mare, am putea scrie ceva de genul acesta.

 @Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }

3. Actualizări de locație

Există o mulțime de aplicații care colectează locația utilizatorului. În acest scop, ar trebui să utilizați API-ul Google Location Services, care conține o mulțime de funcții utile. Există un articol separat despre utilizarea lui, așa că nu îl voi repeta.

Aș dori doar să subliniez câteva puncte importante din perspectiva performanței.

În primul rând, utilizați numai locația cea mai precisă după cum aveți nevoie. De exemplu, dacă faceți niște prognoze meteo, nu aveți nevoie de locația cea mai precisă. Obținerea unei zone foarte accidentate pe baza rețelei este mai rapidă și mai eficientă a bateriei. Puteți obține acest lucru setând prioritatea la LocationRequest.PRIORITY_LOW_POWER .

De asemenea, puteți utiliza o funcție a LocationRequest numită setSmallestDisplacement() . Setarea în metri va face ca aplicația dvs. să nu fie notificată despre schimbarea locației dacă a fost mai mică decât valoarea dată. De exemplu, dacă aveți o hartă cu restaurante din apropiere și setați cea mai mică deplasare la 20 de metri, aplicația nu va face solicitări pentru verificarea restaurantelor dacă utilizatorul doar se plimbă într-o cameră. Solicitările ar fi inutile, pentru că oricum nu ar exista un nou restaurant în apropiere.

A doua regulă este să solicitați actualizări ale locației doar de câte ori aveți nevoie de ele. Acest lucru se explică de la sine. Dacă într-adevăr construiți acea aplicație de prognoză meteo, nu trebuie să solicitați locația la fiecare câteva secunde, deoarece probabil nu aveți prognoze atât de precise (contactați-mă dacă aveți). Puteți utiliza funcția setInterval() pentru a seta intervalul necesar în care dispozitivul vă va actualiza aplicația despre locație. Dacă mai multe aplicații continuă să solicite locația utilizatorului, fiecare aplicație va fi notificată la fiecare nouă actualizare a locației, chiar dacă aveți un setInterval() mai mare. Pentru a împiedica aplicația dvs. să fie notificată prea des, asigurați-vă că setați întotdeauna un interval de actualizare cel mai rapid cu setFastestInterval() .

Și, în sfârșit, a treia regulă este să solicitați actualizări ale locației doar dacă aveți nevoie de ele. Dacă afișați unele obiecte din apropiere pe hartă la fiecare x secunde și aplicația trece în fundal, nu trebuie să cunoașteți noua locație. Nu există niciun motiv pentru a actualiza harta dacă utilizatorul nu o poate vedea oricum. Asigurați-vă că nu mai ascultați pentru actualizări de locație atunci când este cazul, de preferință în onPause() . Apoi puteți relua actualizările în onResume() .

4. Cereri de rețea

Există șanse mari ca aplicația dvs. să folosească internetul pentru descărcarea sau încărcarea datelor. Dacă este, aveți mai multe motive să acordați atenție gestionării solicitărilor de rețea. Una dintre ele este datele mobile, care sunt foarte limitate la o mulțime de oameni și nu ar trebui să le irosești.

Al doilea este bateria. Atât rețelele WiFi, cât și cele mobile pot consuma destul de mult dacă sunt folosite prea mult. Să presupunem că doriți să descărcați 1 kb. Pentru a face o cerere de rețea, trebuie să activați radioul celular sau WiFi, apoi vă puteți descărca datele. Cu toate acestea, radioul nu va adormi imediat după operație. Acesta va rămâne într-o stare destul de activă pentru încă aproximativ 20-40 de secunde, în funcție de dispozitiv și de operator.

Cereri de rețea

Deci, ce poți face în privința asta? Lot. Pentru a evita trezirea radioului la fiecare două secunde, preluați lucrurile de care utilizatorul ar putea avea nevoie în următoarele minute. Modul adecvat de loturi este foarte dinamic, în funcție de aplicația dvs., dar dacă este posibil, ar trebui să descărcați datele de care utilizatorul ar putea avea nevoie în următoarele 3-4 minute. De asemenea, se pot edita parametrii lotului în funcție de tipul de internet al utilizatorului sau de starea de încărcare. De exemplu, dacă utilizatorul se află pe WiFi în timpul încărcării, puteți preleva mult mai multe date decât dacă utilizatorul este pe internet mobil cu baterie scăzută. Luarea în considerare a tuturor acestor variabile poate fi un lucru greu, pe care doar puțini oameni l-ar face. Din fericire, totuși, există Managerul de rețea GCM la salvare!

Managerul de rețea GCM este o clasă cu adevărat utilă, cu o mulțime de atribute personalizabile. Puteți programa cu ușurință atât sarcini repetate, cât și sarcini unice. La sarcinile repetate puteți seta cel mai mic, precum și cel mai mare interval de repetare. Acest lucru va permite gruparea nu numai a solicitărilor dvs., ci și a solicitărilor de la alte aplicații. Radioul trebuie pornit doar o dată pe o anumită perioadă și, în timp ce este pornit, toate aplicațiile din coadă descarcă și încarcă ceea ce ar trebui să facă. Acest manager este, de asemenea, conștient de tipul de rețea a dispozitivului și starea de încărcare, astfel încât să puteți ajusta în consecință. Puteți găsi mai multe detalii și mostre în acest articol, vă îndemn să îl verificați. Un exemplu de sarcină arată astfel:

 Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();

Apropo, începând cu Android 3.0, dacă faceți o solicitare de rețea pe firul principal, veți obține o NetworkOnMainThreadException . Asta te va avertiza cu siguranță să nu mai faci asta.

5. Reflecție

Reflecția este capacitatea claselor și a obiectelor de a-și examina propriii constructori, câmpuri, metode și așa mai departe. Este folosit de obicei pentru compatibilitatea cu versiunea anterioară, pentru a verifica dacă o anumită metodă este disponibilă pentru o anumită versiune a sistemului de operare. Dacă trebuie să utilizați reflectarea în acest scop, asigurați-vă că stocați în cache răspunsul, deoarece utilizarea reflectării este destul de lentă. Unele biblioteci utilizate pe scară largă folosesc și Reflection, cum ar fi Roboguice pentru injectarea dependenței. Acesta este motivul pentru care ar trebui să preferați Dagger 2. Pentru mai multe detalii despre reflectare, puteți verifica o postare separată.

6. Autoboxing

Autoboxing și unboxing sunt procese de conversie a unui tip primitiv într-un tip Object sau invers. În practică, înseamnă conversia unui int într-un întreg. Pentru a realiza acest lucru, compilatorul folosește funcția Integer.valueOf() intern. Conversia nu este doar lentă, ci și obiectele ocupă mult mai multă memorie decât echivalentele lor primitive. Să ne uităm la un cod.

 Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

În timp ce acest lucru durează în medie 500 ms, rescrierea pentru a evita autoboxing-ul va accelera drastic.

 int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

Această soluție rulează la aproximativ 2 ms, ceea ce este de 25 de ori mai rapid. Dacă nu mă crezi, testează-l. Numerele vor fi evident diferite pe dispozitiv, dar ar trebui să fie totuși mult mai rapid. Și este, de asemenea, un pas foarte simplu de optimizat.

Bine, probabil că nu creați des o variabilă de tip Integer ca acesta. Dar cum rămâne cu cazurile în care este mai dificil de evitat? Ca într-o hartă, unde trebuie să utilizați obiecte, cum ar fi Map<Integer, Integer> ? Uită-te la soluția pe care o folosesc mulți oameni.

 Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }

Inserarea a 100.000 de intrări aleatorii în hartă durează aproximativ 250 ms. Acum uitați-vă la soluția cu SparseIntArray.

 SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }

Acest lucru durează mult mai puțin, aproximativ 50 ms. Este, de asemenea, una dintre metodele mai ușoare de îmbunătățire a performanței, deoarece nu trebuie făcut nimic complicat, iar codul rămâne, de asemenea, lizibil. În timp ce rulez o aplicație clară cu prima soluție, mi-a luat 13 MB din memorie, utilizarea inturilor primitive a luat ceva sub 7 MB, deci doar jumătate.

SparseIntArray este doar una dintre colecțiile interesante care vă pot ajuta să evitați autoboxing-ul. O hartă precum Map<Integer, Long> ar putea fi înlocuită cu SparseLongArray , deoarece valoarea hărții este de tip Long . Dacă te uiți la codul sursă al lui SparseLongArray , vei vedea ceva destul de interesant. Sub capotă, este practic doar o pereche de matrice. De asemenea, puteți utiliza un SparseBooleanArray în mod similar.

Dacă citiți codul sursă, este posibil să fi observat o notă care spune că SparseIntArray poate fi mai lent decât HashMap . Am experimentat mult, dar pentru mine SparseIntArray a fost întotdeauna mai bun atât din punct de vedere al memoriei, cât și al performanței. Bănuiesc că depinde în continuare de dvs. ce alegeți, experimentați cu cazurile de utilizare și vedeți care vi se potrivește cel mai mult. Cu siguranță aveți SparseArrays -urile în cap când utilizați hărți.

7. OnDraw

După cum am spus mai sus, atunci când optimizați performanța, probabil veți vedea cel mai mare beneficiu în optimizarea codului care rulează des. Una dintre funcțiile care rulează mult este onDraw() . S-ar putea să nu vă surprindă că este responsabil pentru desenarea vederilor pe ecran. Deoarece dispozitivele rulează de obicei la 60 fps, funcția este rulată de 60 de ori pe secundă. Fiecare cadru are 16 ms pentru a fi manevrat complet, inclusiv pregătirea și desenarea acestuia, așa că ar trebui să evitați cu adevărat funcțiile lente. Doar firul principal poate desena pe ecran, așa că ar trebui să evitați să faceți operațiuni costisitoare pe acesta. Dacă înghețați firul principal pentru câteva secunde, este posibil să primiți dialogul ANR (Application Not Responding). Pentru redimensionarea imaginilor, lucrul în baza de date etc., utilizați un fir de fundal.

Dacă credeți că utilizatorii dvs. nu vor observa această scădere a ratei de cadre, vă înșelați!
Tweet

Am văzut unii oameni încercând să-și scurteze codul, gândindu-se că așa va fi mai eficient. Cu siguranță nu aceasta este calea de urmat, deoarece un cod mai scurt nu înseamnă în totalitate un cod mai rapid. În niciun caz nu trebuie să măsurați calitatea codului după numărul de linii.

Unul dintre lucrurile pe care ar trebui să le evitați în onDraw() este alocarea de obiecte precum Paint. Pregătiți totul în constructor, astfel încât să fie gata atunci când desenați. Chiar dacă aveți onDraw() optimizat, ar trebui să îl apelați doar cât de des este necesar. Ce este mai bun decât apelarea unei funcții optimizate? Ei bine, nu apelează deloc nicio funcție. În cazul în care doriți să desenați text, există o funcție de ajutor destul de îngrijită numită drawText() , unde puteți specifica lucruri precum textul, coordonatele și culoarea textului.

8. ViewHolders

Probabil îl știi pe acesta, dar nu îl pot sări peste el. Modelul de design Viewholder este o modalitate de a face listele de defilare mai fluide. Este un fel de memorare în cache a vizualizărilor, care poate reduce în mod serios apelurile la findViewById() și poate umfla vizualizările prin stocarea acestora. Poate arăta cam așa.

 static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }

Apoi, în cadrul getView() a adaptorului dumneavoastră, puteți verifica dacă aveți o vizualizare utilizabilă. Dacă nu, creați unul.

 ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");

Puteți găsi o mulțime de informații utile despre acest model pe internet. Poate fi folosit și în cazurile în care vizualizarea listă conține mai multe tipuri diferite de elemente, cum ar fi unele antete de secțiune.

9. Redimensionarea imaginilor

Este posibil ca aplicația dvs. să conțină câteva imagini. În cazul în care descărcați niște JPG-uri de pe web, acestea pot avea rezoluții foarte mari. Cu toate acestea, dispozitivele pe care vor fi afișate vor fi mult mai mici. Chiar dacă faceți o fotografie cu camera dispozitivului dvs., aceasta trebuie să fie redusă înainte de afișare, deoarece rezoluția fotografiei este mult mai mare decât rezoluția afișajului. Redimensionarea imaginilor înainte de a le afișa este un lucru crucial. Dacă ați încerca să le afișați la rezoluție maximă, ați rămâne fără memorie destul de repede. Există multe scrise despre afișarea eficientă a bitmap-urilor în documentele Android, voi încerca să rezumam.

Deci ai un bitmap, dar nu știi nimic despre el. Există un steag util al Bitmaps-ului numit inJustDecodeBounds la dispoziția dumneavoastră, care vă permite să aflați rezoluția bitmap-ului. Să presupunem că bitmap-ul dvs. este de 1024 x 768, iar ImageView utilizat pentru afișare este de doar 400 x 300. Ar trebui să continuați să împărțiți rezoluția bitmap-ului la 2 până când este încă mai mare decât ImageView-ul dat. Dacă o faceți, va reduce eșantionarea bitmap-ului cu un factor de 2, oferindu-vă un bitmap de 512x384. Bitmap-ul subeșantionat utilizează de 4 ori mai puțină memorie, ceea ce vă va ajuta foarte mult să evitați celebra eroare OutOfMemory.

Acum că știi cum să o faci, nu ar trebui să o faci. … Cel puțin, nu dacă aplicația ta se bazează în mare măsură pe imagini și nu este vorba doar de 1-2 imagini. Evitați cu siguranță lucruri precum redimensionarea și reciclarea manuală a imaginilor, utilizați biblioteci terțe pentru asta. Cele mai populare sunt Picasso by Square, Universal Image Loader, Fresco by Facebook sau preferatul meu, Glide. Există o comunitate activă uriașă de dezvoltatori în jurul său, așa că puteți găsi o mulțime de oameni de ajutor și la secțiunea de probleme de pe GitHub.

10. Modul strict

Modul strict este un instrument de dezvoltator destul de util despre care mulți oameni nu știu. Este de obicei folosit pentru detectarea solicitărilor de rețea sau a acceselor la disc din firul principal. Puteți seta ce probleme ar trebui să caute Modul strict și ce penalizare ar trebui să declanșeze. Un eșantion google arată astfel:

 public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }

Dacă doriți să detectați fiecare problemă pe care o poate găsi Modul Strict, puteți utiliza și detectAll() . Ca și în cazul multor sfaturi de performanță, nu ar trebui să încercați orbește să remediați toate rapoartele în modul strict. Doar investigați-l și, dacă sunteți sigur că nu este o problemă, lăsați-o în pace. De asemenea, asigurați-vă că utilizați Modul strict numai pentru depanare și îl aveți întotdeauna dezactivat în versiunile de producție.

Performanță de depanare: modul profesional

Să vedem acum câteva instrumente care vă pot ajuta să găsiți blocaje sau cel puțin să arătați că ceva nu este în regulă.

1. Monitor Android

Acesta este un instrument încorporat în Android Studio. În mod implicit, puteți găsi Android Monitor în colțul din stânga jos și puteți comuta între 2 file acolo. Logcat și monitoare. Secțiunea Monitoare conține 4 grafice diferite. Rețea, CPU, GPU și memorie. Sunt destul de explicite, așa că le voi trece rapid prin ele. Iată o captură de ecran a graficelor făcute în timpul analizării unor JSON pe măsură ce este descărcat.

Monitor Android

Partea Rețea arată traficul de intrare și de ieșire în KB/s. Partea CPU afișează utilizarea CPU în procente. Monitorul GPU afișează cât timp este nevoie pentru a reda cadrele unei ferestre UI. Acesta este cel mai detaliat monitor dintre aceste 4, așa că dacă doriți mai multe detalii despre el, citiți asta.

În cele din urmă avem monitorul de memorie, pe care probabil îl vei folosi cel mai mult. În mod implicit, arată cantitatea curentă de memorie liberă și alocată. Puteți forța și o colectare de gunoi cu ea, pentru a testa dacă cantitatea de memorie utilizată scade. Are o caracteristică utilă numită Dump Java Heap, care va crea un fișier HPROF care poate fi deschis cu HPROF Viewer and Analyzer. Acest lucru vă va permite să vedeți câte obiecte ați alocat, câtă memorie este ocupată de ce și, poate, care obiecte provoacă scurgeri de memorie. A învăța cum să folosești acest analizor nu este cea mai simplă sarcină de acolo, dar merită. Următorul lucru pe care îl puteți face cu Memory Monitor este să faceți o urmărire temporizată a alocării, pe care o puteți porni și opri după cum doriți. Ar putea fi util în multe cazuri, de exemplu atunci când derulați sau rotiți dispozitivul.

2. GPU Overdraw

Acesta este un instrument de ajutor simplu, pe care îl puteți activa în Opțiuni pentru dezvoltatori după ce ați activat modul dezvoltator. Selectați Debug GPU overdraw, „Afișați zonele overdraw”, iar ecranul dvs. va primi niște culori ciudate. E ok, asta ar trebui să se întâmple. Culorile înseamnă de câte ori o anumită zonă a fost depășită. Culoarea adevărată înseamnă că nu a existat un overdraw, acesta este ceea ce ar trebui să ținți. Albastru înseamnă un overdraw, verde înseamnă două, roz trei, roșu patru.

Overdraw GPU

Deși a vedea culoarea adevărată este cel mai bun, veți vedea întotdeauna unele depășiri, în special în jurul textelor, sertarelor de navigare, dialogurilor și multe altele. Așa că nu încercați să scăpați complet de el. Dacă aplicația dvs. este albăstruie sau verzuie, probabil că este în regulă. Cu toate acestea, dacă vedeți prea mult roșu pe unele ecrane simple, ar trebui să investigați ce se întâmplă. S-ar putea să fie prea multe fragmente stivuite unele pe altele, dacă le continuați să le adăugați în loc să le înlocuiți. După cum am menționat mai sus, desenul este cea mai lentă parte a aplicațiilor, așa că nu are sens să desenezi ceva dacă vor fi desenate mai mult de 3 straturi pe el. Simțiți-vă liber să verificați aplicațiile preferate cu el. Veți vedea că chiar și aplicațiile cu peste un miliard de descărcări au zone roșii, așa că luați-vă ușor atunci când încercați să optimizați.

3. Redare GPU

Acesta este un alt instrument din opțiunile pentru dezvoltatori, numit randare Profil GPU. După ce îl selectați, alegeți „Pe ecran ca bare”. Veți observa niște bare colorate care apar pe ecran. Deoarece fiecare aplicație are bare separate, în mod ciudat, bara de stare are propriile sale, iar în cazul în care aveți butoane de navigare software, acestea au și ele propriile bare. Oricum, barele se actualizează pe măsură ce interacționați cu ecranul.

Redare GPU

Barele constau din 3-4 culori și, conform documentelor Android, dimensiunea lor contează într-adevăr. Cu cât este mai mic, cu atât mai bine. În partea de jos aveți albastru, care reprezintă timpul folosit pentru a crea și actualiza listele de afișare ale Vizualizării. Dacă această parte este prea înaltă, înseamnă că există o mulțime de desene personalizate de vedere sau multă muncă efectuată în funcțiile onDraw() . Dacă aveți Android 4.0+, veți vedea o bară violet deasupra celei albastre. Acesta reprezintă timpul petrecut transferând resurse către firul de randare. Apoi vine partea roșie, care reprezintă timpul petrecut de redarea 2D al Androidului emitând comenzi către OpenGL pentru a desena și redesena liste de afișare. În partea de sus este bara portocalie, care reprezintă timpul în care procesorul așteaptă ca GPU-ul să-și termine activitatea. Dacă este prea înalt, aplicația lucrează prea mult pe GPU.

Dacă ești suficient de bun, mai există o culoare deasupra portocaliului. Este o linie verde care reprezintă pragul de 16 ms. Deoarece obiectivul dvs. ar trebui să fie rularea aplicației la 60 fps, aveți 16 ms pentru a desena fiecare cadru. Dacă nu reușiți, unele cadre ar putea fi sărite, aplicația ar putea deveni sacadată, iar utilizatorul va observa cu siguranță. Acordați o atenție deosebită animațiilor și derulării, acolo este locul în care netezimea contează cel mai mult. Chiar dacă puteți detecta unele cadre sărite cu acest instrument, nu vă va ajuta cu adevărat să aflați unde este exact problema.

4. Vizualizator de ierarhie

Acesta este unul dintre instrumentele mele preferate, deoarece este foarte puternic. Puteți să-l porniți din Android Studio prin Instrumente -> Android -> Monitor dispozitiv Android sau este, de asemenea, în folderul sdk/tools ca „monitor”. Puteți găsi, de asemenea, un executabil de vizualizare a ierarhiei de sine stătătoare, dar, deoarece este depreciat, ar trebui să deschideți monitorul. Oricum deschideți Android Device Monitor, comutați la perspectiva Hierarchy Viewer. Dacă nu vedeți nicio aplicație care rulează atribuită dispozitivului dvs., există câteva lucruri pe care le puteți face pentru a o repara. De asemenea, încercați să verificați acest thread cu probleme, există oameni cu tot felul de probleme și tot felul de soluții. Ar trebui să funcționeze ceva și pentru tine.

Cu Hierarchy Viewer, puteți obține o imagine de ansamblu cu adevărat îngrijită a ierarhiilor de vizualizare (evident). Dacă vedeți fiecare aspect într-un XML separat, este posibil să identificați cu ușurință vizualizări inutile. Cu toate acestea, dacă continuați să combinați aspectele, poate deveni ușor confuz. Un astfel de instrument facilitează identificarea, de exemplu, a unor RelativeLayout, care au doar 1 copil, un alt RelativeLayout. Asta face ca unul dintre ele să fie detașabil.

Evitați apelarea requestLayout() , deoarece provoacă traversarea întregii ierarhii de vizualizare, pentru a afla cât de mare ar trebui să fie fiecare vizualizare. Dacă există un conflict cu măsurătorile, ierarhia poate fi parcursă de mai multe ori, ceea ce, dacă se întâmplă în timpul unei animații, va face cu siguranță săriți unele cadre. Dacă doriți să aflați mai multe despre cum își atrage Android opiniile, puteți citi acest lucru. Să ne uităm la o vedere așa cum se vede în Vizualizatorul ierarhiei.

Vizualizator de ierarhie

Colțul din dreapta sus conține un buton pentru maximizarea previzualizării unei anumite vizualizări într-o fereastră de sine stătătoare. Sub acesta puteți vedea și previzualizarea reală a vizualizării în aplicație. Următorul articol este un număr, care reprezintă câți copii are vizualizarea dată, inclusiv vizualizarea în sine. Dacă selectați un nod (de preferință cel rădăcină) și apăsați „Obține timpi de layout” (3 cercuri colorate), veți avea încă 3 valori completate, împreună cu cercuri colorate care apar etichetate măsură, aspect și desen. S-ar putea să nu fie șocant că faza de măsurare reprezintă timpul necesar pentru măsurarea vizualizării date. Faza de layout este despre timpul de randare, în timp ce desenul este operația de desenare reală. Aceste valori și culori sunt relativ unele față de altele. Verde înseamnă că vizualizarea se redă în primele 50% din toate vizualizările din arbore. Galben înseamnă redarea în 50% mai lentă din toate vizualizările din arbore, roșu înseamnă că vizualizarea dată este una dintre cele mai lente. Deoarece aceste valori sunt relative, vor exista întotdeauna valori roșii. Pur și simplu nu le poți evita.

Sub valori aveți numele clasei, cum ar fi „TextView”, un ID de vizualizare intern al obiectului și android:id-ul vizualizării, pe care îl setați în fișierele XML. Vă îndemn să vă creați un obicei de a adăuga ID-uri la toate vizualizările, chiar dacă nu le faceți referire în cod. Va face identificarea vizualizărilor în Hierarchy Viewer cu adevărat simplă și, în cazul în care aveți teste automatizate în proiectul dvs., va face, de asemenea, direcționarea elementelor mult mai rapidă. Acest lucru va economisi ceva timp pentru dvs. și colegii dvs. să le scrieți. Adăugarea de ID-uri la elementele adăugate în fișierele XML este destul de simplă. Dar cum rămâne cu elementele adăugate dinamic? Ei bine, se dovedește a fi și foarte simplu. Doar creați un fișier ids.xml în dosarul de valori și introduceți câmpurile necesare. Poate arăta astfel:

 <resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>

Apoi, în cod, puteți utiliza setId(R.id.item_title) . Mai simplu nu poate fi.

Mai sunt câteva lucruri la care să acordați atenție atunci când optimizați interfața de utilizare. În general, ar trebui să evitați ierarhiile profunde, preferând în același timp pe cele superficiale, poate largi. Nu utilizați machete de care nu aveți nevoie. De exemplu, probabil că puteți înlocui un grup de LinearLayouts imbricate fie cu un RelativeLayout , fie cu un TableLayout . Simțiți-vă liber să experimentați cu diferite machete, nu doar să utilizați întotdeauna LinearLayout și RelativeLayout . De asemenea, încercați să creați câteva vizualizări personalizate atunci când este necesar, poate îmbunătăți performanța semnificativ dacă este făcut bine. De exemplu, știați că Instagram nu folosește TextViews pentru afișarea comentariilor?

Puteți găsi mai multe informații despre Hierarchy Viewer pe site-ul Android Developers cu descrieri ale diferitelor panouri, folosind instrumentul Pixel Perfect etc. Încă un lucru pe care aș sublinia este capturarea vizualizărilor într-un fișier .psd, care poate fi făcut prin butonul „Capturați straturile ferestrei”. Fiecare vizualizare va fi într-un strat separat, așa că este foarte simplu să o ascundeți sau să o modificați în Photoshop sau GIMP. Oh, acesta este un alt motiv pentru a adăuga un ID la fiecare vizualizare pe care o puteți. Va face ca straturile să aibă nume care au sens.

Veți găsi mult mai multe instrumente de depanare în Opțiuni pentru dezvoltatori, așa că vă sfătuiesc să le activați și să vedeți ce fac. Ce ar putea merge prost?

Site-ul pentru dezvoltatori Android conține un set de bune practici pentru performanță. Acestea acoperă o mulțime de domenii diferite, inclusiv managementul memoriei, despre care nu am vorbit cu adevărat. L-am ignorat în tăcere, deoarece gestionarea memoriei și urmărirea scurgerilor de memorie este o poveste cu totul separată. Utilizarea unei biblioteci terță parte pentru afișarea eficientă a imaginilor va ajuta foarte mult, dar dacă mai aveți probleme de memorie, consultați Leak Canary realizat de Square sau citiți asta.

Încheierea

Deci, aceasta a fost vestea bună. Noutatea proastă este că optimizarea aplicațiilor Android este mult mai complicată. Există o mulțime de moduri de a face totul, așa că ar trebui să fii familiarizat cu avantajele și dezavantajele acestora. De obicei, nu există nicio soluție de tip glonț de argint care să aibă numai beneficii. Numai înțelegând ce se întâmplă în culise vei putea alege soluția cea mai potrivită pentru tine. Doar pentru că dezvoltatorul tău preferat spune că ceva este bun, nu înseamnă neapărat că este cea mai bună soluție pentru tine. Există mult mai multe domenii de discutat și mai multe instrumente de profilare care sunt mai avansate, așa că am putea ajunge la ele data viitoare.

Asigurați-vă că învățați de la cei mai buni dezvoltatori și companii de top. Puteți găsi câteva sute de bloguri de inginerie la acest link. Evident, nu este vorba doar de chestii legate de Android, așa că dacă ești interesat doar de Android, trebuie să filtrezi blogul respectiv. Recomand cu căldură blogurile Facebook și Instagram. Chiar dacă interfața de utilizare Instagram pe Android este îndoielnică, blogul lor de inginerie are câteva articole foarte interesante. Pentru mine este minunat că este atât de ușor să vezi cum se fac lucrurile în companii care se ocupă de sute de milioane de utilizatori zilnic, așa că a nu le citi blogurile pare o nebunie. Lumea se schimbă foarte repede, așa că dacă nu încerci în mod constant să te îmbunătățești, să înveți de la alții și să folosești instrumente noi, vei rămâne în urmă. După cum spunea Mark Twain, o persoană care nu citește nu are niciun avantaj față de una care nu știe să citească.