În Android, clasa android.app.Service este utilizată pentru componente a căror funcționalitate implică procesări complexe, de lungă durată, necesitând acces la anumite resurse, fără a fi necesar să pună la dispoziție o interfață grafică sau un mecanism de interacțiune cu utilizatorul. Prin intermediul unui serviciu, se asigură faptul că aplicația Android continuă să se găsească în execuție, chiar și atunci când interfața grafică a acesteia nu este vizibilă.
Astfel, un serviciu nu trece prin evenimentele ce fac parte din ciclul de viață al unei activități. Totuși, un serviciu poate fi controlat (pornit, oprit) din contextul altor componente ale unei aplicații Android (activități, ascultători de intenții cu difuzare, alte servicii).
Serviciul este rulat pe firul de execuție principal al aplicației Android. De aceea, în situația în care operațiile pe care le realizează poate influența experiența utilizatorului, acestea trebuie transferate pe alte fire de execuție din fundal (folosind clasele HandlerThread
sau AsyncTask
).
Un serviciu continuă să se ruleze chiar și în situația în care componenta aplicației Android care l-a invocat prin intermediul unei intenții devine inactivă (nu mai este vizibilă) sau chiar este distrusă. Acest comportament este adecvat în situația în care serviciul realizează operații de intrare/ieșire intensive, interacționează cu diverse servicii accesibile prin intermediul rețelei sau cu furnizori de conținut.
În programarea Android, există două tipuri de servicii:
Un serviciu poate avea, în același timp, ambele tipuri (started și bounded). Acesta va putea rula o perioadă de timp nedefinită (până în momentul în care procesarea pe care o realizează este încheiată), permițând totodată componentelor unei aplicații Android să interacționeze cu el prin intermediul unor metode pe care le expune.
Indiferent de modul în care este folosit un serviciu, invocarea sa este realizată, ca pentru orice componentă Android, prin intermediul unei intenții. În cazul serviciilor, se preferă să se utilizeze intenții explicite, în detrimentul intențiilor implicite, din motive de securitate.
Categoria în care se încadrează un anumit serviciu este determinată de metodele pe care acesta le implementează:
startService()
;bindService()
;unbindService()
.
Pentru a putea fi utilizat, orice serviciu trebuie să fie declarat în cadrul fișierului AndroidManifest.xml
, prin intermediul etichetei <service> în cadrul elementului <application>. Eventual, se poate indica o permisiune necesară pentru pornirea și oprirea serviciului, astfel încât aceste operații să poată fi realizate numai de anumite aplicații Android.
<manifest ...> <application ...> <service android:name="ro.pub.cs.systems.eim.lab05.SomeService" android:enabled="true" android:exported="true" android:permission="ro.pub.cs.systems.eim.lab05.SOME_SERVICE_PERMISSION" /> </application> </manifest>
Αtributul android:name este singurul obligatoriu în cadrul elementului <service>
, desemnând clasa care gestionează operațiile specifice serviciului respectiv. Din momentul în care aplicația Android este publicată, această valoare nu mai poate fi modificată, întrucât poate avea un efect asupra componentelor care utilizează acest serviciu prin intermediul unei intenții explicite folosită la pornirea serviciului sau la asocierea componentei cu serviciul respectiv.
ΑndroidManifest.xml
. Totuși, în situația în care acest lucru este absolut necesar, ar trebui să se indice intenției măcar pachetul în care se regăsește serviciul respectiv. Începând cu Android 5.0, tentativa de pornire a unui serviciu folosind intenții implicite determină excepție, fiind nepermisă.
Atributul android:enabled
indică dacă serviciul poate fi instanțiat de către sistemul de operare.
Atributul android:exported
specifică posibilitatea ca alte componente (aparținând altor aplicații) să poată interacționa cu serviciul. În situația în care serviciul nu conține filtre de intenții, acesta poate fi invocat numai prin precizarea explicită a numelui clasei care îl gestionează (calificată complet), valoarea sa fiind false
, fiind destinat invocării din contextul unor componente aparținând aceleiași aplicații Android ca și el. În cazul în care serviciul definește cel puțin un filtru de intenții, valoarea sa este true
, fiind destinat invocării din contextul altor aplicații Android.
Atributul android:permission
precizează denumirea unei permisiuni pe care entitatea trebuie să o dețină pentru a putea lansa în execuție un serviciu sau pentru a i se putea asocia. Aceasta trebuie indicată în cadrul intențiilor transmise ca argumente metodelor utilizate pentru a invoca serviciul respectiv (startService()
, respectiv bindService()
), altfel intenția respectivă nu va fi livrată către serviciu.
Alte atribute ale elementului <service>
sunt: android:icon
, android:isolatedProcess
, android:label
, android:process
.
Un serviciu este o clasă derivată din android.app.Service
(sau din subclasele sale), implementând o serie de metode din ciclul de viață al serviciului:
startService()
; serviciul va fi executat imediat după această metodă; este responsabilitatea programatorului să oprească serviciul printr-un apel al uneiea dintre metodele stopSelf()
, respectiv stopService()
, altfel serviciul va rula pentru o perioadă de timp nedefinită; nu este necesar ca metoda să fie implementată, dacă serviciul este de tip bounded;bindService()
; implementarea acestei metode trebuie să furnizeze un obiect ce implementează interfața ΙBinder, prin intermediul căruia serviciul să poată interacționa cu componenta care l-a invocat, punând la dispoziție o anumită funcționalitate, descrisă de metode publice; toate tipurile de serviciu trebuie să implementeze această metodă, însă pentru serviciile de tip started, valoarea întoarsă va fi null
;οnUnbind()
); o astfel de metodă va fi invocată numai dacă rezultatul întors de metoda οnUnbind()
este true
;
|
|
Se observă faptul că ciclul de viață al unui serviciu are loc între metodele onCreate()
și onDestroy()
. De cele mai multe ori, este necesar ca un serviciu să folosească unul sau mai multe fire de execuție (dedicate) astfel încât să nu influențeze responsivitatea aplicației Android. Într-o astfel de situație, firul de execuție va fi pornit pe metoda onCreate()
și va fi oprit pe metoda onDestroy()
. De asemenea, în cadrul metodei onCreate()
au loc diferite operații de configurare (inițializări), în timp ce în cadrul metodei onDestroy()
realizează eliberarea resurselor folosite. Metodele onCreate()
și onDestroy()
sunt invocate pentru toate tipurile de servicii, atât pentru cele de tip started, cât și pentru cele de tip bounded.
În cazul unui serviciu de tip started, perioada activă din ciclul de viață este cuprinsă între apelul metodei onStartCommand()
(apelată în mod automat atunci când o componentă apelează metoda startService()
, primind ca argument obiectul de tip Intent
care a fost folosit la invocarea sa) și apelul metodei onDestroy()
(apelat atunci când serviciul este oprit, prin intermediul uneia dintre metodele stopSelf()
sau stopService()
).
În cazul unui serviciu de tip bounded, perioada activă din ciclul de viață este cuprinsă între apelul metodei onBind()
(apelată în mod automat atunci când o componentă apelează metoda bindService()
, primind ca argument obiectul de tip Intent
care a fost folosit la invocarea sa) și apelul metodei onUnbind()
(apelată în mod automat atunci când toate componentele asociate serviciului au apelat metoda unbindService()
).
onStartCommand()
să fie apelată și metoda onBind()
. În această situație serviciul va fi activ până când nu va mai deține nici o componentă asociată. Apelurile metodelor stopSelf()
sau stopService()
nu vor avea așadar efect atâta timp cât mai există componente conectate la serviciu.
În momentul în care este pornit (printr-un apel al metodei startService()
din cadrul altei componente), un serviciu apelează în mod automat metoda onStartCommand()
. În cadrul acestei metode trebuie realizată procesarea pe care o presupune serviciul respectiv. Pentru serviciile de tip started, este necesar ca serviciul să fie oprit explicit, fie din propriul său context, printr-un apel al metodei stopSelf()
, fie de către o componentă (aceeași sau alta decât cea care l-a invocat), prin intermediul metodei stopService()
.
Metoda onStartCommand()
este apelată în mod automat în momentul în care serviciul este pornit prin intermediul metodei startService()
și primește ca parametrii:
startService()
); acesta poate fi null
în situația în care serviciul este repornit după ce procesul din care făcea parte a fost distrus;Având în vedere faptul că această metodă poate fi apelată de mai multe ori pe parcursul ciclului de viață al unui serviciu, tot aici trebuie implementat și comportamentul în cazul în care acesta este repornit.
Comportamentul serviciului în situația în care este repornit poate fi controlată prin intermediul valorii întregi care este furnizată ca valoare întoarsă:
startService()
și stopService()
); prin aceasta, se indică faptul că metoda onStartCommand()
va fi invocată de fiecare dată când serviciul este (re)pornit după ce a fost distrus de sistemul de operare Android (situație în care parametrul de tip Intent
va avea valoarea null
, dacă între timp serviciul nu a mai fost invocat);stopSelf()
) atunci când operațiile pe care trebuiau să le realizeze s-au terminat; serviciul este (re)pornit după ce a fost distrus de sistemul de operare Android numai în situația în care între timp au mai fost realizate apeluri ale metodei startService()
, altfel nu este recreat; un astfel de comportament este adecvat pentru procese care sunt realizate periodic;onStartCommand()
folosind ca parametru intenția originală, a cărei procesare nu a fost terminată corespunzător.@Override public int onStartCommand(Intent intent, int flags, int startId) { processInBackground(intent, startId); return startMode; }
În toate aceste cazuri, oprirea serviciului trebuie realizată explicit, prin apelul metodelor:
stopService()
din contextul componentei care l-a pornit;stopSelf()
din contextul serviciului.
Un serviciu este pornit printr-un apel al metodei startService()
. Aceasta primește ca parametru un obiect de tip Intent
care poate fi creat:
Intent intent = new Intent(this, SomeService.class); startService(intent);
setComponent()
asociat intenției respective): Intent intent = new Intent(); intent.setComponent(new ComponentName("SomePackage", "SomeService")); startService(intent);
Transmiterea de informații suplimentare către serviciu poate fi realizată prin intermediul metodelor putExtras(Bundle)
, putExtra(String, Parcelable)
sau put<type>Extra(String, <type>)
.
Metoda startService()
presupune livarea unui mesaj asincron la nivelul sistemului de operare Android, astfel încât aceasta se execută instantaneu. Fiecare apel al acestei metode invocarea, în mod automat, al metodei onStartCommand()
.
De regulă, comunicația dintre o componentă și un serviciu este unidirecțională, dintre componenta care invocă către serviciul invocat, prin intermediul datelor încapsulate în intenție. În situația în care se dorește ca serviciul să transmită date către componentă, se utilizează un obiect de tip PendingIntent, prin intermediul căruia pot fi transmise mesaje cu difuzare.
Un serviciu poate fi oprit:
stopService()
, ce primește ca parametru un obiect de tip Intent
care poate fi creat explicit sau implicit;
startService()
nu sunt imbricate, invocarea metodei stopService()
oprește numai serviciul corespunzător (dacă se află în execuție).
stopSelf()
, eliberând resursele pe care sistemul de operare le-ar fi folosit pentru a-l menține în execuție; metoda poate fi apelată:onStartCommand()
) și metoda nu va avea nici un efect.
Pentru oprirea unui serviciu este suficient un singur apel al uneia dintre metodele stopSelf()
sau stopService()
.
Recursul la clasa IntentService este adecvată în situația în care se dorește implementarea unor servicii care rulează în fundal, pe un fir de execuție dedicat realizând un set de operații la un moment dat de timp, atunci când sunt solicitate. În situația în care serviciul este accesat simultan de mai multe componente ale unei (unor) aplicații Android, procesarea acestora va fi realizată secvențial.
Un obiect de acest tip este lansat în execuție prin pornirea unui serviciu și transmiterea unui obiect de tip Intent
care conține toți parametrii necesari pentru realizarea sarcinii respective. Toate operațiile solicitate sunt înregistrate într-o coadă de așteptare și executate succesiv, prin invocarea automată a metodei onHandleIntent(). După ce procesarea a fost finalizată, procesul se oprește singur (nu este necesar să se apeleze metodele stopSelf()
sau stopService()
explicit).
IntentService
oferă o implementare implicită a metodelor onStartCommand()
care plasează intenția către coada de așteptare și invocă metoda onHandleIntent()
precum și a metodei onBind()
care întoarce valoarea null
.
Prin urmare, o implementare presupune definirea unei clase derivată din IntentService
care suprascrie metoda onHandleIntent()
, ce primește ca parametru intenția conținând parametrii ce se doresc a fi procesați, execuția sa fiind realizată (în mod transparent) pe un fir de execuție ce rulează în fundal. De asemenea, trebuie furnizat și un constructor implicit.
import android.app.IntentService; import android.content.Intent; public class SomeStartedService extends IntentService { private final String name = "SomeStartedService"; public SomeIntentService() { super(name); // ... } @Override protected void onHandleIntent(Intent intent) { // ... } }
În situația în care este necesar să se suprascrie metodele onCreate()
, onDestroy()
sau onStartCommand()
, este recomandat să se apeleze și metodele din clasa părinte, astfel încât ciclul de viață al firului de execuție pe care sunt realizate procesările să fie actualizat corespunzător.
@Override public int onStartCommand(Intent intent, int flags, int startId) { // ... return super.onStartCommand(intent, flags, startId); }
În situația în care este necesar ca serviciul să gestioneze mai multe solicitări concomitent, se implementează o clasă derivată din Service
, definindu-se (intern) o clasă de tip fir de execuție (uzual, se folosește o instanță a clasei Handler, care permite transmiterea de mesaje) pe care vor fi realizate toate procesările.
public class SomeStartedService extends Service { private Looper looper; private ServiceHandler serviceHandler; private final static String handlerThreadName = "HandlerThread"; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message message) { // ... stopSelf(message.arg1); } } @Override public void onCreate() { HandlerThread handlerThread = new HandlerThread(handlerThreadName, Process.THREAD_PRIORITY_BACKGROUND); handlerThread.start(); looper = handlerThread.getLooper(); serviceHandler = new ServiceHandler(looper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Message message = serviceHandler.obtainMessage(); message.arg1 = startId; serviceHandler.sendMessage(message); return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } }
Procesarea este realizată în cadrul metodei handleMessage() a clasei de tip Handler
. O instanță a acesteia este creată de fiecare dată când este invocată metoda sendMessage(), care are drept argument un obiect de tip Message. Prin intermediul mesajului (obținut ca rezultat al metodei obtainMessage() care furnizează o instanță dintr-o colecție globală), trebuie să se transmită și identificatorul cu care a fost invocat serviciul, astfel încât acesta să fie transmis către metoda stopSelf()
, necesară pentru oprirea serviciului. Astfel, se asigură faptul că serviciul nu se termină până ce nu este tratată și cea mai recentă invocare a sa.
În situația în care este necesar, sistemul de operare Android poate solicita memoria utilizată de un anumit serviciu, în funcție de prioritatea pe care acesta o are.
Un serviciu de tip started are o prioritate mai mare decât o activitate inactivă, dar mai mică decât o activitate activă. Din acest motiv, este necesar ca programatorul să trateze cazurile în care serviciul este repornit, ca urmare a distrugerii sale de către sistemul de operare Android, în vederea satisfacerii necesarului de memorie.
Un serviciu de tip bounded are de obicei o prioritate similară cu a activității cu care interacționează, deși aceasta poate fi controlată prin intermediul unor parametrii transmiși în momentul în care este realizată legătura. Este puțin probabil ca un serviciu atașat unei activități active să fie distrus la fel cum este destul de probabil ca un serviciu pentru care toate activitățile atașate sunt inactive să fie distrus.
Un serviciu care rulează în prim-plan nu este aproape niciodată distrus.
În funcție de firul de execuție pe care rulează precum și de prioritatea care le este atribuită (în funcție de care sistemul de operare Android poate reclama resursele pe care acestea le utilizează), operațiile complexe pot fi realizate prin intermediul unor:
În situația în care un serviciu interacționează cu utilizatorul (sau utilizatorul este conștient de existența sa), prioritatea acestuia trebuie mărită, astfel încât sistemul de operare Android să nu poată reclama resursele pe care le utilizează. Un astfel de serviciu trebuie să poată oferi o notificare în bara de stare, care nu poate fi ascunsă decât atunci când serviciul este distrus sau nu mai rulează în prim-plan.
Un astfel de comportament poate fi obținut prin marcarea serviciului ca rulând în prim-plan, prin invocarea metodei startForeground() care primește ca parametri:
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.notification_icon) .setContentTitle("...") .setContentText("..."); Intent intent = new Intent(this, SomeActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); notificationBuilder.setContentIntent(pendingIntent); Notification notification = notificationBuilder.build(); startForeground(NOTIFICATION_ID, notification);
Este recomandat ca utilizatorii să poată dezactiva ei înșiși rularea serviciului în prim-plan, de regulă prin intermediul obiectului de tip Notification
. Același lucru trebuie realizat și în momentul în care rularea serviciului în prim-plan nu mai este necesară. În acest sens, trebuie apelată metoda stopForeground(true)
(argumentul metodei indică dacă se dorește și terminarea notificării), aceasta anulând în mod automat notificarea asociată.
Întrucât responsivitatea aplicației Android reprezintă un criteriu foarte important, asigurarea unei experiențe corespunzătoare a utilizatorului poate fi obținută prin transferul operațiilor complexe de procesare și a operațiilor de intrare / ieșire în fundal, pe un alt fir de execuție. Acest lucru este necesar datorită faptului că pe firul de execuție principal sunt rulate mai multe componente ale aplicației Android (activități, servicii, ascultători ai intențiilor cu difuzare), astfel încât interacțiunea cu utilizatorul poate fi blocată în situația în care procesarea realizată de servicii nu este transferată în fundal.
onReceive()
nu se termină în 5 secunde sunt considerate blocate.
De regulă, sunt plasate pe fire de execuție dedicate operații cu fișiere (scrieri, citiri), căutări în rețea, tranzacții cu baza de date, calcule complexe.
Android oferă mai multe mecanisme pentru gestiunea serviciilor care rulează în fundal:
Prin intermediul clasei AsyncTask
se permite mutarea operațiilor costisitoare din punct de vedere al utilizării procesorului și al memoriei pe fire de execuție rulate în fundal, oferind sincronizarea cu firul de execuție ce randează interfața grafică, acesta fiind notificat cu privire la progresul realizat cât și cu privire la momentul în care procesarea a fost terminată.
AsyncTask
nu este persistentă în cazul (re)pornirii unei activități (situație în care firul de execuție asociat este distrus), se recomandă utilizarea sa pentru procese de fundal care nu durează o perioadă de timp prea mare.
O implementare a clasei AsyncTask
poate fi parametrizată cu tipurile de date care sunt folosite pentru intrare, pentru raportarea progresului și pentru ieșire (rezultate).
Void
pentru toate aceste tipuri.
Este necesară suprascrierea următoarelor metode:
onProgressUpdate()
- folosită pentru actualizarea interfeței grafice, cu valorile primite de la metoda publishProgress()
; metoda este sincronizată cu firul de execuție principal (care randează interfața grafică), astfel încât diferitele controale pot fi accesate de aici;onPreExecute()
- apelată în contextului firului de execuție al interfeței grafice, înainte de metoda doInBackground()
;onPostExecute()
- apelată în momentul în care procesarea este terminată, primind ca parametru rezultatul întors de metoda doInBackground()
; metoda este sincronizată cu firul de execuție principal (care randează interfața grafică), astfel încât diferitele controale pot fi accesate de aici.private class SomeAsyncTask extends AsyncTask<String, Integer, String> { @Override protected String doInBackground(String... parameter) { String result = new String(); int progress = 0; for (int step = 1; step <= parameter[0].length(); step++) { progress = step; result += parameter[0].charAt(parameter[0].length() - step); try { Thread.sleep(100); } catch (InterruptedException interruptedException) { Log.e(Constants.TAG, "An exception has occurred: " + interruptedException.getMessage()); } publishProgress(progress); } return result; } @Override protected void onProgressUpdate(Integer... progress) { progressTextView.setText(progress[0].toString()); } @Override protected void onPreExecute() { // ... } @Override protected void onPostExecute(String result) { progressTextView.setText(result); } }
Rularea unui obiect de tip AsyncTask
se face prin invocarea metodei execute()
care primește ca parametru informațiile care se doresc a fi procesate.
AsyncTask
poate fi rulat o singură dată. Apelurile ulterioare ale metodei execute()
vor genera excepții.
Clasa abstractă Loader
implementează practicile recomandate pentru încărcarea asincronă a informațiilor în cadrul unor controale grafice din interfața cu utilizatorul (afișate în activități sau fragmente).
O astfel de abordare este utilizată pentru:
De regulă, se folosește o implementare a clasei AsyncTaskLoader
.
Clasa Handler
gestionează transmiterea de mesaje sau execuția unor rutine asociate unor fire de execuție prin intermediul unei cozi de așteptare (de tipul MessageQueue). Un obiect de acest tip este asociat unui singur fir de execuție. Acesta este creat în momentul în care este realizată instanța, cătrea cesta fiind transmise toate mesajele, secvențial.
Prin intermediul acestui obiect, se permite:
send…()
: sendEmptyMessage()
, sendMessage()
, sendMessageAtTime()
, sendMessageDelayed()
);post…()
: post()
, postAtTime()
, postDelayed()
).
Folosirea unor fire de execuție definite de utilizator și sincronizarea manuală cu interfața grafică poate fi necesară în situația în care trebuie realizată o gestiune mai complexă decât cea oferită de clasele AsyncTask
, Handler
sau Loader
.
În acest sens, este folosită o implementare a clasei Thread
, procesarea pe un fir de execuție separat fiind realizată în cadrul metodei run()
.
private void executeOnDedicatedThread() { Thread dedicatedThread = new Thread(new Runnable() { @Override public void run() { // ... } }); dedicatedThread.start(); }
Dacă se dorește actualizarea controalelor din cadrul interfeței grafice, este necesar ca firele de execuție care rulează în fundal să fie sincronizate cu firul de execuție principal anterior acestei operații. Acest lucru poate fi realizat:
runOnUiThread(new Runnable() { @Override public void run() { // ... } });
Handler
pentru a realiza actualizări în contextul firului de execuție în care acesta a fost creat, utilizând metoda post(): private Handler handler = new Handler(); // created on the main thread private void executeOnSeparateThread() { Thread separateThread = new Thread(new Runnable() { @Override public void run() { // do some background processing here handler.post(new Runnable() { @Override public void run() { // access the graphical user interface here } }); } }); separateThread.start(); }
Clasa Handler
pune la dispoziție și metode pentru a executa anumite metode la un moment dat de timp:
adb shell dumpsys activity services | grep lab05
, doar cele pornite sunt afișate
adb shell pm list packages -f | grep lab05
am force-stop ro.pub.cs.systems.eim.lab05.startedservice
am startservice ro.pub.cs.systems.eim.lab05.startedservice/.service.StartedService
am start-foreground-service ro.pub.cs.systems.eim.lab05.startedservice/.service.StartedService
1. În contul Github personal, să se creeze un depozit denumit 'Laborator05'. Inițial, acesta trebuie să fie gol (nu trebuie să bifați nici adăugarea unui fișier README.md, nici a fișierului .gitignore sau a a fișierului LICENSE).
2. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/eim-lab/Laborator05. În urma acestei operații, directorul Laborator05 va trebui să se conțină un director labtasks
ce va deține proiectele AndroidStudio, fișierul README.md și un fișier .gitignore care indică tipurile de fișiere (extensiile) ignorate.
student@eim:~$ git clone https://www.github.com/eim-lab/Laborator05.git
3. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator05' de pe contul Github personal.
student@eim:~$ cd Laborator05 student@eim:~/Laborator05$ git remote add Laborator05_perfectstudent https://github.com/perfectstudent/Laborator05 student@eim:~/Laborator05$ git push Laborator05_perfectstudent master
4. Să se încarce în mediul integrat de dezvoltare Android Studio proiectele StartedService respectiv StartedServiceActivity din directorul labtasks/StartedService
.
SLEEP_TIME
din interfața Constants
). Aceste valori sunt transmise prin intermediul unor intenții cu difuzare (eng. broadcast intents), la nivelul întregului sistem de operare Android.
5. În proiectul StartedService, în clasa StartedService
din pachetul ro.pub.cs.systems.eim.lab05.startedservice.service
, să se completeze metoda onStartCommand()
astfel încât aceasta să pornească un fir de execuție în cadrul căruia să fie propagate 3 intenții cu difuzare la nivelul sistemului de operare Android.
Pentru fiecare intenție, se vor specifica:
Constants
(Constants.ACTION_STRING
, Constants.ACTION_INTEGER
, Constants.ACTION_ARRAY_LIST
); se va utiliza metoda setAction();extra
(având cheia Constants.DATA
și valoarea dată de Constants.STRING_DATA
, Constants.INTEGER_DATA
, Constants.ARRAY_LIST_DATA
); se va utiliza metoda putExtra() care primește ca argumente cheia și valoarea.Transmiterea propriu-zisă a intenției se face prin intermediul metodei sendBroadcast().
Cele trei mesaje vor fi temporizate la intervalul indicat de valoarea Constants.SLEEP_TIME
(propagarea mesajelor va fi intercalată de apeluri Thread.sleep().
a) De ce este necesar ca serviciul să realizeze operațiile pe un fir de execuție dedicat?
b) Ce alternativă s-ar fi putut folosi pentru a se evita o astfel de abordare? Ce avantaj și ce dezavantaj prezintă această alternativă?
Monitorizați ciclurile din Thread.run() in logcat:
Log.d(Constants.TAG, "Thread.run() was invoked, PID: " + android.os.Process.myPid() + " TID: " + android.os.Process.myTid());
6. În proiectul StartedServiceActivity, să se pornească serviciul, printr-un apel al metodei startService(); intenția care va fi transmisă ca argument metodei startService()
trebuie să refere explicit serviciul care urmează a fi pornit, din motive de securitate (se folosește metoda setComponent(), care indică atât pachetul corespunzător aplicației Android care conține serviciul, cât și clasa corespunzătoare acestuia - calificată complet).
a) Să se ruleze aplicațiile. Se va rula aplicația StartedService care instalează serviciul pe dispozitivul mobil. Ulterior se va rula aplicația StartedServiceActivity. Verificați faptul că serviciul a fost pornit și oprit corespunzător prin mesajele afișate în consolă.
b) Monitorizați în DDMS procesele asociate activității și serviciului. Ce se întâmplă dacă activitatea este eliminată (onDestroy()
)?
c) Explicați ce se întâmplă dacă repornim activitatea (monitorizați în DDMS si logcat).
7. În proiectul StartedServiceActivity, să se implementeze un ascultător pentru intenții cu difuzare, în clasa StartedServiceBroadcastReceiver
din pachetul ro.pub.cs.systems.eim.lab05.startedserviceactivity.view
. Acesta extinde clasa BroadcastReceiver și implementează metoda onReceive(), având ca argumente contextul din care a fost invocată și intenția prin intermediul căreia a fost transmis mesajul respectiv. Astfel, datele extrase din intenție (având cheia indicată de Constants.DATA
) vor fi afișate într-un câmp text (messageTextView
) din cadrul interfeței grafice.
8. În proiectul StartedServiceActivity, în cadrul metodei onCreate()
a activității StartedServiceActivity
(din pachetul ro.pub.cs.systems.eim.lab05.startedserviceactivity
), să se realizeze următoarele operații:
a) să se creeze o instanță a ascultătorului pentru intenții cu difuzare;
b) să se creeze o instanță a unui obiect de tipul IntentFilter, la care să se adauge toate acțiunile corespunzătoare intențiilor cu difuzare propagate de serviciu; se va folosi metoda addAction();
c) să se atașeze, respectiv să se detașeze ascultătorul de intenții cu difuzare, astfel încât acesta să proceseze mesajele primite de la serviciu doar în situația în care activitatea este vizibilă pe suprafața de afișare; în acest sens, vor fi utilizate metodele registerReceiver(), respectiv unregisterReceiver(), apelate pe metodele de callback ale activității corespunzătoare stării în care aceasta este vizibilă pe suprafața de afișare (onResume()
, respectiv onPause()
).
d) Să se oprească serviciul printr-un apel al metodei stopService(). Unde ar putea fi plasată aceasta? Care sunt avantajele și dezavantajele unei astfel de abordări?
9. Rulați din nou aplicația, întrerupând temporar activitatea (printr-o apăsare a tastei Home) în timp ce sunt procesate intențiile cu difuzare transmise de serviciu. Ce observați la revenirea în activitate?
Modificați modul de implementare al ascultătorului de intenții cu difuzare astfel încât în momentul în care se primește un mesaj, să repornească activitatea (dacă este cazul), asigurându-se astfel faptul că nu se mai pierde nici o informație transmisă de serviciu dacă aceasta nu este vizibilă pe suprafața de afișare.
10. [Opțional] Să se încarce în mediul integrat de dezvoltare Android Studio proiectul BoundedServiceActivity din directorul labtasks/BoundedService
. Se dorește să se implementeze un serviciu de tip bounded care poate interacționa cu o activitate prin intermediul unei metode care furnizează un șir de caractere ales aleator dintr-o listă de valori.
a) În clasa BoundedService
din pachetul ro.pub.cs.systems.eim.lab05.boundedserviceactivity.service
, să se implementeze o clasă internă publică, derivată din interfața IBinder, care oferă o referință către serviciu prin intermediul metodei publice getService()
. Instanța acestei clase interne va fi furnizată ca rezultat al metodei onBind().
b) În clasa BoundedService
din pachetul ro.pub.cs.systems.eim.lab05.boundedserviceactivity.service
, să se implementeze o metodă care întoarce o valoare aleatoare din tabloul de constante Constants.MESSAGES
. Aceasta va avea identificatorul de acces public
, astfel încât să poată fi accesată din cadrul activității.
c) În clasa BoundedServiceActivity
din pachetul ro.pub.cs.systems.eim.lab05.boundedserviceactivity.view
, să se instanțieze un obiect de tipul ServiceConnection, care va fi utilizat pentru asocierea serviciului la activitate.
Vor fi suprascrise metodele:
IBinder
(prin intermediul metodei getService()
) și se actualizează variabila care reține starea legăturii dintre serviciu și activitate;d) Să se atașeze, respectiv să se detașeze activitatea la / de serviciu, în cadrul metodelor de callback corespunzătoare stării în care aceasta poate interacționa cu utilizatorul.
onStart()
se apelează metoda bindService(), utilizându-se un obiect de tip Intent
explicit (care primește ca argument clasa care urmează să fie invocată);onStop()
se apelează metoda unbindService(); se actualizează variabila care reține starea legăturii dintre serviciu și activitate.e) Să se implementeze o clasă ascultător pentru evenimentul de tip apăsare corespunzător controlului grafic de tip buton. În cadrul metodei de tratare a evenimentului, să se apeleze metoda din cadrul serviciului care furnizează un mesaj aleator și să se afișeze acest mesaj în cadrul câmpului text, împreună cu data și ora la care a fost furnizat.
f) Să se afișeze mesaje sugestive pe metodele de callback ale activității și ale serviciului și să se observe care este momentul în care acesta este creat, respectiv este distrus. Ce se întâmplă în momentul în care se apasă tasta Home?
11. Să se încarce modificările realizate în cadrul depozitului 'Laborator05' de pe contul Github personal, folosind un mesaj sugestiv.
student@eim:~/Laborator05$ git add labtasks/* student@eim:~/Laborator05$ git commit -m "implemented taks for laboratory 05" student@eim:~/Laborator05$ git push Laborator05_perfectstudent master
Services
Bound Services
Android Service and Broadcast Receiver Example
Android Services - Tutorial
Dave MacLEAN, Satya KOMATINENI, Grant ALLEN, Pro Android 5 - capitolele 16 (BroadcastReceivers and Long-Running Services) și 17 (Exploring the Alarm Manager)
Marko GARGENTA, Masumi NAKAMURA, Learning Android, 2nd Edition - capitolele 10 (Services) și 13 (Broadcast Receivers)
Reto MEIER, Android 4 Application Development, capitolul 5 (Intents and Broadcast Receivers)
Joseph ANNUZZI, Jr, Lauren DARCEY, Shane CONDER, Introduction to Android Application Development - Developer's Library, 4th Edition, Addison-Wesley, 2013 - capitolul 4, subcapitolele Working with Services, Receiving and Broadcasting Intents
Bill PHILLIPS, Brian HARDY, Android Programming. The Big Nerd Ranch Guide, Pearson Technology Group, 2013 - capitolele 29 (Background Services), 30 (Broadcast Intents)