Kreiranje custom listenera (listener pattern) u Androidu

+

Uvod

listener

“Listener pattern” je jedan od najčešće korišćenih paterna koji se koriste pri razvoju aplikacijja. Princip rada se zasniva na tome da u jednoj klasi definišemo dogadjaj i trenutak kada se započinje izvršavanje dogadjaja (“okida dogadjaj”), dok u drugoj klasi definišemo objekat (tzv. listener objekat) koji osluškuje taj dogadjaj i reaguje na njega.

Postoji priličan broj već ugradjenih listener-a u sklopu samog androida, ali pored njih možemo da kreiramo i sopstvene custom listener-e i tako omogućimo da definišemo callback metode za događaje koju su trigerovani i iz drugih delova našeg koda. Custom listeneri se koriste u sledećim slučajevima:

  • Komunikacija izmedju fragmenta i Aktivnosti
  • Komunikacija izmedju dva fragmenta preko Aktivnosti
  • Komunikacija izmedju dijaloga i Aktivnosti
  • Primena kod Recycler View-a
Primer standardnog listenera ugradjenog u operativni sistem

Ovaj patern se često korisi i u sklopu operativnog sistema, a najbolji primer ugradjenog interfejasa u android core je OnClickListener koji je zadužen za “klik” dogadjaj. Ovaj listener je definisan u sklopu View.java klase.

U sklopu View.java klase je definisana setter metoda pod nazivom setOnClickListener(). Pozivanjem ove setter metode u nekoj drugoj klasi se definiše listener objekat koji osluškuje taj dogadjaj, a kroz njega i callback metoda koja reaguje na dogadjaj. U ovom primeru objekat “btnNekiButton” je naslednik View.java klase a samim tim nasledio je sve interfejse uključujući i pomenuti, pa može jednostavno da pozove njegovu setter metodu:

Ili ako prikažemo poznati kod sa “on the fly” kreiranim objektom:

Postupak

Interfejs može da se definiše samostalno u odvojenom fajlu u komunikaciji izmedju dve klase se iz praktičnih razloga definiše u jednoj klasi. U ovim primerima ćemo koristi termine “PrvaKlasa” (mesto gde se definiše interfejs tj. dogadjaj) i “DrugaKlasa” (mesto gde se registruje objekat koji osluškuje i definiše callbackMetoda tj. akcija koja treba da se izvrši nakon “okidanja” dogadjaja).

a) Interfejs (prva klasa):

Kao i kod već pripremljenih listenera u okviru android operativnog sistema i ovde je potrebno prvo kreirati interfejs. Ovaj deo posla je u prethodnom primeru napisan u sklopu androida, dok ćemo u ovome slučaju mi to da uradimo. Interfejs obezbedjuje da se svaki objekat koji implementira interfejs ima callback metodu:

b) Setter metoda (prva klasa)

Pored interfejsa je potrebna setter metoda, čijim se pozivanjem definiše koji objekat je listener (tj. objekat koji osluškuje dogadjaj). Ova metoda nam omogućava da taj objekat definišemo bilo gde, jer je dovoljno da ga prosledimo kao parametar kada pozovemo tu metodu (pogledaj više o tome ovde).

c) Okidanje dogadjaja = pozivanje callback metode (prva klasa)

Pozivanje callback metode se može smatrati kao okidač dogadjaja, stoga negde u okviru klase pozovite callback metodu i na taj način će biti “okinut” prekidač i startovan dogadjaj:

Mada treba izbeći exception:

Celokupni kod iz prve klase možete pregledati ovde.

d) Definisanje objekta koji osluškuje i callback metoda

Svi dosadašnji delovi se kreiraju u jednoj klasi koja je napravilia dogadjaj dok se ovaj deo koda nalazi u nekoj drugoj klasi i zadužen je da u toj drugoj klasi definiše listener objekat koji osluškuje dogadjaj. Definisanjem listener objekta praktično definišemo i callback metodu koja reaguje na dogadjaj. Postoji više načina da se to izvede:

d1) Definisanje listener objekta i callback metode kroz setter metodu

Definisanje callbackMetode može da se izvrši na dva načina u zavisnosti šta je listener.

d1.a) Listener = anonimni objekat

U ovome slučaju definisanje callback metode se vrši pozivanjem setter metode i prosledjivanjem “anonimnog” objekta koji implementira interfejs a samim tim i callback metodu. Da bi mogli da pozovemo metodu iz druge klase potrebno je da je pozovemo preko objekta prve klase ili objekta koji implementira interfejs (više o “objekatKojiImplementiraInterfejs” pogledajte ovde).

Objekat koji prosledjujemo kroz parametar kreiramo kao instancu anonimne klase koja implementira interfejs (kako se “on the fly” kreira objekat od anonimne klase pogledajte ovde).

d1.b) Listener = Cela druga klasa

U ovome slučaju cela klasa implemetira interfejs, pa je potrebno da se cela klasa registruje kao osluškivač tako što se kroz setter metodu prosledi klasa koristeći ključnu reč “this” a zatim override callbackMetoda:

d2) Definisanje listener objekta i callback metode kroz konstruktor

Ako je listener izrazito bitan za samu klasu onda se setter metoda zameni konstruktorskom metodom same klase:

U drugoj klasi na isti način kao što setter metodi prosledjujemo objekat koji osluškuje dogadjaj, tako sada kreiramo objekat na osnovu konstruktorske metode PrveKlase a zatim ćemo mu kroz parametar proslediti “on the fly” kreiran anonimni objekat koji implementira listener:

Ako “anonimniObjekatKojiImplementiraInterfejs” generišemo “on the fly”:

d3) Definisanje listener objekta i callback metode kroz lifecycle metodu

Fragment

Ovaj pristup se koristi kada fragment treba da prosledi podatke aktivnosti. Kod u okviru fragmenta je sličan kodu iz PrvaKlase, te se u okviru fragmenta nalaze sva tri prethodno opisana dela (interfejs, setter metoda, okidanje dogadjaja):

Aktivnost

Kod Aktivnosti se postupak delimično razlikuje od DrugeKlase jer se u sklopu metode onFragmentAttach() proverava da li aktivnost implementira interfejs pa se tek onda definiše da sama aktivnost postaje listener objekat. Pošto Aktivnost implementira interfejs overajdovanjem callbackMetode se definiše akcija nakon okidanja dogadjaja:

Drugi način je da se u okviru samog fragmenta proveri da li aktivnost koja se dodeljuje “mListener”-u implementira interfejs ili ne. A zatim nakon provere i dodeli aktivnost (pogledajte ceo kod ovde).

NAPOMENA:
Ukoliko treba da se registruje više od jednog listenera onda je potrebno prilagoditi kod da radi sa nizom listener-a:

Pogledajte ceo novi kod ovde.

Primeri korišćenja listener paterna

Primer komunikacije fragmenta i Aktivnosti

Da bi fragment bio stvarno reusable potrebno je da ne znaju ništa o okruženju jer samo tako mogu da se koriste u različitim okruženjima. Komunikaciju izmedju fragmenta i nekog drugog objekta/okruženja (npr. aktivnost) bez poznavanja jedno drugog vršimo uz pomoć interfejsa. Praktično preko interfejsa definišemo koji su minimalni zahtevi da bi objekat “razgovarao” sa drugim.

Kod u okviru fragmenta:

Kod u okviru Aktivnosti:

Primer komunikacije izmedju dva fragmenta

Pošto direktna komunikacija izmedju dva fragmenta nije dobra, komunikacija izmedju njih se može ostvariti na dva načina:

  • Preko Aktivnosti koristeći listener pattern
  • Koristeći deljeni “ViewModel”

U ovome primeru je prikazana komunikacija koriteći listener pattern. Poruka iz PrvogFragmenta se šalje u Aktivnost, nakon čega Aktivnost kroz callbackMetodu šalje poruku DrugomFragmentu.

PrviFragment

Aktivnost

DrugiFragment

Na sličan način se rešava komunikacija izmedju Dialoga i Aktivnosti.

Primer komunikacije izmedju adaptera RecyclerView-a i aktivnosti

U okviru adapter klase se standardno definiše interfejs, a zatim i setter metoda, kao i okidanje dogadjaja. Iako se u primerima na netu često može naći da se okidanje dogadjaja vrši u okviru “onBindViewHolder() metode, to nije dobar pristup jer se poziva svaki put kada view skroluje, stoga je preporuka da se okidanje dogadjaja vrši u okviru ViewHolder klase tj. u okviru njenog konstruktora.

Adapter

Nakon definisanja interfejsa, potrebno je u fragmentu ili aktivnosti koja koristi adapter registrovati listener i definisati callback metodu:

Fragment ili Aktivnost

Ovde ćemo definisati anonimni listener objekat “on the fly” i u njemu definisati akciju na okidanje dogadjaja:

Ovo je moglo da se uradi i na drugi način ako bi npr. Aktivnost implementirala interfejs, pa bi onda bilo potrebno da se overajduje callback metoda.

×

Anonimna klasa je klasa koja nema ime, stoga kada bi hteli da instanciramo objekat od ovakve klase to bi izgledalo ovako:

Nama je potrebna specifična anonimna klasa, ona koja implementira odredjeni interfejs, stoga iako ne moramo da definišemo ime klase moramo da definišemo koji interfejs da implementiramo. Ako je interfejs definisan u svome fajlu to se postiže ubacivanjem naziva interfejsa ispred zagrada:

Medjutim kada je interefejs potreban za komunikaciju samo izmedju dve klase onda se on obično definiše i kreira u okviru tzv. prve klase dok se anonimni objekat koristi u okviru druge klase pa je potrebno jasnije pozvati interfejs:

Pored ovoga je potrebno da implementiramo sve abstraktne metode ovog interfejsa:

U ovom primeru smo koristeći “anonimnu klasu” kreirali “anonimniListenerObjekat” (tj. objekat koji osluškuje), ali “listenerObjekat” ne mora da bude anoniman može da se sačuva u nekoj promenjivoj i da se koristi više puta:

×

×

×

Ako je “objekatKojiImplementiraInterfejs” naslednik View klase onda može da bude ubačen u View druge klase kao “custom view”.

1) Ubačen statički (direktno u .xml)

Ako je “objekatKojiImplementiraInterfejs” ubačen statički (kao custom view u layout) onda ga targetiramo kao običian view koristeći metodu indViewById():

2) Ubačen programirano

2.a) Kreiranjem objekta PrveKlase

“listenerObjekat” je ustvari anonimni objekat kreiran “on the fly”:

2.b) DrugaKlasa implementira interfejs

U slučaju da cela klasa implementra interfejs onda se koristi:

// Lista listenera ceo kod

×

Prva klasa

Druga klasa

// Modal za fragment to aktivnost kada je definisano sve u fragmentu:

×

Kod ovog pristupa u okviru Aktivnosti ne definišemo koji objekat će biti listeneru, već to radimo u sklopu samog fragmenta kroz metodu onAttach(). Tako da listener objekat postaje bilo koja aktivnost na koju se nakači fragment a da pri tom implementira interfejs:

U aktivnosti koja implementira interfejs se samo override callbackMetoda da bi definisali akciju po okidanju dogadjaja:


Uvod u asinhrone operacije i multithreading Androida

Šta je Thread i multithreading?

Thread (srp. nit) je niz uputstava u okviru programa koji se može izvršiti nezavisno od drugog koda. Ako kompjuter ima više procesora i operativni sistem koji podržava tzv. multithreading, to omogućava programerima da dizajniraju programe čiji se thread-ovi mogu izvršavati istovremeno.

thread diagram

Thread mehanizmi

“Looper” i “MessageQueue” su mehanizmi koje koristi Thred pri izvršavanju zadataka. “Message” je zadatak (Runnable objekt) koji treba da se izvrši na Thread-u. MessageQueue se sa jedne strane puni zadacima koji trebaju da se izvrše (message), dok sa druge strane “looper” uzima jedan po jedan zadatak sve dok ne isprazni MessageQueue.

looper

Kod androida MainThread (glavna nit) je zauzeta bavljenjem stvarima kao što je crtanje korisničkog interfejsa (UI), odgovaranje na interakcije korisnika i generalno, po defaultu, izvršavanje (većine) koda koje pišemo. Stoga pošto andriod podržava multithreading, pametno je iskoristiti ovu mogućnost i pri programiranju radnje koje mogu privremeno da blokiraju rad UI-a izmestiti na drugi Thread. Kada izmestimo zahtevne radnje koje se sporo izvršavaju sa glavne niti, potrebno je da se vratimo sa tim podacima na nju jer samo glavna nit može da ažurira UI.

Primer pogrešnog koda koji blokira UI

Klikom na dugme počinje da se download-uje slika sa mreže (akcija može da potraje), interfejs je sve vreme zamrznut dok traje download i dok se slika ne prikaže u svom ImageView:

OBJAŠNJENJE:
“Caller Thread”: je thread koja poziva “worker Thread” za obavljanje nekog zadatka. Često je “Caller Thread” ustvari nit koji crta UI tzv. “Main Thread”, pa se tada “Worker Thread” još i zove “background Thread”.

Kreiranje novog Thread-a

Kreiranje novog Thread-a se može izvršiti na dva načina.

a) Ekstendovanjem Thread klase

Prvi način je da naša klasa ekstenduje klasu Thread i tako nasledi sve metode ove klase. Metoda koja je bitna za izvršavanje zadataka na Thread-u je run().

Nakon kreiranja klase je dovoljno u okviru aktivnosti kreirati njenu instancu i pokrenuti metodu start():

b) Implementiranjem Runnable interfejsa

Za ovaj pristup je potrebno kreirati klasu koja implementira Runnale interfejs. Runnable interfejs sadrži samo jednu metodu run(). U ovu metodu se ubacuje kod koji treba da se izvrši. Objekat koji nastaje instanciranjem ovakve klase se koristi kao parametar pri kreiranju Thread-a. Jedan objekat se može koristiti za više Thread-ova.

Da bi kreirali Thread na ovaj način potrebno je u okviru aktivnosti kreirati instancu klase Thread koja prihvata Runnable objekat kao parametar i zatim pokrenuti metodu start():

NAPOMENA:
U praksi se instanca klase koja implementira “Runnable” interfejs nejčešće kreira “on the fly” bez definisanja klase:

Koja je razlika izmedju ova dva pristupa?

Ako proširimo klasu sa Thread, naša klasa ne može više proširiti neku drugu klasu jer Java ne podržava višestruko nasledjivanje. Ali, ako implementiramo Runnable interfejs, naša klasa se može i dalje proširivati sa drugim klasama. Medjutim treba naglasiti da proširivanjem klase interfejsom Runnable ipak ne dobijamo sve metode koje se dobijaju sa proširivanjem klase sa Thread (npr. yield(), interrupt()…). Stoga ukoliko Vam trebaju te metode interfejs nije opcija, ali ako vam je dovoljna metoda run() Runnable interfejs je preporučen način stim što vam preostaje opcija da možete proširite vašu klasu sa nekom drugom klasom.

Ažuriranje UI sa sporednog Thread-a

Kod Androida radnje koje se odvijaju na drugoj niti (background thread) ne mogu da ažuriraju UI, jer UI može da ažurira samo Main Thread. Stoga potrebno je da sporedna nit na neki način komunicira sa glavnom niti i pošalje joj zadatke u MessageQueue, nakon čijeg izvršenja bi glavna nit ažurirala UI.

a) View.post() / View.postDelayed()

Ovaj pristup koristi metodu post() i na taj način obezbedjuje da Runnable bude dodat u message queue gde čeka na red da se pokrene na glavnom threadu.

Primer

Ovo je najlakši način ali nije prigodan za složenije slučajeve, jer je teško održavanje i debugovanje koda. Tako da je preporuka da se za kompleksnije interakcije koristimo ili AsyncTask i Handler.

b) Handler

Handler se koristi da olakša komunikaciju izmedju “caller Thread-a” i “worker Thread-a”. Ovaj pristup je odličan upravo kada se poveća kompleksnost kao ili kada izvodimo više ponovljajućih zadataka (npr. preuzimanje više slika koje će biti prikazane u ImageView-u…).

Primer

U ovome primeru se u novom worker Thread-u u okviru run() metode izvršava izvršava dugačka radnja
final Bitmap b = loadImageFromNetwork();
a zatim po završetku Handlerova metoda post() koja ažurira UI:

Activity.runOnUiThread()

Ovo je ustvari nadogradjena verzija Handler-a. Metoda runOnUiThread() proverava da li je trenutni Thread ustvari UiThread, ako jeste odmah izvršava odredjeni kod na UI Thread-u. Tek ako trenutni Thread nije Ui Thread, onda se ponaša kao Handler koji prosledjuje zadatak u MessageQueue. Radi lakšeg shvatanja u sledećem delu je prikazan kako bi izgledao sadržaj ove metode:

A koristi se tako što joj se prosledi Runnable objekat sa zadatkom za izvršenje.

Primer

b) AsyncTask

AsyncTask klasa takodje omogućava da se zadaci izvršavaju na sporednom Threadu a da se njihovi rezultati publikuju na main Thread-u. Za izvršavanje asinhronih zadataka je dovoljno ekstendovati našu klasu sa AsyncTask klasom i primeniti njene metode:

  • onPreExecute()
  • doInBackground()
  • onProgressUpdate()
  • onProgressUpdate()
  • onPostExecute()

Ovo je jednostavan način za koji nije potrebno znanje Thread modela, jer klasa enkapsulira kreiranje Thread-ova i korišćenje Handler-a. Problem sa AsyncTask-om je što klasa nije “svesna” životnog ciklusa (lifecycle) aktivnosti ili fragmenta, pa je na programeru da reši šta da radi sa zadacima definisam kroz AsyncTasks kada se aktivnost uništi. Stoga ovo nije baš najbolja opcija za dugačke operacije, jer ako aplikaciju uništi Android radi oslobadjanja memorije, uništiće i naš pozadinski proces.
AsyncTasks je idealan kada je CallerThread ustvari UI Thread, i akcije ne traju dugo, kao što je:

  • Prikupljanje podataka sa web servisa i njihov prikaz
  • Upit bazi podataka (database query)
  • Čitanje i pisanje na hard disk I/O
Primer

×

Anonimna klasa je klasa koja nema ime, stoga kada bi hteli da instanciramo objekat od ovakve klase to bi izgledalo ovako:

Nama je potrebna specifična anonimna klasa, ona koja implementira odredjeni interfejs, stoga iako ne moramo da definišemo ime klase moramo da definišemo koji interfejs da implementiramo. Ako je interfejs definisan u svome fajlu to se postiže ubacivanjem naziva interfejsa ispred zagrada:

Pored ovoga je potrebno da implementiramo sve abstraktne metode ovog interfejsa:


Kreiranje menija kod android aplikacija

Kreiranje sadržaja menija

Gde se nalaze meniji?

Za svaki meni je potrebno definisati stavke koje se nalaze u njemu. To se definiše u XML formatu u fajlu koje se nalazi u specifičnom folderu pod nazivom “menu” (nalazi se u sklopu “res” foldera). Ovaj fajl generišemo desnim klikom na folder “menu” u project sekciji i izborom New / “Menu resurse file” opcije.

Atributi item elementa menija

Stavke menija se definišu kroz element <item/> koji može imati definisane mnoge atribute, najčešće korišćeni atributi su sledeći:

  • id (android:id=”@+id/delete”)
  • title (android:title=”@string/delete”)
  • icon (android:icon=”@drawable/ic_delete”)
  • enabled (android:enabled=”false”)
  • checkable (android:checkable=”true”)
  • showAsAction (Ovi atributi su vezani za prikazivanje elemenata “options menija”u okviru action bar-a )
    • ifRoom (app:showAsAction=”ifRoom”)
    • withText (app:showAsAction=”withText”)
    • never (app:showAsAction=”never”)
    • always (app:showAsAction=”always”)
Primer

Grupe

Moguće je okupiti slične članove menija u grupe:

Podmeni

Pravljenje podmenija je jednostavno, u sklopu “item” elementa treba ugraditi novi “menu” element:

NAPOMENA:
Kod elemenata kod kojih se koristi atribut android:checkable=”true” je nakon klika na ovakav član menija potrebno da podesite stanje u polju za potvrdu, jer checkBox (ili radio dugme) ne menja automatski svoje stanje. Nakon klika moramo da upitamo kakvo je stanje stavke bile pre ovog klika (da li je bilo čekirano) koristeći isChecked().
Ako je prethodno bilo čekirano onda sada nakon klika više nije i moramo da stavimo stanje setChecked(false) i obrnuto ako nije bilo čekirano item.isChecked()=false sada posle klika jeste i moramo da stavimo item.setChecked(true);

Plivajući menu (floating context menu)

Kreiranje sadržaja menija

Prvo je potrebno definisati sadržaj menija u XML formatu.

Primer (neki_menu.xml)

U ovome primeru prvi element menija je tzv. checkbox.

Integrisanje menija u aktivnost

Nakon definisanja sadržaja je potrebno da se omogući da taj meni bude dostupan u aktivnosti.

Primer

Registrovanje dugmeta koje otvara meni

U sklopu aktivnosti je potrebno registrovati dugme (u onCreate() metodi) koje će biti povezano za taj meni:

A zatim zakačiti clickListener za njega koji će na klik da pozove metodu openContextMenu(view):

Definisanje šta će da se dogodi klikom na člana menija

U metodi onContextItemSelected() definišemo šta će da se desi klikom na neki od članova menija. Za selektovnanje člana menija se koristi “id” itema definisanog u XML-u (neki_menu.xml):

Options meni

options menu

Za options meni nije potrebno da se definiše dugme koje će da reaguje na klik i otvori ovaj meni, zato što ovo dugme android sam ubacuje u action bar i dodeljuje mu ikonicu sa tri vertikalne tačke.

Treba naglasiti da ovde imamo priliku definišemo koji će član menija u biti odmah prikazan u actionBar-u pored options ikone (tri vertikalne tačke). Ovo se obezbedjuje sa atributom app:showAsAction="ifRoom". Options meni se prikazuje baš na mestu kliknutog dugmeta. Ukoliko se neki član prikazuje odmah u action baru on se onda ne nalazi u samom meniju.

Kreiranje sadržaja menija

Pored ovih specifičnosti kao i kod svih drugih menija i ovde je neophodno da se definiše sadržaj menija u XML formatu.


open options menu

Integrisanje menija u aktivnost

Na sličan način kao kod Context Float menija se i ovde integriše meni:

Definisanje šta će da se dogodi klikom na člana menija

U metodi onOptionsItemSelected definišemo šta će da se desiti nakon klika na neki od članova menija:

PopUp meni

Ovaj meni je specifičan jer je vezan za mesto gde je kliknuto. Meni se prikazuje tamo gde ima prostora ili ispod kliknutog mesta, ili iznad kliknutog mesta. PopUp meni se sakriva sa ekrana kada korisnik klikne na neki član menija ili negde sa strane van menija.

popup

Definisanje šta će da se dogodi klikom na člana menija

Da bi klasa (npr. aktivnost) mogla da izvrši akcije nakon klika potrebno je da klasa implementira interfejs PopupMenu.OnMenuItemClickListener.
Nakon imeplementiranja interfejsa je potrebno da se implementira (Override) i njegova abstraktna metoda onMenuItemClick(), kroz koju nam je omogućeno da definišemo za svaki član menija koja će akcija biti preduzeta nakon klika na član.

Integrisanje menija u aktivnost

Da bi prikazali PopUp meni potrebno je da na okidaču (npr. neko dugme) definišemo onClick metodu (npr. shoPopup()) koja izgleda ovako:

Contextual Action Mode meni

Ovaj specifični meni se prikazuje u sklopu actionBar-a, tačnije preko actionBar-a se. Pored članova menija prikazanih ikonama u desnom kraju, nalazi se i naslov menija u sredini, i strelica za zatvaranje menija na levoj strani.

pre
pre

Kreiranje sadržaja menija

Kao i za svaki meni prvo je potrebno napraviti sadržaj menija u xml formatu (New “Menu resurse file”) u folderu “menu”

Primer (context_action_menu.xml)

Definisanje ActionModeCallback metode

Pre svega u sklopu aktivnosti je potrebno definisati promenjivu tipa ActionMode (napomena izabrari verziju v.7) jer se ona koristi u okviru callback metode:

A zatim i ActionModeCallback instancu u kojoj se vrši sav posao u vezi Contextual Action Mode menijem. Ima 4 metode, u prvoj onCreateActionMode() se povezuje predhodno definisan sadržaj menija sa Action Mode menijem. U metodi onActionItemClicked() se definišu akcije nakon klika na neki od članova. A ono šta se dešava kada se meni zatvori se definiše u metodi onDestroyActionMode().

Registrovanje dugmeta koje otvara meni

U sklopu aktivnosti je potrebno na odredjenom dugmetu definisati clickListener (u onCreate() metodi) sa kojim ćemo omogućiti da se na klik pozove prethodno definisana callback metoda mActionModeCallback():

Pa bi ceo kod za listener ovako izgledao:



Aktivnost u okviru android operativnog sistema

Šta je android aktivnost?

Android aplikacije su kolekcija zadataka, gde svaki zadatak ima svoju funkcionalnost. Te funkcionalnosti mogu se raspodeliti na četiri Androidove komponente:

Funkcionalnost Android klasa Primer
Interakcija sa korisnikom Activity Unošenje beleške, igranje računarske igre
Pozadinski proces koji dugo traje čak i kada korisnik nije direktno u interakciji sa aplikacijom. Servis se izvršva sve dok se sam ne isključi ili ga neko drugi ekspicitno ne stopira. Service Reprodukovanje muzike, ažuriranje izgleda ikonice za vremensku prognozu
Primanje poruka BroadcastReceiver Aktiviranje alarma pri određenom događaju
Unošenje i učitavanje podataka ContentProvider Otvaranje novog telefonskog kontakta

mozak aktivnosti

Aktivnost kao jedan od načina sa kojom android omogućava funkcionalnost aplikacije, ima za cilj da omogući interakciju sa korisnikom. Iz tog razloga svaka aktivnost ima korisnički interfejs. Za interaktivnost sa korisnikom aktivnost obezbedjuje prozor u kome se smešta korisnički interfejs. Ovaj prozor obično popunjava ceo ekran uredjaja, ali može biti manji ili da pliva preko drugih prozora. Najčešće jedna aktivnost implementira jedan ekran.

Da bi naša klasa predstavljala aktivnost u aplikaciji ona mora da ekstenduje klasu “Activity”. Najčešće se koristi klasa “AppCompatActivity” koja je naslednik klase Activity i pokriva nove funkcionalnosti kao što je actionBar… Ova klasa dolazi sa appcompat-v7 biblotekom.

Sama klasa “Activity” (AppCompatActivity) je podklasa klase Context, što znači da sve aktivnosti imaju pristup globalnim informacijama o okruženju aplikacije.
Ako aplikacija ima više aktivnosti, uvek je jedna aktivnost definisana kao glavna aktivnost (eng. MainActivity). Ova glavna aktivnost predstavlja prvi ekran koji se pojavljuje kada korisnik pokrene aplikaciju, nakon toga iz glavne aktivnost se može pokrenuti i druga aktivnost. Na ovaj način, aktivnost služi kao ulazna tačka za interakciju aplikacije sa korisnikom. Ta aktivnost može da pokrene drugu aktivnost, tada glavna aktivnost prelazi u stanje pauze dok je aktivna sekundarna aktivnost. Kada se završi sekundarna aktivnost, glavna aktivnost se vraća u prvi plan i nastavlja.

Osnovne metode životnog ciklusa

Od trenutka kada se aktivnost prikaže na ekranu do trenutka kada se sakrije, aktivnost prolazi veći broj faza u njenom tzv. “Životnom ciklusu” (eng.lifecycyle).

osnovni lifecycle

onCreate()

Ovo je prva metoda koja se pokreće po pokretanju aplikacije. Aplikacija pre izvršenja ove metode nije još nije vidljiva a tek postaje vidljiva kada se metoda onCreate() izvrši.

NAPOMENA:
U telu ove metode NIKADA ne pravite kod koji se predugo izvršava (npr. beskonačnu petlju) jer se Ui aplikacije prikazuje tek po zavšetku ove metode.

Metod onCreate() prihvata parametar savedInstanceState tipa Bundle. Bundle objekat omogućava da u jedan objekat prikupimo različite tipove podataka u vidu key/value. Ako se aktivnost pravi “od nule” onda je ovaj parametar null, medjutim ukoliko se ponovno stvara (nakon onDestroy()), onda taj parametar ima vrednost Bundle objekta upotrebljenog u metodi onSaveInstanceState().

U ovoj metodi se izvršava sve ono što je bitno pre prikazivanja aplikacije, kao što je npr. definisanje eventListener-a ili povezivanje aktivnosti sa odgovarajućim layout-om koji je definisan u XML-u:

Primer

onStart()

Po završetku metode onCreate(), UI aplikacije postaje vidljiv i moguća je interakcija aplikacije sa korisnikom pa se pokreće ova metoda. Ukoliko je aplikacija već bila pokrenuta pa je bila zaustavljena, ova metoda se pokreće i po završetku onRestart() metode.

onPause()

Ova metoda se poziva kada se aplikacija pauzira ali i pre nego što se aplikacija u potpunosti zaustavi. Ako se aplikacija samo pauzira nakon ove metode se poziva metoda onResume(), dok u slučaju zaustavljanja rada aplikacije po završetku ove metode se poziva metoda onStop().

onResume()

Pri prvom pokretanju aplikacije ova metoda se pokreće posle metode onStart(), a kasnije se poziva po završetku metode onPause() svaki put kada se aktivnost nastavlja.

onStop()

Ova metoda se pokreće nakon što aktivnost postaje nevidljiva. Razlog može da bude akcija koju izvrši korisnik/android tako što iz nekog razoga potisnu našu iz prvog plana, najčešće zbog neke druge aplikacija (npr. primamo poziv dok radi naša aplikacija). Aplikacija se u tom trenutku ne vidi ali i se i dalje izvršava u pozdini.

onRestart()

Ova metoda se poziva pri restartovanju aplikacije pre nego što aktivnost postane vidljiva. Ova metoda se poziva posle metode onStop(), i može se zaključiti da se ova metoda poziva svaki put kada i metoda onStart(), osim pri prvom pokretanju aplikacije.
Treba naglasiti da se ova metoda NE IZVRŠAVA kada aktivnost prvi put pokreće, kao i kada se rotira ekran (aktivnost uništava i ponovo pokreće).

onDestroy()

Ova metoda se poziva kada sistem odluči da uništi aplikaciju. Prethodi joj metoda onStop().

NAPOMENA:
U saradnji sa nabrojanim lifecycle metodama se veoma često koriste još dve metode:

  • onSaveInstanceState(Bundle outState)
  • onRestoreInstanceState(Bundle savedInstanceState)

Ove dve metode se koriste za čuvanje i ponovno iskorišćavanje podataka nakon prekida rada aplikacije. Kada se aplikacija zaustavlja android pokreće metodu onSaveInstanceState() pre metode onStop(). Na ovaj način andriod omogućava da se kroz nju sačuva state aplikacije.
Treba naglasiti da android ne poziva metodu onSaveInstanceState () kada korisnik eksplicitno zatvori aktivnost ili kada aplikacija pozove metodu finish().

Metoda onRestoreInstanceState() se koristi da povrati sačuvane podatke, pa se izvršava posle metode onStart() a pre metode onResume().

Metodi onCreate() se prosedjuje isti Bundle objekat pa se i ona može koristiti za povratak podataka (koristi se u ovu namenu kada je aplikacija uništena i ponovo se kreira).

Primena metoda životnog ciklusa aktivnosti

Kada aplikacija izgubi fokus ili postane nevidljiva, korisnik ne može da vrši interakciju sa aplikacijom, ali se aplikacija i dalje izvršava u pozadini, stoga je potrebno je napisati kod sa kojim bi trebali da zaustavimo sve operacije koje troše CPU, zaustavimo sve audio ili video reprodukcije kao i da sačuvamo trenutne vrednosti nekih podataka (npr. gde je stala reprodukcija videa, vrednost nekog proračuna…). Ako je igra u pitanju savet je da odmah po gubitku fokusa automatski zaustavimo igru, jer je korisnik izgubio mogućnost da kontroliše igru. Takođe, po povratku fokusa nikada ne bismo trebali da automatski da pokrenemo igru, jer bi korisnik mogao biti nepripremljen (npr. vožnja trke u trkačkoj igri), preporuka je da se pokaže ekran za pauzu i na taj način dozvolimo korisniku da sam izbere kada će da nastavi igru. Većina ove potrebne funkcionalnosti se piše u pomenutim metodama.
Slučajevi koji se mogu desiti u životu aplikacije i za koje moramo biti pripremljeni, su:

a) Gubitak i povratak fokusa

Gubitak fokusa (aplikacija je vidljiva!) se najčešće dešava kada se aktivira neki sistemski dijalog. Pre gubitka fokusa se poziva metoda onPause(). Ukoliko aplikacija povrati fokus izvršiće se metoda onResume().

b) Korisnik pauzira aplikaciju a zatim se vraća na nju

Aktivnost može biti pauzirana na više načina:

  • kada korisnik pritisne Home dugme (aplikacija će se restartovati ako korisnik ponovo izabere pauziranu aplikaciju u meniju (klikom na dugme pravougaonik) gde su sve privremeno zaustavljene aplikacije)
  • kada se isključi ekran (aplikacija će se restartovati kada korisnik uključi ekran)

U oba slučaja se aktivnost zaustavlja, ali se ne uništava. Metode koje su se do tada izvršile su: onPause(), onSaveInstanceState(), i onStop(). Nakon restarta se izvršava metoda onRestart() a zatim odmah i metoda onStart(), onRestoreInstanceState() i na kraju metoda onResume()

c) Operativni sistem uništava i ponovno pokreće aplikaciju

Najbolji primer za ovaj slučaj je rotiranje uredjaja. Android rotaciju posmatra kao promenu konfiguracije i sprema se da uništi aktivnost, a zatim da je ponovo startuje. Pre uništenja se poziva onStateInstanceState() koji je odličan za privremeno čuvanje podataka, da bi se kasnije iskoristili pri ponovnom pokretanju aktivnosti uz metodu onCreate() koja takodje ima Bundle objekat.

d) Operativni sistem uništava aplikaciju da oslobodi resurse

Ovo se dešava ako se aplikacija ne koristi dugo vremena (pauzirana je), pa je sistem nateran da je uništi kako bi oslobodio resurse zatvaranjem keširanih procesa. Po izvršenjeu metode onStop() aplikacija postaje nevidljiva, a pre samog uništenja android prvo poziva metodu onSavaInstanceState(). Uništenje aplikacije se dešava nakon metode onDestroy().

OBJAŠNJENJE:
Android “ubija” procese kada mu treba memorija prema odredjenom redosledu koji zavisi od važnosti procesa (“importance hierarchy”):

  1. keširan process
  2. servis
  3. vidljiv proces ali bez fokusa (visible process)
  4. aktivan proces (foreground process)

Treba naglasiti da u slučaju ponovnog pokretanje nove instance aplikacije, ovoj aplikaciji su dostupni svi sačuvani podaci kroz metodu onSaveInstanceState() pa kao i u prethodnom primeru možemo ih pokupiti iz Bundle objekta u metodi onCreate().

e) Korisnik uništi aplikaciju ili se aplikacija sama završi

Ovaj slučaj se može desiti:

  • kada korisnik stisne sistemsko dugme back
  • kada aktivnost pokreće sopstveno uništenje pozivom metode finish()

Postupak započinje sa metodama: onPause(), zatim onStop() kada aplikacija postaje nevidljiva, a zatim i metoda onDestroy(). Treba naglasiti da se u oba slučaja podaci ne čuvaju (ne poziva se metoda onSaveInstanceState()), te se aplikacija pri ponovnom pokretanju aplikacija pokreće od “nule” sa defaultnim podacima.

NAPOMENA:
Kada korisnik pređe sa jedne na drugu aktivnost pa se vrati na početnu, može doći do pokretanja više instanci iste aktivnosti na uređaju. Da bi se to izbeglo, programer treba da definiše ponašanje aktivnosti. Ovo se izvšava u okviru fajla AndroidManifest.xml. Da bi se na uređaju pokretala uvek samo jedna instanca aktivnosti, potrebno je u activity elementu activity koji sadrži (MAIN i LAUNCHER):

dodati i:

da krajnji izgleda ovako:


Razvoj mobilnih aplikacija

Uvod

Mobilna aplikacija je program koji je dizajniran da radi na mobilnom uređaju kao što je mobilni telefon, tablet ili wearable (pametni sat…). Te aplikacije imaju pristup senzorima i specifičnim funkcionalnostima uredjaja (kamera, žiroskop, vibracija i dr.). Razvijanje aplikacija za mobilne uređaje zahteva razmatranje karakteristika ovih uređaja i njihovih ograničenja (rade na bateriji i imaju slabije procesore i manje RAM memorije nego desktop računari). Programeri takođe moraju imati u vidu širok spektar: dimenzija ekrana, hardverske specifikacije i konfiguracije različitih uredjaja. Na većini mobilnih uredjaja se nalazi jedan od sledeća dva operativna sistema: Android i IOS.

mobile apps

Različiti pristupi izradi mobilnih aplikacija

Mobilne aplikacije se ponekad kategorizuju u zavisnosti od toga koji je pristup korišćen pri izradi aplikacije, pa stoga možemo razvoj mobilnih aplikacija podeliti na sledeće grupe:

  • Izvorne (native) mobilne aplikacije:
    • iOS app development
    • Android app development
  • Kvazi-izvorne mobilne aplikacije:
    • React Native
    • NativeScript
    • Xamarin
  • Hybridne mobilne aplikacije
    • Ionic + Android.js
    • Quasar + Vue.js
    • Framework7 + React.js
  • Responsivne web aplikacije

Izvorne (native) mobilne aplikacije

android & iOS

Izvorna aplikacija je softverski program koji je razvijen za upotrebu na određenoj platformi ili uređaju. Budući da je izvorna aplikacija izgrađena samo za upotrebu na određenom uređaju i njegovom operativnom sistemu, ona ima mogućnost da maksimalno iskoristi hardver i softver specifičan za uređaje tako da mogu pružiti najbolje performanse.
Native aplikacije moraju biti napisane u odgovarajućem programskom jeziku za svaki operativni sistem, stoga se za iOS aplikacije koristi Objective-C ili Swift, dok se nativne Android aplikacije pišu u Javi ili Kotlin-u.

Da bi ste razvili “nativne mobilne aplikacije” na više različitih platformi, potrebno je da u timu imate softverske inženjere za svaki operativni sistem. Pored velike (čitaj “skupe”) zavisnosti od specifičnog kadra, treba naglasiti da je otežavajuća okolnost što ne možemo da iskoristimo i podelimo programski kod između različitih verzija iste aplikacije. Zaključak je da nativne aplikacije imaju superiorne perfomanse ali i veliku cenu.

Kvazi izvorne mobilne aplikacije

nativescript

Za izradu kvazi izvornih mobilnih aplikacija se koriste frejmvorci (ReactNative i NativeScript) koji se baziraju na web konceptima pri razvoju mobilnih aplikacija. ReactNative i NativeScript koriste JavaScript, dok Xamarin koristi C# pri razvoju ovih aplikacija. Tako pisan kod se naknadno kompajlira u izvorni programski jezik (u zavisnosti od platforme), a rezultat je nešto što funkcioniše kao prava izvorna aplikacija.

reactnative

Ovaj način izrade mobilnih aplikacija nam omogućava da istovremeno (sa istim kodom) kreiramo aplikacije za iOS i Android. Koristeći ovaj pristup pravimo veliku uštedom u vremenu i troškovima proizvodnje uz minimalan gubitak perfomansi. Trenutno ReactNative, NativeScript i Xamarin, kažu da kroz svoje API-je imaju 100% podršku izvorne funkcionalnosti. Xamarin je u mogućnosti da pravi iOS, Android, Windows, MacOS i Linux, dok ReactNative i NativeScript su ograničeni samo na iOS i Android.

Poznato je da aplikacije pravljenje za istu namenu ipak na iOS i Android platformama izgledaju malo drugačije, jer svaka platforma prati sopstvene smernice. Ova činjenica je najveći problem kvazi izvornih aplikacija pošto se kod piše jednom a koristi za obe platforme. Postoji slučaj kada se elementi grafički podudaraju sa smernicama specifične platforme, ali se njihov položaj na ekranu ne podudara tj. položaj na ekranu je različit na različitim platformama.

Hibridne aplikacije

hibridne mobilne aplikacije

Radi se o web aplikacijama koje koriste klasične web tehnologije (HTML, JavaScript, i CSS) i “Cordova” kao wrapper koji im daje mogućnost za komunikaciju sa sistemskim komponentama i kompajlira ih u aplikacije dostupne kroz tzv. webView (ogoljeni browser).
Slično kao i kod kvazi izvornih aplikacija i ovde programski kod pišemo samo jednom, a ostale verzije (za svaku od željenih platformi) se generišu iz tog koda. Ovakve aplikacije se zovu “hibridne”, jer ipak nisu čisto nativne mobilne aplikacije, (sve prikazivanje vrši preko tzv. “WebView” umesto platforme korisničkog interfejsa), ali takodje nisu ni čisto Web-based (jer imaju pristup izvornim API-ima uređaja i pripremljene su za distribuciju na odgovarajućoj platformi).

Problem hibridnih aplikacija je što nemaju pristup svim sistemskim funkcijama a imaju slabije performanse od nativnih aplikacija. Ovaj problem je izražen kod starijih telefona (pogotovo na Android platformi pre verzije 4.0) kod kojih se sporo renderovuje korisnički interfejs a pri izvršavanju animaceije dolazi do seckanje. Iako je trenutno situacija bolja, uglavnom zbog značajne procesorske snage aktuelnih aparata, a delom zbog alata koje se koriste za razvoj i napretka u optimizaciji mobilnih operativnih sistema. Ne treba zanemariti da gradeći hibridne aplikacije mi automatski nasledjujemo sve probleme koje dolaze sa programiranjem na web-u (različita podržanost funkcionalnosti za različite browsere…). Više o ovome pročitajte u članku “Uvod u hibridne mobilne aplikacije”

Responsive web aplikacije

javascript

Web aplikacije se takodje mogu tretirti kao mobilne jer se izvršavaju na mobilnom uredjaju ali imaju najlošije perfomase i nemaju mnoge funkcionalnosti koje imaju aplikacije kreirane na jednom od prethodno pomenutih načina. Web aplikacije pružaju tzv. “application-like” iskustvo pri čemu se ne instaliraju lokalno već su dostupne izvršavajući aplikaciju u okviru browser-a na mobilnom uredjaju.

Progressive Web App

“Progressive Web App” se mogu smatrati naprednijom verzijom web aplikacija, jer koriste service workers koji rade u pozadini aplikacije i deluju kao posrednici između internet mreže i aplikacije, pa su u mogućnosti da presretnu mrežne zahteve u pozadini. Progresivne web aplikacije (PWA) se mogu izvršavati kada je uredjaj van mreže (koristeći service workers), i imaju izvesni hardverski pristup uređaju (npr. obaveštenja na uradjaju) koji su tradicionalno dostupni samo izvornim mobilnim aplikacijama. PWA su uvek “up-to-date” zahvaljujući “service worker update process”. PWA omogućavaju korisnicima da “sačuvaju” aplikacije koje smatraju najkorisnijim na svom početnom ekranu kreirajući prečicu bez korišćenja neke od prodavnica aplikacija (GooglePlay, App Store).