3. Aug 2021Android

Ako debugovať kód v Android Studio

Naučiť sa efektívne debugovat software je ťažké. Vyžaduje si to trpezlivosť a veľa debugovania. Nesmie chýbať ani abstraktná predstavivosť a vedomosť danej štruktúry projektu. Nuž každý niekde začínal. Predstavím Vám solidný základ ako začať debugovať v Android Studiu a čo na čo slúži.

Tomáš ParonaiAndroid Developer

Debug (alebo ladenie)

Debugovanie je proces, ktorého cieľom je odhaliť chybu v kóde. V princípe platí, že čím viac si dáme záležať na dôkladnej implementácií kódu, tým menej alebo vôbec sa k nemu vracať nemusíme. Izolovanie chyby a jej debugovaním trávime oveľa viac času, ako pri implementácií. Aj tu platí zlaté pravidlo našich mám:

Dva krát meraj, raz strihaj! - random mama

Pri vývoji Android aplikácií sa debugovaniu nevyhneme. Aj tí najlepší vývojári robia chyby, ktoré nie sú hneď na oko jasné. Odhalia sa neskôr pri testovaní alebo pri budúcom vývoji. Z prvého pohľadu nemusí byť hneď jasné, čo je problémom, vtedy siahneme na debugger.

Android debugger

Android debugger je nástroj v Android Studiu, s ktorým sa vieme napojiť na bežiaci proces vyvíjanej aplikácie alebo ju vieme spustiť tak, aby bol napojený hneď od štartu. Ak chceme použiť fyzické zariadenie, musíme povoliť debugovanie a nainštalovať správny variant aplikácie cez studio.

Ako zapnúť developerské rozhranie na fyzickom zariadení

  1. Otvorte si nastavenia v telefóne
  2. Navigujte do Informácie o telefóne
  3. Klikajte na Číslo zostavy
  4. Zhruba po piatom kliknutí vám vyskočí správa, že ste vývojár
  5. Navigujte späť do nastavení
  6. Otvorte si Systém
  7. Mali by ste tam mat možnosť Pre vývojárov
  8. Otvorte si to a skrolnite dole na sekciu Ladenie
  9. Zapnite Ladenie cez USB

Ako zapnúť debugovanie v Android Studio

Ak už máme otvorený projekt v Android Studio, pripojíme si zaradenie pomocou USB k počítači. Ak chcete debugovať na emulátore, je potrebné mať emulátor spustený. Na väčšine android zariadení vám vyskočí upozornenie, či dôverujete danému počítaču, ktoré je potrebné povoliť, ak chceme čítať a zapisovať dáta cez USB kábel.

Teraz máme na výber:

  1. Môžeme spustiť aplikáciu štandardným spôsobom cez launcher alebo cez studio a až tak pripojiť debuger
  2. Môžeme spustiť aplikáciu cez studio so zapnutým debugerom

Po úspešnom spustení sa nič špeciálne neudeje, ak ste si nezadefinovali breakpointy v zdrojovom kóde.

Debugovanie

V nasledujúcom príklade máme ViewModel, ktorý nám počíta faktoriál čísla n.

class DebugViewModel: ViewModel() {
    private val _state: MutableStateFlow<Int> = MutableStateFlow(0)
    val state: StateFlow<Int> get() = _state

    fun calculateFactorialOf(n: Int) {
        viewModelScope.launch {
            var result = 1
            repeat(n) { i ->
                result = calculate(result, i)
            }
            _state.emit(result)
        }
    }

    private fun calculate(res: Int, i: Int): Int = res * i
}

Po spustení aplikácie a spustení funkcie calculateFactorialOf máme stále výsledok 0. Z prvého pohľadu nie je úplne jasné, kde nastáva nečakaná chyba. Kliknutím na kraj riadku, si vieme umiestniť breakpoint.

Pre spustenie aplikácie už s pripojeným debuggerom kliknite na ikonku chrobáčika v hornej lište.

Pre pripojenie debug procesu klikneme na ikonku chrobáčika so šípkou v hornej lište.

Následne vyberieme proces, ktorý chceme debugovať a potvrdíme. V našom prípade je to aktuálny príklad.

Po potvrdení naskočí tab Debug v dolnej časti IDEA. Ak sa tak nestane, vieme ho zobraziť z toolbaru View → Tool Windows → Debug. To je naše debugovacie rozhranie, v ktorom budeme mať možnosť kontrolovať proces krok po kroku.

Teraz potrebujeme zreprodukovať akciu, ktorá nám spustí funkciu calculateFactorialOf. V našom príklade je to tlačidlo, ktoré potvrdí výpočet. Po stlačení IDEA otvorí súbor s brekpointom, na ktorom sa zastavil, a sústredí sa na riadok, kde sa zastavil. Ako ste si všimli, že užívateľské rozhranie na telefóne "zamrzlo". To je z toho dôvodu, že daný kód beží na hlavnom UI vlákne a pri debugovaní sa pozastaví na breakpointe kým ho neposunieme ďalej alebo nenecháme pokračovať.

  1. Preskočiť riadok. Po kliknutí nám postúpi program na nasledujúci riadok.
  2. Vstúpiť do funkcie. Ak by na danom mieste bola funkcia, po kliknutí na dané tlačidlo nám vstúpi debuger do funkcie, ktorá ide v poradí. Ak je tam deklarácia primitívnej premennej, posunie nám program o riadok nižšie.
  3. Vstúpiť do funkcie na silu. Ak volanú metódu, ktorú debugujeme, je volaná externe IDEA môže ignorovať klasický vstup do funkcie. Ak však použijem vstup na silu, IDEA sa pokúsi vstúpiť do funkcie.
  4. Vystúpiť z funkcie. V prípade, že už som vnorený niekde vo vnútri, môžem predčasne opustiť danú funkciu a vrátiť sa späť na miesto, z ktorého som doň vstúpil. Referencia adries odkiaľ som vstúpil do danej podrutiny voláme callstack a môžeme ho pozorovať pod číslom 12.
  5. Ak umiestnim kurzor na iný riadok, a stlačím toto tlačidlo, program mi bude pokračovať na zvolenom riadku, ak po ceste nemám žiadne iné breakpointy. Na riadok môžem pokračovať aj kliknutím na číslo riadku.
  6. Evaluate expression. Pomocný exekútor, s ktorým vieme vyhodnocovať aktuálne premenné a pracovať s nimi v uzavretom kontexte. Napríklad vieme v ňom skontrolovať, že či funkcia calculate vracia očakávané výsledky. V okne Evaluate vieme zadať príkaz calculate(4, 2) a stlačením evaluate nám ukáže výsledok funkcie.
  7. Spustí program na novo (reštart aplikácie).
  8. Pokračuje program až kým nenarazí na ďalší breakpoint.
  9. Pozastaviť debug. Po stlačení sa odpojí debuger od procesu.
  10. Zobrazí okno so zoznamom breakpointov.
  11. Ignorovať breakpointy. Debuger nezastaví program na žiadnom breakpointe.
  12. Callstack - Je zoznam rutín a ich podrutín. Vďaka nej, vieme identifikovať odkiaľ sme vstúpili do danej časti kódu alebo rutiny a kam sa vrátime ak naša časť kódu dokončí svoju prácu. Rozdeľuje sa na biele riadky - znázorňujú rutiny, ktoré su definované v našom zdrojovom kóde projektu a žtle riadky - znázorňujú rutiny, ktoré sú definované v externom kóde ako sú knižnice. Skúste kliknúť na biely riadok a debuger vás nanaviguje na riadok, z ktorého pôvodne vstúpil do rutiny s breakpointom.
  13. Prehľad premenných - Zobrazuje aktuálne alokované a viditeľné (v scope) parametre pre danú časť kódu alebo rutiny, ktorú práve debugujeme.

Identifikácia problému

Dôvod, že máme výsledok 0 môže byt zlý výpočet, ktorý nám na oko nie je viditeľný alebo sme nepočítali s akýmsi vedľajším účinkom rutiny na výpočet faktoriál. Preto sme umiestnili breakpoint na začiatok bloku kódu, ktorý nám vykonáva výpočet.

Môžeme teda posunúť náš program o krok dopredu tlačidlom č 1. Už teraz vidíme, že nám pribudla premenná v prehľade result a jeho hodnota 1.

Teraz sa nachádzame na riadku s funkciou repeat, ktorá vykoná n krát lambdu. V lambde voláme funkciu, kde násobíme výsledok aktuálnym indexom opakovania. Logicky to dáva zmysel, lebo faktoriál napríklad čísla 5 je f=5*4*3*2*1, index v repeat začína od najmenšieho čísla a keď budeme násobiť opačne, nič sa nedeje lebo platí komutatívnosť.

Skúsme teda vstúpiť do našej lambdy kliknutím na číslo riadku. V prehľade vidíme aktuálny index repeat pod názvom premennej i. Čiže repeat nezačína od 1 ale od 0! A násobenie 0 je vždy 0. Môžeme teda opraviť našu implementáciu pričítaním result = calculate(result, i+1). Hotovo, úspešne sme našli chybu v kóde pomocou debuggera 👏.

Threads

Ako som už písal vyššie, ak debuger dosiahne breakpoint, pozastaví aktuálne vlákno kým nenecháme program pokračovať. Môžeme mať aj breakpoint na inom vlákne, ktorý stále beží na pozadí. Čo sa stane ak takýto breakpoint dosiahneme? Vypíše nám debuger, že sme dosiahli zastavenie na inom vlákne. Takto počas debugovania sa môžeme prepnúť na iné vlákno a debugovať.

Rýchly príklad

Používanie watcherov

V mnohých prípadoch pri debugovaní sledujeme viacej premenných naraz a ako sa menia ich hodnoty. Náš príklad je primitívny len na demonštráciu, ale v skutočnosti máme desiatky premenných tried s triedami ako ich vlastnosti a tak ďalej. A niekedy takáto chybička krásy, kde čakáme číslo 1 a máme 0 vie postaviť na hlavu nie jedného developera.

Na pomoc si berieme watcher. Watcher je akýsi stalker premennej, ktorú mu definujeme, že ju má sledovať. Naše definovane watchere sa vypisujú na vrchu v prehľade. Ich hlavnou úlohou je nám developerom uľahčiť debugovanie. Bez nich by sme sa v mnoho prípadoch museli preklikávať cez vnorené triedy.

V predchádzajúcom príklade, určime si breakpoint v lambde repeat. Spustíme program, pripojíme debugger a vykonáme akciu aby sme sa dostali do nášho breakpointu. Povedzme že chceme vedieť výsledok výpočtu už predtým ako sa vykoná. Klikneme na + v prehľade a do riadku si napíšeme rovnaké volanie funkcie a potvrdíme enterom.

Teraz ak budeme pokračovať v programe, opäť sa nám zastaví proces na breakpointe a vidíme už budúci nový výsledok.

Tak isto vieme pridať premennú do watcherov ak na ňu v prehľade klikneme pravým tlačidlom a zvolíme Add to Watches.

Zoznam breakpointov a jeho vlastnosti

Debugovanie môže byť v mnohých prípadoch chirurgické. Tým myslím, že chyba sa deje len vo špecifickom prípade a my pri debugovaní nechceme strácať čas zastavovaním sa na breakpointe keď na to nemáme dôvod.

Kliknutím na zoznam breakpointov si otvoríme okno, kde môžeme vidieť všetky naše breakpointy v projekte. Povedzme, že naša chyba sa deje len ak chcem počítať faktoriál čísla 5, ale v ostatných prípadoch to funguje ako má. Môžeme zadefinovať podmienku, kedy sa nám debugger ma zastaviť breakpointe:

Alebo ak by sme mali viacero breakpointov, vieme dokonca vytvoriť medzi nimi závislosť, kedy sa majú aktivovať. Napríklad ak debugger dosiahne breakpoint na riadku 15, tak vtedy mi aktivuj breakpoint na riadku 17.

Môžeme mu nastaviť aj aby zastavil všetky vlákna, nie len to aktuálne, alebo aby nezastavoval vôbec, len aby niečo vypísal. To sa špeciálne hodí, ak chceme aktivovať set breakpointov ale náš aktivačný bod nie je podstatný na debugovanie.

Je mnoho rôznych kombinácií, ako si vieme breakpointy zadefinovať a všetky tieto možnosti nám majú uľahčiť a urýchliť proces debugovania.

Vlastnosti breakpointu vieme zobraziť aj pravým klikom na breakpoint v riadku.

Tomáš ParonaiAndroid Developer