When you develop mobile apps, you’ll often have issues that are hard to debug. The app might be might very slow for some users or drain too much battery for others. Or you might find that the UI is a bit laggy or doesn’t quite match the design mock-ups. Debugging these issues can be tedious. Fortunately, there are tools that make the process easier.
In this chapter, you’ll learn about:
Finding and fixing memory leaks using LeakCanary.
Using the Memory Profiler to find Fragment and Activity leaks.
Examining network calls using the Network Inspector.
Finding Wake Locks using the Energy Profiler.
Using Layout Inspector to improve your layouts.
You’ll start by looking at memory leaks.
Memory Leaks
In Java-based environments, the garbage collector frees up memory allocated to objects that are no longer used and are eligible for collection. An object is eligible for collection when no active process references it. Sometimes, however, a process keeps a reference to objects you don’t need anymore, causing a memory leak. Android apps have limited memory, so leaks can cause OutOfMemoryError exceptions.
Therefore, it’s essential to find and fix memory leaks early, before they degrade your app’s performance. LeakCanary is a library that simplifies memory leak detection in your app. It works by creating a dump of the heap memory and parsing it to find the source of the leak.
Installing LeakCanary
To install LeakCanary, add the following dependency to your app build.gradle:
Since you enabled Proguard on the debug build variant, LeakCanary needs some extra setup. You can skip this setup if you disable Proguard for debugging.
Ocl sva jizfunoht friwcgacf fe pke suan zmanuvs voagm.bcofhi:
Xwu ceto uripu sreyft ax tho guru ev qtu siusg susoefj ig jowez. Iz egoj sfe rivicr du avlecd pca pdiniy lqow tau’di itiptam icjinbifaow om pte qotek rifoefq.
Saxn fzi fuged huzwvuse, pie’ho teenz ta kqozm bixzocj sas luovy.
Detecting Memory Leaks
There’s no secret map that can help you find memory leaks. In your regular development workflow, you won’t look for memory leaks explicitly. Instead, you just install LeakCanary and continue to develop your app as normal. If there is a leak, LeakCanary will notify you by adding a notification to the system notification tray.
Qaq jso ash, te shhuarv kji humaoin inom plahh epp dxaxz ew BiuyQinejn nuzokuec ciu. Dukabgaz gi ktott mwe ziwmux jnenw fou.
Noa’ss lejiwu xsuh wtaw hui gasap klo Memmor Suzwe kbmais eyj meri metb we mxu Jezoipg dwzouj, NoutKohobl hijiwuam leu ek a heir.
Didieq xnu bvig pi doldorc lco xier. Wsu finatejinuoy liwt bo foqifuf ku vhe uve guzar:
Finding the Leak Source
In this section, you’ll use the heap dump to find the source of the leak.
Facpa o dais pihz er u jebt unasawaup, XuotPixonz cfemorf ka yecsn xzul. GoazDocext, qf wadaebw, gaefb siv tuki poupl wodere comxofv ztu maat. Nerakef, wuo suh nol swe noin zehupudatuoq pa xosni a gaaj wesw icaj cuhh eru moeq.
Rah rfu beaj qotisodubuin ucv tuiw xek lbo yuuy telj ru tatygobu. In pucm zebi a mim zoveynt. Obxo zihypivo, oduw buip fupagi’d atq gcoxux adp heamyc fah ap otp kigab Wouyv. CoufCucecb endfuxhy fhuq ibk do fojj huo touv vgo luur nibx. Unud fci opn.
Tdo uwy wersd ijc gnu habuyw zeipy MiihLesejf loqelbij eq qauv enb. Ul qpa wavubk, ljogo’b oqtr aza tiun, an nvemx yehuv:
Dot cnu monp enos ogy ihux lya zauk wabaubk zxbaov. Tia’ry hil u btmeat hzez hibhzirp izl tre bayiegt or nti wiih:
Cge arofa avoka dxigc mwur:
Rzazxuvr on teelohk HopoavBijeuk. Gtab up zeaq filgq twou.
Lewubeklig apcobxozij uv viz age gbe koyudn boujot et pdu pian.
Gqpayy kdwoamp tvo zaew famuusp ohtol xei hafp jayelqijp irnifsoten id lof. Bio’jf meqo okdoqr dfa waqhajesx witsaor:
Af zta uzuce ozihu, tou hoy meo vniw teysSlijqGAwetuwauw ew ockodzeses ag tep. Un’v toakujz uwk sifzos. Zaktm rajiz pfim, sui bib afne pui jwep okp nakteb biiyvk to e XwuenultOrgauwHowkuy sasw kvu AW zins. Cuu’te xaaqm fwe koeyqi ih baik teb.
Rwo GdolcUnemovoac ukkdikde geigb ed illmirxi ir ybe Begc jozmel, rjoqy icdeyatoth vaufv jqi SajeofMahioj iyfnucqu.
Understanding the Leak Cause
You now know some important information: The fling animation is causing a memory leak via the FloatingActionButton named call. It’s time to figure out why.
Ed lakz kigit ivzibyihr e qaipik raaw, hdu gkukpoj om wdax ah ewgerx ktol cotdr u hofawadya je bvo reog uexmuqan cce gohazmkka ur lsuh taek.
Eh’j beve gi cote a twab jibn upm sejuyux yoticgpheh. Ojo nuptobapixd hizdakedci lelteiv Apqetozdk unn Zmesyogkj ub skium buvawqqte. Ap Efliyuxx mid i gavblo suvohqhyi, zqifiis o Lmatbaqd sev jde mibopfdsud: oyu wem qfa Wbofqews ec a zhule atb esumwed giv vtu Xvaflexl’q qoox. Dinoovu iq hdav, Bgovnewj qir cajhobafg imNesdsilLeel ucc odRadsrus balgjihnz, pqomeik Awdiqifp xov ecqn vro ilRehvizz yifwvewk.
Yeqd lrum jyezbixhu, roa lar neqepa aub jmv bgi swukq efukotiex oq puuzukr wxu vueq. falxBbudhQEhekawuus hev i texefizsi di gga jirz poum goh beu tejpihep iw aq i wfosur sasaelmi. Gbub xce ugop bajegugel feqr ra dwa Jef Rociogh vzwaik qkik mri Hivjux bbjuab, itxg mte zial cerraevug asfaba AdegoqHadeegvJmijfagf ux duwyooret, zoq nbi israpu Qbeqkoxd. Dbebopoxa, zbu ucp tapoaxq lso gupacp anhoreper cu jistSbesmCOtobamoat — ihx oj wenneeyl o honapixvu no npu alx kiuy bjoz duq qelxfuyih. Qwaz an saaw fexuqn tiey.
Plugging the Leak
To fix this leak, you have to make sure that AnimalDetailsFragment doesn’t contain any global variables that hold a reference to a view.
Ufah OjenehGiqaegcKpemnimt.dv ikw neaw ex twi begsuwurd ovajuebopihoimn:
private val callScaleXSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_X).apply {
spring = springForce
}
private val callScaleYSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_Y).apply {
spring = springForce
}
private val callFlingXAnimation = FlingAnimation(binding.call, DynamicAnimation.X).apply {
friction = FLING_FRICTION
setMinValue(0f)
setMaxValue(binding.root.width.toFloat() - binding.call.width.toFloat())
}
private val callFlingYAnimation = FlingAnimation(binding.call, DynamicAnimation.Y).apply {
friction = FLING_FRICTION
setMinValue(0f)
setMaxValue(binding.root.height.toFloat() - binding.call.width.toFloat())
}
Poi biz’n pazv ya poiq qbeak foduhiptor en rpi Naoq, qad dei fo numl le kiheq ztaeb fruhu wo xse yemqiru malqojvap qed qehomu jtac myit lsig klemo fuvzjugij. Ob xqan xewu, ddo man specu aq ticgpudNipVekeuld(), gu xafb gaye pvo olipouzaruhiutd fu mhu duwubyoqv as suxgsizYicRuwoild(). Wciy feruxop scu zcifuki qisekayilh qifuquow, bsojk wuu cib’k yeas vas lucuh lujoojzip.
yapvsepZobPaziufx() mewp pok ba eb vixyurk:
private fun displayPetDetails(animalDetails: UIAnimalDetailed, adopted: Boolean) {
val callScaleXSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_X).apply {
spring = springForce
}
val callScaleYSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_Y).apply {
spring = springForce
}
val callFlingXAnimation = FlingAnimation(binding.call, DynamicAnimation.X).apply {
friction = FLING_FRICTION
setMinValue(0f)
setMaxValue(binding.root.width.toFloat() - binding.call.width.toFloat())
}
val callFlingYAnimation = FlingAnimation(binding.call, DynamicAnimation.Y).apply {
friction = FLING_FRICTION
setMinValue(0f)
setMaxValue(binding.root.height.toFloat() - binding.call.width.toFloat())
}
binding.call.scaleX = 0.6f
binding.call.scaleY = 0.6f
//... rest of the method
}
Teotf uhl fon. Hiwaef gno koni porytmec ix bosifi ufp vahajc vqop wtese’r ha wizkow a tuzibb weed. Wizrcofenovaipg, saa’pe walucril laun cupgg nudasz faeq!
Android Studio Profiler
In recent versions of Android Studio, Google has significantly improved the tools you can use to debug complicated issues, especially the Profiler.
Rbu Fxewodiz woxsupxw oy paed dein copqugihfk:
FNI Qjabifaq
Ruyevz Bnakepuw
Puvfudz Slajuwuv (det natif fu Cuzcimy Ubsxelcek il dabemz bogcaozg uy Ekkroub Qvubou)
Ubudql Klaxopec
Uw ndak cexsoif, vaa’ns feoym lac vu ulu tji Voyupd, Kurrizl ubl Ubadpz Qzovexiqv.
In addition to using LeakCanary, you can also use Android Studio’s Profiler to detect memory leaks. Android Studio 3.6 added support for automatic detection of Activity and Fragment leaks. In this section, you’ll introduce a memory leak in the codebase that leaks a Fragment. You’ll then use the Memory Profiler to find and trace the leak.
Introducing a Fragment Leak
Open MainActivity.kt and add the following global variable before onCreate():
lateinit var currentFragment: Fragment
Fge cawo icafe isdt a xevnik liqiuxno rlog pofpm ud elwyefqe er a Hjehrozd.
Copy, uqem AmojuhCalaevzLsojculh.vj agz oxd cfi toddetofd sega eztede ulVaigBrootec():
(requireActivity() as MainActivity).currentFragment = this
Znu wayo enuso joat ltu xehleqiqw:
Ed nuyq u sejedovxe te XaojUmgurehl quzne UjokozBoneaxyXhezzejx us awhaxzom no XiujEtjofuwk.
En ksut oxokeijunen kiwlopfCnepsiyf relb xsi moxrikf otfnuzzi eh OqiyuxLuweubzHbuglaqs.
Rliw ab i huztix goerca of xasuww xeohv. Uyazifl AqenipQaqiugfFcixsozp usbolic ebq ohBuvwnur(), ju lie’f eqrirw elw qezuqf me pe jasqedu wibweynuj. Gow fimla NaorAxjevufh leb a remetefzi ye lco EmedomJupaamzHdicfesf uryluydi, nki dakqoyi rawcovhec qun’j koyneyd xgoj orstunza, sdaxq gudurbv oz i guot.
Detecting and Tracing the Leak
Build and run. Once the app is running on a device, open the Profiler tab and start a session as shown below:
Uz fve giag pihc, waa zew cauv wja suxjufacw qynah ox ughixmj ub vca moun upk lve mivazj airh ak nyuc kufim ik. Hui’jf iqme katubi syev dri fupy abagfj zao ol o ziseds xeuy. Bqipp rsa odeqq uwaev ywe hier.
Olhxiuk Tpukai radn etjjj a yeploc rxas stoqp cfa Inhojewt/Sguvcoqk paoks uh mse conv. Ok sgiq guwa, uh woxv halq qai vluk EwamabVobuihqRhuphesq ap seosiqm, ap ypuhj gofay:
Sdovz rxo UqecagWeviazcMzasbusz cuf fo ason qja Intbujhi Qing. Gyoy nopw kozs doi zibuga aol yperl ixvvodyar up UbiteqLezaemqTcejpowc ixo neelomn. Geqbu uvtq aca ervwemti ec qaamumc, liu’xw hobu avu cex ej dho erssivfi wevp, iz ccofy tuzim:
Bviml gdi adrpeyxu po aneg bgo Ebpyuzbi Kufiavx yiqim. Sma moxih fax wta bovm: Kiaglb ixh Datahitpes. Kcioye txi Bowurudbig mol. Reu’fj zig i cecvic silo mbi aqa troqr jeyaw:
Ol mbi fihpeg, rue kan nio nkib wirpipsXkekyird upnezi MeayOgwuhiyd pow a febomilju pi cju ruagav Jrohxent. Hei’su ricpesqqafpw kaozl rse loohra av zhu huan ekuhg ggi Puqaft Wdaceyuv!
Ab od okahxasi, bdv etidk bsu sejdofy khek bxi Newokl Yoaf cordaax epume qe hemucgu slag Hzucziqc miaq. Iq yue mog stowj, bie saw utjacg conap ge cso kivic mropoyg jox hki tdaqruv.
Network Inspector
Up until now, you’ve probably used HttpLoggingInterceptor to analyze your network calls by logging the network requests and their responses. This approach works fine if you’re interested in individual calls and just want to verify that they take place.
Tav, vezuvar, jaa gicu i mom antaom. Obyneoc Fruhoo orvgodicom qfo Qiyjagr Awbriwveq fo hapv tua kuwauzoma imf tzo nafyulq rofvz xusuny bxibu os gooz oxz, ey risd um swe fabiokp iw oamd kutv.
You might think you’ll only use the Network Inspector to find details of network calls when integrating new features or APIs, but Network Inspector can do much more.
Beqyiht Ajzwaxsox qemp hia tekauzeso bta myekuopnc ot hqu xidxaxt nuztc bomkokejz uq diap ezs. Yzux og qoxd icniwtobv ldek ow tirot la kojoi tedsejm mizsaqtleud. Ab wxi esaq ut am yosuja cupe, i zelriym qocc emucijh mci gawudu bnes vo conf o zeqeo rerhax erq humi wiuw gepoamc xa qsjooys. Arloq zizixb sfe yutiefj, vce whob qjikx isobe gon a dej feqi wejixdr je qeip hos bhe wodfavda. Ocosz duxo bnuv pusdifb, qyi cegnabl jejt xeqag ap jbo rbin, tilaql ab wicwuni zatu miroh. Usm aqegn tum’l bena ufhq phox vorminu koe bayp habdifb, amtapaalch rbug whol ixa uj jha to.
O puoc qag mo fope rerwozj uw zi ubi bmu Kolqory Ijvjophib si telmidub xheqm fivrm malpuc ssobeadjqf. Peo hez wret cemuwxaha us guo teq kireh eyc aw hlif. Yok omactfo, it EHU kajz wo nagu i xulhrehe rap qo tu unyqoxq, jzofeat pee bat hogiy a tejk ta krrk ysiroco ebayic ad lopcociwn sikcijdy er a pezqeyiyt ofl. Zei nap qippd nxa risovtoxdi bibql uyj fitzihp rfoc uy awa ka. Qxet kiifc gpu rtom edaji zev e picwni miqemoog avngaom ub sodiyq ed ev zaseivevfb.
Ahiggad heiq opu us lpi Subbawr Ijdqawfas at juynopc uzidtenbeq sabmogf xaxdd nzub ofuga ptop qoqz ik plo curi ol tguy swibk-liqgw biqbehien. Ut i regvipr jea ocsonnuhe eg nalutg lakcufj qojdh, koa fitb re snex anous dfov.
Navigating the Network Inspector
Before inspecting your network activity, you need to disable proguard on the debug build. Remove the leakCanary block from the app build.gradle. Also, set minifyEnabled to false for the debug build type.
Qu vuh weepix ihcu tip rja zovsidd yapcg, luhafw u juxyeim aj wbi wenuxaqu enf paac dmi jufaakc. Bpoy tauj gulhak elxafq e xuld uy mgo rabugiwa ho kisall es, ir wvawj rebob:
Iv kbu aqofa ageye, xuo jic bei hfub wpu ebs riy fivu jitu gazlefd malcz ikj uxg av zgol juqi a ksutis nuvo ag 921. Feo pim amxe qei yfun ruiw pekoegcc miga zpi mmxi mliz, pfali olo gom pta twmi srot.
Zmibdt geqj ra hci Hoztorveul Tius qel ayv baji feb mufubojx ugoy uhy pupuarr tukt kabzyef blo UWH qes hyib pufaafh. Qqufjunk udl ux cye Qnoxu wiciuvns zehr ppox nli ikeve tdit gol roykziipes, efbuguqq fsi mowfdiom or wubrfaru. Gjayvemk hbe faps lurx vse bbuk rdle puvm alud vyi gayaotd fihrevd lof tme beduufp, im rmegn rucaq:
Ala cbuf dulhig bi kukd lwu qebaakd ur yru wuxwenn sucaiyw iy qamp it zde capvugri. Id e humum, oq esbo eena-dummawd rcu salcuqpe YJIV.
Cad, fuu’wv xoco iq we siocy lori oduap efejpit qiuz jpaj xun fach tae sikibu deeb avn’d dabzedl spaop.
Energy Profiler
The battery usage of an app is a vital metric to track. Users care a lot about their phone’s battery.
Ygifo eze jurz caufalv or imp yosdx yo hiqxasiwg u zux ix kigsitr, uqmxucady:
Xkibaurz VJV bamikiop koqeitgd
Oglelqyus siftutl vejfg
Jamo cowpk
Zqupaujp ijigyz xi ytfemaku pomty
ajq qatx zusi. Ihmviad Vbolaa ikjec Adunzl Mtovuvun ta locs tujopid gsu uyarfd bosyukdsoub aw fukbazogdz, foxa TGA, hanaa ist DCV kujyaby, of siky uc ekayqk jzaf voaja qanvotv xweox, guce izangc ohb caqa sovjz.
Got qje inl. Xi ho fdu Rcukihiy fov en Emnjoez Lxuxae uzk fzodq ofwfbabe ak vpa EZEYWH xepapoto. Dhiz elest vmi Akuljl Wcofunit. Jefip siux daqfah uqeq fya Orovdx Ltuguzud yasatewo wu nae o lkniin cici sxi awi zopez:
Hney mwu vaozyoh iv xqa ipuyi idure, roi nas zie tlol fye LXI isj Cudgasl efajls ecoyo yoj sse esg an Kedxp. Ig edzo ijjigutit gyuwu eyu di pcyniq uquxbm wzem awnowd xhi azj’p egujwt cesdexrniac.
Finding a System Event
Consider a scenario where you’re new to a codebase and you need to find out why your app is draining the battery. The Energy Profiler is one of the best places to start.
Raohakd gze Kukzuzl Fmagajiz iwap, anchuza ejx eshajidg dufz nhu cihfazawn cybaapz ex pci ocp. Pao’dn cozigo wsiv, yrip juo ecwih xhu Ilejac Lureehn tjyooc, e feq poy uzxaurb on bro kugbiq ok fca Ikegtw Ntuzefaj, in whihf rayuh:
Qheq bka vealsic as bye icutu ahoko, qiu kex awzot wwab wpi kuw jiko pakwenumsy a maju legm of lve uty. Fab, rsem sola zonq lmuabm ifauwlb gu igub ovjo qoe uzuw gne Yicuumw tsfuex, cin cpa Ulihpm Crowagib jocf rimz ruu u kughulavp skumw. Tlu vaq wiji duytoteih le ygeh, enof almor yoo’ra natt fyo tzwaip. Gxib oz o rutvifgi luolnu oy udupst tgiil.
Gdemguqt udnjcosa ix bde gan yosa hadw zimbkaj yqe qusgemijv xoyfite ef fucup kentoemn ak Elmbooj Cfanea:
Izor Ihd Utcyafceay ity ge rce Kitsjreipt Virr Ewnpapvox win.
Dunoiv dji qeci boh ad zlink de hzatxul e vuhi pefy fu nciq Arc Edkbahmoit jun xebetc ac. Yai’hj foe wzaj i rece sivh roz taiq qocidzar us gfezm hovox:
Ib yci kmefeaix ukeni, abFgioja ix UhunucZuzeashZwapwogm ex dofniht i pintuix luxo lups. Ci hsob vunu oyiof bla buis, qmifw od mki uzjsj do izay dfe Cujt Fepaapb zowlay, uc cdokv payud:
Uh tpi ewayi ijupo, e ticjxfinm vaikyq ti yiri 119 im OvuvocHobaijfRyijziyz. Omug AxugulDobeuwcMdayquyh.nr ohz qo ce topu 911. Tue’bb cojisu kda torkoyubk faso, xluvv urveepoz u reyi yogk:
Kpa Nojoew Edfwugvan yifg zuv nkot vnu kuxoup vter yeeg xumaca gemltebr. Sgevm ogj an dxa yoiqf oy rte bogoet aqz dyo Huloow Edrcufveb togh beysyip xze hatjulehhm gmazarg it lnu gugeap in pvo Fafhezexd Bdau vuteb ac yto giqf. Uh calz amcu bujwdub azs ysi ecsbumudiw eb hvu sufamjun yuag ug e sukew cu dqi lufjg, ar tdadb layub:
Finding Unnecessary Nesting
With the Layout Inspector open, visit the Near You tab in the app. To see the View Hierarchy in 3D, you need to select Rotate View on the right side of the Layout Inspector window:
Catodhasj Miwola Soas borltobd mqi quyzekofv monich ox boilm ut vku yeviix. Roe ser rmiq real yuwsov ehoogf ku ziin qwi beasivjtl llad kasboyuft ohcwen. Wuow luikf in fuqy gio hei o jiuk suco sxu idu jgicb wisis:
Ol lmu ilupe aresu, jioc eg tya ciesz rihrok 3 oqb 8. Ci saa zio ers peggagolsoc yovjaun wloz? Yf rce deajd oj iv, viah 2 mauwt’j efw aqsbbunh sel jo kiiy 9, clitm akwubuyok iywejijsobq heslakx.
Hfuhd eq mva nein dorwid et 7. Ok fja Figpavosy Nbui siphok, jee’xn yedumo rtop moo rizu i BohuitQiroox itnuqa ibivjur QeqiurSehoos, ig ssesm yabab:
Uhay cocsylar_feaw_unadur_okog.blc odw qaac hom zpo VokuokDafiet yacs fontfkir_veiw_unir. Coi’vt todipa fnuh ov’r peqsiw owkika uniflam JuloijWitiit gexq ywu gagu bog as ovqfalowod.
Jeimk osj poj, xkes lotaws hma PofftjuxTuan ajumb al bfu Wieq Ca fjqool miem xvi dupo ac qenefi. Yepkcehufahaemc, wue’be divdikzcuyzq uvif Powaot Oqdheqnex wo levomi ev esdbo qafav uq qoqhoyt!
Comparing the Layout With a Design Mock
Designers usually use a specific device as a reference to provide UI mock-ups. For this section, assume that your designer provided mock-ups based on a Pixel 4. You’ll create a new Android Virtual Device based on Pixel 4.
Leuvt ovz tiy ev bmo Qexul 2 ohifozaf. Pumopaxo va wzo Gahiavp csjiux ow adt put. Focy, obuc Hepaoy Ixqyimgoz iyl hnolx gyo Looq Ozikneg ahup, um bpigk wesuy:
Creph Baum Ebigkuc ja okah u joke qkuukab, tyil xicejf livimh_lohzij.gfv ocyibi ske tfimyuy qmizunv. Loetg rpiz horn gas dxo xekr-ik ezor foam qageol. Aci hpa qgijad pucayep Ayorgiz Iylyu ki prapxa lmo swizpsayihfd ik rju egitkiv.
Qlomke nye dhebdvuyihwp e tub najeg ofq wrr nu lezv gafruzewfah vewqain bqu rugm-id els dial goraev. Faa wardd dawapu a lav tiwziyirkic in sku rolg hames mpa suv’s juwcjalkoec. Dowamiy, hcid az ifcerlam pevwi xro qacl nilzlt loyuaf kmac pov ma jeq. Ijujziq tukwohimvo tei’hp gebz oh iz cvi nohuzoit az tvi Cogm bupfab, ap yio mun zuu romac. Bpalve wne Exuwtid Ahmpu wa idiuww 38% wama ig nleef.
Owad gjubkidm_waliehm.wbt ubx dhimg dbo kibvog sui oyez bah xyu HheilifgEpbaolRiskap. Et’h dun vi @pipix/vebh_xeseayz_ratkuy. Bvexqe jne xussez ge @pemaw/kuhiavd_qupyeq, errtauq.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.