În Tips & Tricks

Debugging în Android

Android Debugging

Am vorbit în articolul anterior despre primii pași în dezvoltarea unei aplicații. Acum a venit momentul să vedem și câteva dintre uneltele pe care platforma Android ni le pune la dispoziție pentru a putea depana și analiza aplicația la care lucrăm.

Nu există software fără bug-uri, ele putând să apară atât pe parcursul dezvoltării aplicației, cât și după ce aceasta a fost lansată pe piață și este în mâna utilizatorilor. De aceea, este important ca platforma să ofere dezvoltatorilor facilități cât mai puternice de debugging, build automation și analiză. În articolul de față, vom vedea, în mare, câteva dintre tool-urile puse la dispoziție de SDK-ul Android.

Android Debug Bridge

Android Debug Bridge sau, mai pe scurt, ADB, este unul dintre utilitarele de bază ale platformei. ADB este o aplicație client/server care facilitează comunicarea cu emulatorul sau telefonul. Are 3 componente:

  1. Un client care rulează pe calculator;
  2. Un server care ajută la comunicarea dintre client și serviciul (daemon) care rulează pe emulator sau pe telefon;
  3. Serviciul (daemon) care rulează pe emulator sau telefon.

ADB poate fi folosit din linia de comandă și este utilizat și de alte aplicații, cum ar fi DDMS (vom vorbi despre acest utilitar putin mai târziu). ADB comunică prin TCP și va ocupa un port (5037) atunci când este pornit, dar nu vom intra în detalii legate de funcționarea internă, ci ne vom axa pe câteva aspecte practice.

Interacțiunea cu telefonul

După cum spuneam, ADB poate comunica atât cu un emulator, cât și cu un dispozitiv fizic, conectat la calculator prin intermediul portului USB. Pentru a porni server-ul ADB, ajunge să executăm în terminal comanda

$ adb devices

Aceasta va porni serverul (dacă nu este pornit deja) și va lista toate dispozitivele conectate.

De exemplu:

$ adb devices
List of devices attached
emulator-5554  device
HT012p8019342  device

Este important de știut că, în cazul în care folosiți un telefon, acesta trebuie să aibă activat modul de development și USB debugging, altfel ADB nu se va putea conecta la el. Aceste opțiuni se găsesc în meniul de setări al telefonului. Modul de activare e diferit de la versiune la versiune. Pentru detalii despre versiunea pe care o folosiți, citiți pe site-ul de development al Android.

Dacă avem mai multe device-uri conectate (emulator, telefon, tabletă), putem specifica pe care îl țintim cu o anumită comandă, prin opțiunea „-s”. De exemplu, dacă dorim să instalăm o aplicație pe emulator, dar nu și pe telefon, putem rula comanda

$ adb –s emulator-5554 install /path/to/application.apk 

Opțiunea de „install” este, practic, un echivalent pentru “adb shell pm install”. Ceea ce ne duce la următorul punct.

Interacțiunea cu shell-ul

Prin ADB putem avea acces direct la shell-ul sistemului de operare. Dacă vă mai amintiți din primul articol, spuneam că, în esență, Android rulează pe un sistem de operare bazat pe UNIX. Astfel, având acces la shell, putem rula aproape orice comandă de terminal care e disponibilă în mod tradițional în UNIX. Pentru a intra în shell-ul telefonului, trebuie doar să scriem “adb shell”, iar comenzile ulterioare vor fi executate în contextul sistemului de operare de pe telefon.

De exemplu:

$ adb shell
# ls
config
cache
etc.
...

Pentru a reveni din shell-ul telefonului, putem folosi Ctrl+D.  De reținut că în shell-ul telefonului putem avea drepturi de „superuser” sau „admin” și trebuie avut grijă la comenzile pe care le executăm, în special cele care alterează date. Pentru emulatoare, shell-ul oferă mereu acces de root, însă în cazul telefoanelor nu este valabil. Dacă este nevoie de acces root pe un telefon sau tabletă, acesta trebuie „root-at” în prealabil.

După cum se poate vedea, ADB este un tool puternic al cărui potențial stă și în combinația cu shell-script-urile și tool-urile de build automation, cum ar fi Jenkins. Dar despre aceste aspecte vom vorbi într-un articol viitor.

Dalvik Debug Monitor Server

Acest tool, numit pe scurt DDMS, este inclus în SDK-ul Android și este un utilitar mult mai avansat decât ADB. Acesta oferă o mulțime de informații pentru debugging și analiză a unei aplicații. Printre acestea se numără:

  • un mecanism de port-forwarding;
  • capturi de ecran;
  • informații despre heap-ul și thread-urile de pe telefon;
  • access la sistemul de logging;
  • informații despre procese și starea componentelor radio.

În general, acesta este integrat în IDE-urile avansate (precum Eclipse sau IDEA), dar poate fi folosit și ca tool stand-alone. El se poate porni și din linia de comandă prin comanda “ddms”.

Nici aici nu vom intra în detaliile interne ale aplicației. DDMS oferă o privire completă asupra proceselor interne din telefon și ne oferă posibilități de analiză a aplicației pe care o dezvoltăm, în detaliu. Iată câteva dintre acestea:

  • vizualizarea utilizării heap-ului pentru un proces

Putem vedea cât din memoria heap e folosită de un proces la un anumit moment.

  • urmărirea alocării de memorie a obiectelor

Putem urmări obiectele alocate în memorie și putem vedea care procese și thread-uri le-au alocat. Acest lucru este esențial pentru analiza nivelului de memorie folosit – nivel care are impact asupra performanței – și poate ajuta la depistarea de memory leak-uri.

  • acces la sistemul de fișiere

În cazul în care aplicația face uz de fișiere stocate pe telefon, putem urmări fișierele, unde au fost create, dimensiunea lor, conținutul și așa mai departe.

  • method profiling

Aici putem urmări diverse metrici ale metodelor, cum ar fi timpul de execuție și numărul de apeluri.

  • urmărirea traficului de rețea

Folosirea traficului de date pe telefon este unul foarte costisitor atât din punctul de vedere al bateriei, cât și al volumului de date transferate, având impact și asupra costului pentru utilizator. Putem urmări traficul și request-urile pe care le facem, pentru a găsi potențiale probleme în tab-ul de Network Usage.

  • putem urmări log-ul din sistem

Android are un sistem de logging complet pe care îl putem urmări în tab-ul dedicat.

Toate acestea fac din DDMS un utilitar esențial pentru optimizarea și depanarea aplicației.

Logcat

Am menționat în paragraful anterior de sistemul de logging. Android beneficiază de un sistem ce oferă un mecanism de colectare a log-urilor atât din aplicații, cât și din sistemul de operare.  Acesta este destul de complet, adunând la un loc atât mesaje de la aplicații, cât și de la sistemul de operare. Logcat se poate accesa și folosi prin intermediul ADB.

Log-ul dispune de mai multe nivele de prioritate. În ordinea priorității, acestea sunt:

  • Verbose (V) – cea mai mică prioritate, totul apare în log;
  • Debug (D);
  • Info (I);
  • Warning (W);
  • Error (E);
  • Fatal (F);
  • Silent (S) – cea mai mare prioritate, nimic nu apare în log.

Putem specifica pentru un mesaj nivelul minim de prioritate pe care îl are. În funcție de el, mesajele din log pot fi filtrate. Un alt mecanism de filtrare pus la dispoziție este cel de tagging. Putem specifica pentru fiecare mesaj un tag care să aducă un indiciu suplimentar asupra originii mesajului. În general, tag-ul este unic la nivel de aplicație sau modul. Astfel, fiecare linie din log este identificată prin aceste elemente de bază: prioritate și tag. Ele sunt afișate la începutul fiecărei linii de log în formatul <prioritate>/<tag>.

De exemplu:

I/ActivityManager(  585): Starting activity: Intent{action=android.intent.action.CALL}

I este nivelul de prioritate INFO, iar tag-ul este „ActivityManager”. SDK oferă o clasă dedicată pentru logging, care are câte o metodă pentru fiecare nivel de prioritate. Ele sunt foarte simple și voi da doar un exemplu pentru nivelul de debug:

Log.d("MyActivity", "MyClass.getView() — get item number " + position);

Deși clasa de Log este suficient de completă pentru a satisface nevoile de logging dintr-o aplicație, este recomandat să creăm un wrapper class specific aplicației, pentru a elimina din redundanțe (cum ar fi specificarea de fiecare dată a tag-ului). În plus, este bine ca tag-ul de log specific aplicației (sau tag-urile) să fie definit ca și constantă în clasa wrapper sau într-o clasă de constante.

Log-ul poate fi accesat din 3 locuri diferite: din DDMS (unde fiecare nivel de prioritate are o culoare specifică, pentru a ușura cititul), din IDE (dacă are posibilitatea aceasta) sau din linia de comandă. Iată câteva comenzi utile pentru a putea lucra cu log-ul din linia de comandă:

$ adb logcat ActivityManager:I

Acestă comandă va afișa log-ul și doar mesajele care vin de la tag-ul ActivityManager cu prioritate Info sau mai mare. În același mod se pot înseria mai multe tag-uri, fiecare cu nivelul corespunzător de prioritate.

Dacă dorim să restricționăm nivelul de prioritate al tuturor tag-urilor, putem folosi caracterul “*” în loc de tag, astfel:

$ adb logcat *:W

Dacă dorim să salvăm un log în fișiere text, putem redirecționa output-ul comenzii într-un fișier astfel:

$ adb logcat > /path/to/file.log

Logcat oferă mult mai multe posibilități de filtrare și afișare. Fiecare mesaj de log are un set de metadate asociat (identificatorul procesului, thread-ul, timestamp-ul invocări etc.) care pot fi afișate sau nu, în funcție de nevoi. Dar, pentru început, informațiile prezente în acest articol sunt suficiente pentru a putea lucra cu sistemul de logging și debugging din Android.

Ca de obicei, întrebările și comentariile sunt binevenite și voi încerca să răspund la fiecare în parte, în timp cât mai  scurt.