Vjezba5-T Odt PDF

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 7

Servisi

U prošlim vježbama smo vidjeli na koji način je moguće koristiti resurse sa Interneta u
Android aplikaciji i kako akciju dobavljanja podataka prebaciti u posebnu nit kako ne bi
blokirala glavnu nit zaduženu za ažuriranje korisničkog interfejsa.
U ovim vježbama vidjet ćemo kako popraviti propust koji imamo ukoliko koristimo
AsyncTask za odvajanje akcije u novu nit. Mana AsyncTask-a je da ukoliko aktivnost koja
sadrži navedeni AsyncTask prestane biti vidljiva ili ako se uređaj rotira taj AsyncTask se
ponovo pokreće. Odnosno ako aktivnost nije vidljiva AsyncTask ne zna gdje da vrati
razultat jer se metoda onPostExecute() izvršava u glavnoj niti aktivnosti. U nastavku teksta
biće opisano jedno rješenje, a prije toga ponovit ćemo značenje termina Servis u okviru
android aplikacije.
Servisi su komponente koje omogućavaju izvršavanje dugih zadataka i oni ostaju aktivni i
u slučajevima kada njihove aplikacije nisu vidljive. Servisi imaju veći prioritet od aktivnosti i
ukoliko se desi da Android nema dovoljno resursa servis će imati manje šanse da bude
ugašen od aktivnosti. Aplikacije koje imaju neki posao koji se izvršava u pozadini imaju
veći prioritet od aplikacija koje to nemaju.
Servisi koriste glavnu nit pa zbog toga zadatke koji su vremenski zahtjevni treba prebaciti
u posebni thread. Ukoliko stavimo AsyncTask unutar servisa osigurali smo se od
restartovanja AsyncTaska jer se servis neće prekidati čak i kada je aplikacijia
minimizovana.
AsyncTask-ove koji imaju dugotrajno izvršavanje treba stavljati u Android servise koji će
se izvršavati bez prekida i kada se aplikacija minimizuje ili ako se uređaj rotira.
Razlika između servisa i aktivnosti je ta što servisi nemaju svoj odgovarajući korisnički
interfejs. Aktivnosti su dizajnirane da se pokreću, restartuju i zatvaraju tokom svog
životnog ciklusa, dok su servisi dizajnirani da duže traju. Servisi zavise od aktivnosti,
broadcast receivera i drugih servisa. Oni kontrolišu njihovo kreiranje, kontrolisanje i
zaustavljanje.
Pregled mehanizama za izvršavanje zadataka:
Service Thread IntentService AsyncTask
Kada koristiti? Zadatak bez Za dugotrajne Dugotrajni zadaci, Za zadatke koji bi
korisničkog zadatke. Za periodični zadaci. inače blokirali glavni
interfejsa. Ne bi paralelno thread.
trebao biti dug jer izvršavanje
se izvršava na zadataka.
glavnom threadu.
Pokretanje onStartService() start() Putem Intenta execute()
Pozvan iz ... Bilo kojeg threada Bilo kojeg threada Glavne niti Glavne niti
Izvršava se na ... Glavnom threadu U zasebnom U zasebnom U zasebnom
threadu threadu threadu
Ograničenja Blokira glavni Ručno upravljanje Pojedinačne Jedna instanca se
thread izvršavanjem zadatke ne izvršava može pozvati samo
threada, kod paralelno, višestruki jednom.
postaje kompleksan pozivi se dodaju na Vezan za životni
vrlo brzo. message queue. ciklus glavne niti.
Kreiranje servisa
Da bi kreirali servis potrebno je implementirati klasu koja je nasljeđena iz Service klase.
Nakon kreiranja klase obavezni smo implementirati dvije metode ​onCreate​ i ​onBind.
Kako je servis komponenta aplikacije potrebno ju je registrovati u manifest fajlu. Za
registrovanje servisa koristimo ​<service>​ tag, njega smještamo unutar ​<application>​ tag-a.
Unutar ​<service>​ taga potrebno je navesti dva atributa ​android:enabled="true" ​i naziv sa
atributom ​android:name​.
<service android:enabled=”true” android:name=”.MyService”/>
Ukoliko želimo da osiguramo naš servis, da druge aplikacije ne mogu pristupati njemu bez
dobijanja odgovarajući dozvola koristimo još jedan atribut unutar ​<service> ​tag-a. Atribut
android:permission​ postavljamo na vrijednost naziva permisije koju zahtjevate da druga
aplikacija ima kako bi koristila servis vaše aplikacije.

Pokretanje servisa
Da bi mogli pokrenuti servis potrebno je dodati još jednu metodu u klasu serisa. Metoda
onStartCommand priprema servis za početak njegovog izvršavanja. Kako se sve što se
nalazi u onStartCommand metodi pokreće u glavnoj niti praksa je da se na početku
onStartCommand metode pokrene nova nit, a da se prilikom zatvaranja servisa zatvori i
ova nit. Na ovaj način smo osigurali da servis ne blokira glavnu nit aplikacije.

@Override
public​ ​int​ ​onStartCommand​(​Intent intent​,​ ​int​ flags​,​ ​int​ startId​)​ ​{
startBackgroundTask​(​intent​,​ startId​);
​return​ Service​.​START_STICKY​;
}

Ukoliko pogledate jednu tipičnu implementaciju metode onStartCommand vidjet ćete da ta


metoda vraća cjelobrojnu vrijednost, ta cjelobrojna vrijednost govori o tome kako će se
servis ponašati u slučaju da ga se restartuje.
Lista načina kako je moguće upravljati sa restartovanjem servisa:
START_STICKY​ – metoda onStartCommand će se pokretati svaki put kada se
servis restartuje. Tada će vrijednost ​intent​ parametra biti ​null​.
START_NO_STICKY –​ servisi specificirani na ovaj način se restartuju samo u
slučajevima ako postoji neki aktuelan zahtjev za njihovim startanjem. Ukoliko niti
jednom nije pozvana metoda ​serviceStart ​nakon što je servis ugašen tada se neće
pozivati onStartCommand metoda. Ovakvi servisi se koriste za akcije koje
update-uju aplikaciju u određenim vremenskim intervalima i u slučajevima ako
aplikacija nije update-ovana u jednom intervalu biće update-ovana u sljedećem
kada se servis pozove.
START_REDELIVER_INTENT​ – ukoliko želimo da budemo sigurni da su komande
u servisu izvršene koristimo ovaj režim. Ovakva specifikacija servisa predstavlja
kombinaciju prethodne dvije. Ukoliko je servis zaustavljen servis će se restartovati
samo ako postoji aktuelan zahtjev za startanjem ili ako je servis zaustavljen prije
pozivanja ​stopSelf​. U drugom slučaju pozvat će se ​onStartCommand​ metoda i njoj
će biti prosljeđen orginalni intent čije procesiranje nije završeno.
Pokretanje i zaustavljanje servisa
Pokretanje servisa radimo poput startanja intenta. Postoje dva načina implicitni i
eksplicitni.
U slučaju eksplicitnog pokretanja servisa kreiramo eksplicitni intent i njemu prosljeđujemo
klasu servisa kao drugi parametar, nakon čega pozivamo ​startService​ metodu i njoj
prosljeđujemo novokreirani intent.
private​ ​void​ ​explicitStart​()​ ​{
Intent intent ​=​ ​new​ Intent​(​this​,​ MyService​.​class​);
startService​(​intent​);
}

Implicitno kreiranje servisa je veoma slično implicitnom kreiranju intenta. Potrebno je


kreirati implicitni intent i kao akciju poslati akciju specificiranu u servisu. Nakon čega je
potrebno pozvati ​startService​ metodu. U oba slučaja moguće je dodati dodatne podatke u
intent koji će biti prosljeđeni servisu putem ​putExtra​ metode.

private​ ​void​ ​implicitStart​()​ ​{


Intent intent ​=​ ​new​ Intent​(​MyMusicService​.​PUSTI_PJESMU​);
intent​.​putExtra​(​MyMusicService​.​NAZIV_EXTRA​,​ ​“​Naziv pjesme​”​);
startService​(​intent​);
}

Način zaustavljanja servisa zavisi od toga kako ste ga pokrenuli. Ako se radi o
eksplicitnom pokretanju potrebno je samo pozvati metodu ​stopService ​i kao parametar
prosljediti novi (anonimni) intent koji ima klasu servisa kao parametar.

stopService​(​new​ Intent​(​this​,​ MyService​.​class​));

U slučaju servisa koji je pokrenut implicitno potrebno je kreirati novi impcitni intent u kojem
je navedena akcija koja je bila i prilokom pokretanja servisa, nakon čega je potrebno
pozvati ​stopService​ i prosljediti novokreirani intent.

Intent intent ​=​ ​new​ Intent​(​MyMusicService​.​PLAY_ALBUM​);


stopService​(​intent​);

Jedno pozivanje ​stopService​ metode će zatvoriti odgovarajući servis bez obzira koliko
puta je pozvan startService nad tim servisom.

Intent servisi
Android posjeduje klasu koja implementira najbolje prakse za izvršavanje servisa u
pozadini koji se pozivaju povremeno na zahtjev aplikacije, poput update-a sa interneta i sl.
Klasa koja implementira opisane funkcionalnosti je IntentService. IntentService reda
zahtjeve za servisom u redu kako su došli i izvršava ih sekvencijalno, jedne za drugim.
Izvršavanje pojedinog zadatka je asinhrono, u odvojenom threadu od glavnog thread-a.
IntentService interno koristi HandlerThread. HandlerThread je baziran na dvije
komponente Looper i MessageQueue. MessageQueue je red poruka koje dolaze na
izvršavanje u niti. Poruku ovdje možete smatrati kao posao koji nit treba da obavi. Pošto
jedna nit može samo da radi jedan zadatak u jednom trenutku ostali zadaci se dodaju u
red. Looper prolazi kroz red poruka i osigurava da će nit dobiti sljedeći posao. Svaki
sljedeći zadatak se dodaje u red, a kada nit bude slobodna looper će ga isporučiti nit.
IntentService zaprima zahtjeve putem intentova koji sadrže sve potrebne parametre za
izvršavanje jednog zahtjeva i vrši raspoređivanje njihovog izvršavanja umjesto vas.
Pored raspoređivanja reda izvršavanja zahtjeva IntentService vrši kreiranje niti koja će se
izvršavati u pozadini i vrši sinhronizaciju sa UI niti.
Da bi se implementirao IntentService potrebno je kreirati klasu koja je izvedena iz klase
IntentService i koja sadrži implementiranu metodu ​onHandleIntent​. Metoda ​onHandleIntent
treba da sadrži posao koji je vremenski zahtjevan, odnosno većinu procesiranja koje servis
radi. Ova metoda će se izvršavati u posebnoj niti, a sav posao oko kreiranja,
sinhronizovanja i uništavanje te niti obavlja IntentService umjesto vas.
Potrebno je implementirati i metodu ​onCreate​ pomoću koje postavljate početne uslove za
izvršavanje servisa, kao i konstruktor koji prima parametar tipa string.
​ lass​ ​MyIntentService​ ​extends​ IntentService ​{
public​ c
public​ M​ yIntentService​()​ ​{
​super​(​null​);
}
public​ ​MyIntentService​(​String name​)​ ​{
​super​(​name​);
​// Sav posao koji treba da obavi konstruktor treba da se
​// nalazi ovdje
}
@Override
public​ ​void​ ​onCreate​()​ ​{
super​.​onCreate​();
​// Akcije koje se trebaju obaviti pri kreiranju servisa
}
@Override
protected​ ​void​ ​onHandleIntent​(​Intent intent​)​ ​{
​// Kod koji se nalazi ovdje će se izvršavati u posebnoj niti
​// Ovdje treba da se nalazi funkcionalnost servisa koja je
​// vremenski zahtjevna
}
}

Napomena:​ Potrebno je definisati IntentService u manifest fajlu na isti način kako je


opisano i za obične servise korištenjem service taga.
Pozivanje IntentService-a
Pozivanje IntentService-a radimo preko intent mehanizma. Prvo je potrebno kreirati
eksplicitni intent koji će pozvati naš IntentService kojeg smo definisali u manifestu i čiju
klasu smo ranije implemenitrali:

Intent intent ​=​ ​new​ Intent​(​Intent​.​ACTION_SYNC​,​ ​null​,​ ​this​,​ MyIntentService​.​class​);

ACTION_SYNC – specificira da će biti korištena akcija za sinhronizaciju podataka, možete napraviti i


vlastitu akciju i osluškivati je preko BroadcastRecievera i IntentFiltera
Drugi parametar je data uri kojeg nećemo specificirati, zbog toga je ovdje null
Treći parametar je Context, tj. Referenca na aktivnost. ​Ako pozivate iz fragmenta ovdje trebate
staviti ​getActivity().
Četvrti parametar je klasa koju pozivamo kroz intent

Nakon što smo kreirali intent potrebno je specificirati način kako da vratimo podatke iz
intenta. Jedan od mnogobrojnih načina je da koristimo klasu ​ResultReceiver​. Ova klasa je
ustvari omotač oko klase Binder. Binder je mehanizam za komunikaciju između procesa.
Implementaciju navedene klase ćemo opisati poslije.

mReceiver ​=​ ​new​ MojResultReceiver​(​new​ Handler​());


mReceiver​.​setReceiver​(​this​);
// U slučaju kada pozivate intent iz fragmenta ovaj this se odnosi na MojFragment.this

Kao i za sve intentove koje smo dosada pozivali potrebno je proslijediti neke dodatne
podatke putem putExtra metode.

intent​.​putExtra​(​"parametar1"​,​ vrijednost​);
intent​.​putExtra​(​"parametar2"​,​ vrijednost​);
intent​.​putExtra​(​"receiver"​,​ mReceiver​);

Kada smo postavili sve vrijednosti potrebne za pozivanje intenta potrebno je da pozovemo
navedeni intent:
startService​(​intent​);
// Ukoliko startate iz fragmenta pozivajte kao getActivity().startService(intent);

Obrađivanje intenta u IntentService-u


U ​onHandleIntent​ metodi potrebno je primiti intent i pokupiti sve proslijeđene podatke:

final​ ResultReceiver receiver ​=​ intent​.​getParcelableExtra​(​"receiver"​);


Bundle bundle ​=​ ​new​ Bundle​();
Kada započnemo obradu intenta možemo poslati obavjest pozivatelju:

/* Update UI: Početak taska */


receiver​.​send​(​STATUS_RUNNING​,​ Bundle​.​EMPTY​);

Kada završimo sa obradom:

/* Proslijedi rezultate nazad u pozivatelja */


bundle​.​putStringArray​(​"result"​,​ results​);
receiver​.​send​(​STATUS_FINISHED​,​ bundle​);

Ukoliko je došlo do greške:

/* Vrati obavijest da je došlo do izuzetka, e – izuzetak */


bundle​.​putString​(​Intent​.​EXTRA_TEXT​,​ e​.​toString​());
receiver​.​send​(​STATUS_ERROR​,​ bundle​);

Implementiranje ResultReceiver-a
Potrebno je implementirati ResultReceiver klasu:

public​ ​class​ ​MojResultReceiver​ ​extends​ ResultReceiver ​{


​private​ Receiver mReceiver​;

​public​ ​MojResultReceiver​(​Handler handler​)​ ​{


​super​(​handler​);
​}

​public​ ​void​ ​setReceiver​(​Receiver receiver​)​ ​{


mReceiver ​=​ receiver​;
​}

​/* Deklaracija interfejsa koji će se trebati implementirati */


​public​ ​interface​ ​Receiver​ ​{
​public​ ​void​ ​onReceiveResult​(​int​ resultCode​,​ Bundle resultData​);
​}

​@Override
​protected​ ​void​ ​onReceiveResult​(​int​ resultCode​,​ Bundle resultData​)​ ​{
​if​ ​(​mReceiver ​!=​ ​null​)​ ​{
mReceiver​.​onReceiveResult​(​resultCode​,​ resultData​);
​}
​}
}
Kada smo kreirali klasu za ResultReceiver potrebno je dodati i u aktivnost implementaciju
interfejsa i njegove metode koja će se pozivati prilikom dobivanja obavještenja od
IntentService-a.
Tako u aktivnosti dodajemo da ona implementira interfejs ​MojResultReceiver.Receiver:

/* Primjer: */
public​ ​class​ ​MyActivity​ ​extends​ Activity ​implements​ MojResultReceiver​.​Receiver​ ​{

Nakon što je dodano da aktivnost implementira interfejs potrebno je implementirati njegovu


metodu ​onReceiveResult​.

@Override
​public​ ​void​ ​onReceiveResult​(​int​ resultCode​,​ Bundle resultData​)​ ​{
​switch​ ​(​resultCode​)​ ​{
​case​ MyIntentService​.​STATUS_RUNNING​:
​/* Ovdje ide kod koji obavještava korisnika da je poziv upućen */
​break​;
​case​ MyIntentService​.​STATUS_FINISHED​:
​/* Dohvatanje rezultata i update UI */
String​[]​ results ​=​ resultData​.​getStringArray​(​"result"​);
​break​;
​case​ MyIntentService​.​STATUS_ERROR​:
​/* Slučaj kada je došlo do greške */
String error ​=​ resultData​.​getString​(​Intent​.​EXTRA_TEXT​);
Toast​.​makeText​(​this​,​ error​,​ Toast​.​LENGTH_LONG​).​show​();
​break​;
​}
​}

Napomena: ​MyIntentService.STATUS_RUNNING, STATUS_FINISHED i


STATUS_ERROR trebate definisati kao public polja klase MyIntentService. Ova polja
mogu biti tipa int i imati vrijednosti npr: 0, 1 i 2 redom.

Zadaci: ​Implementirati sve pozive web servisa preko IntentService-a sa prošle vježbe. Na
kraju vježbe trebate imati bar jedan poziv web servisa realizovan koristeći IntentService.
Da li bi bio bolji AsyncTask ili IntentService za zadatak koji zahtijeva osvježavanje
vremenske prognoze svakih pet minuta? Kako biste implementirali ovu funkcionalnost?

You might also like