status = BOUNDED;
}
@Override
public void onServiceDisconnected(ComponentName className) {
someBoundedService = null;
status = UNBOUNDED;
}
};
}
</code>
Atașarea propriu-zisă a unei componente a unei aplicații Android la un serviciu este realizată de metoda bindService() care primește ca parametri:- intenția reprezentând serviciul care se asociază activității;
- un obiect de tip
ServiceConnection; - anumite valori prin care se controlează modul în care este făcută asocierea între cele două componente ale aplicației Android:
- Context.BIND_AUTO_CREATE - serviciul trebuie creat în momentul în care se realizează asocierea;
- Context.BIND_ADJUST_WITH_ACTIVITY - modifică prioritatea serviciului în funcție de importanța activității de care este legat (mai mare atunci când este activă, mai mică atunci când este inactivă);
- Context.BIND_ABOVE_CLIENT / Context.BIND_IMPORTANT - specifică faptul că prioritatea serviciului este mai mare decât a activității asociate;
- Context.BIND_NOT_FOREGROUND - asigură faptul că serviciul atașat activității nu va avea niciodată prioritatea necesară pentru a rula în prim-plan;
- Context.BIND_WAIVE_PRIORITY - face ca prioritatea serviciului asociat să nu fie modificată (implicit, prioritatea relativă a serviciului este mărită).
onStart() a unei activități.
Detașarea unei componente a unei aplicații Android la un serviciu este realizată de metoda unbindService() care primește ca argument un obiect de tip ServiceConnection (care monitorizează starea conectivității dintre componentă și serviciu). Decuplarea este realizată, de obicei, în cadrul metodei onStop() a unei activități.
public class SomeActivity extends Activity { // ... @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, SomeBoundedService.class); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (status == BOUNDED) { unbindService(serviceConnection); } } }În situația în care este necesar ca serviciul să fie utilizat și atunci când activitatea se află în fundal, metodele
bindService() și unbindService() pot fi apelate pe metodele de callback onCreate() respectiv onDestroy(). Totuși, aceasta implică o probabilitate mai mare ca memoria utilizată de serviciu să fie solicitată de sistemul de operare Android, dacă acesta nu rulează în contextul aceluiași proces ca și componenta cu care interacționează.
Nu se recomandă ca atașarea, respectiv detașarea unei componente la / de un servicu să fie realizată pe metodele onResume() și onPause() întrucât acestea sunt apelate frecvent, astfel încât procesările realizate în aceste momente să poată determina deprecieri ale performanței sistemului de operare Android. Mai mult, în cazul în care mai multe activități interacționează cu același serviciu, atunci când se realizează transferul între ele, serviciul ar putea fi distrus (pe metoda onPause() se apelează unbindService(), deci toate componentele au fost detașate serviciului), respectiv recreat (pe metoda onResume() se apelează bindService(), deci serviciul are o componentă atașată).
În situația în care este necesar ca serviciul să poată fi atașat la mai multe componente simultan, aparținând unor procese diferite (realizându-se astfel comunicația inter-proces), realizând procesări pe fire de execuție dedicate, se folosește un obiect de tip Messenger. Obiectul de tip Binder asociat acestuia va fi transmis ca rezultat al metodei onBind(). De asemenea, el va încapsula un obiect de tip Handler pe a cărui metodă handleMessage() va fi realizată procesarea corespunzătoare serviciului, acesta putând gestiona tipuri de mesaje diferite, corespunzătoare fiecărui tip specific de componentă. Obiectul Messenger plasează toate apelurile către serviciu într-o coadă de așteptare, toate fiind procesate, secvențial, în contextul aceluiași fir de execuție.
- SomeBoundedService.java
public class SomeBoundedService extends Service { class SomeHandler extends Handler { @Override public void handleMessage(Message message) { switch (msg.arg1) { // ... default: super.handleMessage(message); } } } final Messenger messenger = new Messenger(new SomeHandler()); @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); } }
.aidl care descrie interfața de programare, se va genera o clasă abstractă responsabilă cu comunicația inter-proces, din care va fi derivat serviciul respectiv.
==== Prioritatea unui Serviciu ====
Î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.
==== Categorii de Procesări ====
Î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:- servicii care rulează în prim-plan;
- procesări realizate în fundal.
- identificatorul unei notificări, care trebuie să fie diferit de zero;
- un obiect de tip Notification, care va fi afișată cât timp serviciul se află în prim-plan, întrucât se presupune că serviciul trebuie să aibă o reprezentare vizuală cu care utilizatorul să poată interacționa.
Notification notification = new Notification(R.drawable.icon, "...", System.currentTimeMillis()); Intent intent = new Intent(this, SomeActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); notification.setLatestEventInfo(this, "...", "...", pendingIntent); 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ă.
onReceive() nu se termină în 5 secunde sunt considerate blocate.
- utilizarea clasei AsyncTask permite definirea unei operații care va fi realizată pe un fir de execuție separat, oferind metode care oferă informații cu privire la progres, ce pot fi consultate de alte fire de execuție;
- definirea unei clase derivate din Loader;
- utilizarea clasei Handler;
- implementarea unui fir de execuție definit de utilizator, transmiterea valorilor către interfața grafică fiind realizat prin intermediul clasei Handler.
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.
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.
- doInBackground() - metoda care va fi executată pe firul de execuție dedicat, care primește parametrii de intrare de tipul specificat; acesta trebuie să conțină procesările complexe care se doresc a fi realizate fără a interacționa însă cu firul de execuție principal; notificările sunt realizate:
- prin intermediul metodei publishProgress(), care primește ca parametru progresul realizat, valorile fiind transmise metodei onProgressUpdate();
- prin întoarcerea unui rezultat, atunci când procesarea este terminată, valoarea fiind transmisă metodei onPostExecute();
onProgressUpdate()- folosită pentru actualizarea interfeței grafice, cu valorile primite de la metodapublishProgress(); 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 metodadoInBackground();onPostExecute()- apelată în momentul în care procesarea este terminată, primind ca parametru rezultatul întors de metodadoInBackground(); 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.
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:- a încărca date asincron;
- a monitoriza sursele din care sunt încărcate datele, oferind informații cu privire la rezultatele obținute.
AsyncTaskLoader.
== Clasa Handler ==
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:- planificarea mesajelor spre a fi procesate la un moment fix din viitor (metodele de tip
send…():sendEmptyMessage(),sendMessage(),sendMessageAtTime(),sendMessageDelayed()); - încapsularea unei acțiuni care va fi executată pe un alt fir de execuție decât firul de execuție al interfeței grafice (metodele de tip
post…():post(),postAtTime(),postDelayed()).
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:
- folosind metoda Activity.runOnUiThread() care forțează codul transmis să fie executat pe același fir de execuție care redă interfața grafică:
runOnUiThread(new Runnable() { @Override public void run() { // ... } });
- utilizând un obiect de tip
Handlerpentru 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(); }
ClasaHandlerpune la dispoziție și metode pentru a executa anumite metode la un moment dat de timp:
- postDelayed() - realizează o modificare la nivelul interfeței grafice cu o întârziere specificată ca parametru (exprimat în milisecunde);
- postAtTime() - realizează o modificare la nivelul interfeței grafice la un moment de timp specificat ca parametru (exprimat ca număr de milisecunde ce au trecut de la 1 ianuarie 1970.
- RTC_WAKEUP - scoate dispozitivul mobil din starea de latență prin transmiterea unei intenții în așteptare, la un anumit moment de timp specificat;
- RTC - transmite o intenție în așteptare, la un anumit moment de timp specificat, fără a scoate dispozitivul mobil din starea de latență;
- ELAPSED_REALTIME - transmite o intenție în așteptare, la un anumit interval de timp scurs de la momentul în care dispozitivul mobil a fost pornit, fără a-l scoate din starea de latență;
- ELAPSED_REALTIME_WAKEUP - scoate dispozitivul mobil din starea de latență prin transmiterea unei intenții în așteptare, la un anumit interval de timp scurs de la momentul în care acesta a fost pornit.
AlarmManager reprezintă un moment de timp sau un interval.
În momentul în care se declanșează, obiectul de tip PendingIntent este distribuit la nivelul tuturor componentelor sistemului de operare.
PendingIntent o înlocuiește pe cea existentă.
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000, PendingIntent.getBroadcast(this, 0, new Intent("ALARM_ACTION"), 0));Anularea unei alarme se face prin intermediul metodei cancel() a obiectului de tip
AlarmManager care primește ca parametru intenția în așteptare care nu mai trebuie transmisă.
alarmManager.cancel(PendingIntent.getBroadcast(this, 0, new Intent("ALARM_ACTION"), 0));Utilizatorul are și posibilitatea de a indica anumite intervale de timp la care alarma este repetată, prin intermediul unor metode definite în clasa
AlarmManager:- setRepeating() - utilizată când se dorește un control foarte exact asupra intervalului de timp la care alarma este repetată, exprimat la nivel de milisecunde;
- setInexactRepeating() - folosit pentru a economisi consumul de baterie realizat la scoaterea dispozitivului mobil din starea de latență de fiecare dată când este necesară realizarea unor sarcini planificate (care nu se suprapun); astfel, sistemul de operare Android va sincroniza mai multe alarme cu modul de repetare inexact, declanșându-le simultan; metoda primește ca parametru una dintre constantele:
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, AlarmManager.INTERVAL_FIFTEEN_MINUTES, AlarmManager.INTERVAL_DAY, PendingIntent.getBroadcast(this, 0, new Intent("ALARM_ACTION"), 0));
AlarmManager care primește ca parametru intenția în așteptare care nu mai trebuie transmisă.
===== Activitate de Laborator =====
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/eim2016/Laborator05. În urma acestei operații, directorul Laborator05 va trebui să se conțină un director labtasks ce va deține proiectele AndroidStudio respectiv Eclipse, fișierul README.md și un fișier .gitignore care indică tipurile de fișiere (extensiile) ignorate.
student@eim2016:~$ git clone https://www.github.com/eim2016/Laborator05.git3. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator05' de pe contul Github personal.
student@eim2016:~$ cd Laborator05 student@eim2016:~/Laborator05$ git remote add Laborator05_perfectstudent https://github.com/perfectstudent/Laborator05 student@eim2016:~/Laborator05$ git push Laborator05_perfectstudent master4. Să se încarce în mediul integrat de dezvoltare preferat (Android Studio sau Eclipse) proiectele StartedService respectiv StartedServiceActivity din directorul
labtasks/<IDE>/StartedService.
- Proiectul StartedService conține codul sursă pentru un serviciu de tip started care transmite mai multe valori, de diferite tipuri (șir de caractere, întreg, vector), temporizate la un anumit interval (dată de valoarea
SLEEP_TIMEdin interfațaConstants). Aceste valori sunt transmise prin intermediul unor intenții cu difuzare (eng. broadcast intents), la nivelul întregului sistem de operare Android. - Proiectul StartedServiceActivity conține codul sursă pentru o aplicație Android care utilizează un ascultător pentru intenții cu difuzare (eng. BroadcastReceiver), pentru tipurile de mesaje propagate la nivelul sistemului de operare de către serviciu, pe care le afișează în interfața grafică, prin intermediul unui câmp text.
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:- acțiunea, care va avea valorile definite în interfața
Constants(Constants.ACTION_STRING,Constants.ACTION_INTEGER,Constants.ACTION_ARRAY_LIST); se va utiliza metoda setAction(); - informațiile transmise, plasate în câmpul
extra(având cheiaConstants.DATAși valoarea dată deConstants.STRING_DATA,Constants.INTEGER_DATA,Constants.ARRAY_LIST_DATA); se va utiliza metoda putExtra() care primește ca argumente cheia și 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ă?
<spoiler|Indicații de Rezolvare>
Se implementează o clasă derivată din Thread pentru care se va suprascrie metoda run(). Pe firul de execuție dedicat, se vor propaga intențiile cu difuzare la nivelul sistemului de operare Android, după care acesta își va încheia activitatea.
- ProcessingThread.java
package ro.pub.cs.systems.eim.lab05.startedservice.service; import android.content.Context; import android.content.Intent; import ro.pub.cs.systems.eim.lab05.startedservice.general.Constants; public class ProcessingThread extends Thread { private Context context; public ProcessingThread(Context context) { this.context = context; } @Override public void run() { sendMessage(Constants.MESSAGE_STRING); sleep(); // ... } private void sleep() { try { Thread.sleep(Constants.SLEEP_TIME); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } } private void sendMessage(int messageType) { Intent intent = new Intent(); switch(messageType) { case Constants.MESSAGE_STRING: intent.setAction(Constants.ACTION_STRING); intent.putExtra(Constants.DATA, Constants.STRING_DATA); break; // ... } context.sendBroadcast(intent); } }
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.
<spoiler|Indicații de Rezolvare>
În cadrul metodei onReceive(Context, Intent) din clasa StartedServiceBroadcastReceiver, se verifică:- acțiunea corespunzătoare intenției, folosind metoda getAction();
- informațiile transmise conținute în câmpul
extra, folosind metodele corespunzătoare tipului de date identificat pe baza intenției (getStringExtra(), getIntExtra(), getStringArrayListExtra()).
messageTextView), transmis ca argument la instanțierea ascultătorului pentru intenții cu difuzare.
- StartedServiceBroadcastReceiver.java
package ro.pub.cs.systems.eim.lab05.startedserviceclient.view; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.TextView; import ro.pub.cs.systems.eim.lab05.startedserviceclient.general.Constants; public class StartedServiceBroadcastReceiver extends BroadcastReceiver { private TextView messageTextView; public StartedServiceBroadcastReceiver(TextView messageTextView) { this.messageTextView = messageTextView; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); String data = null; if (Constants.ACTION_STRING.equals(action)) { data = intent.getStringExtra(Constants.DATA); } // ... if (messageTextView != null) { messageTextView.setText(messageTextView.getText().toString() + "\n" + data); } } }
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;
<spoiler|Indicații de Rezolvare>
startedServiceBroadcastReceiver = new StartedServiceBroadcastReceiver(messageTextView);</spoiler> 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(); <spoiler|Indicații de Rezolvare>
startedServiceIntentFilter = new IntentFilter(); startedServiceIntentFilter.addAction(Constants.ACTION_STRING); // ...</spoiler> 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()).
<spoiler|Indicații de Rezolvare>
Este necesar ca activarea ascultătorului să se realizeze pe metoda de callback onResume(), iar dezactivarea sa să fie realizată pe metoda de callback onPause().
@Override protected void onResume() { super.onResume(); registerReceiver(startedServiceBroadcastReceiver, startedServiceIntentFilter); } @Override protected void onPause() { unregisterReceiver(startedServiceBroadcastReceiver); super.onPause(); }</spoiler> d) 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).
<spoiler|Indicații de Rezolvare>
Intent intent = new Intent(); intent.setComponent(new ComponentName("ro.pub.cs.systems.eim.lab05.startedservice", "ro.pub.cs.systems.eim.lab05.startedservice.service.StartedService")); startService(intent);</spoiler> Similar, 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? 8. 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ă. 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. <spoiler|Indicații de Rezolvare> Pentru ca ascultătorul de intenții cu difuzare să poată procesa mesaje chiar și în situația în care activitatea nu este vizibilă pe suprafața de afișare, el trebuie declarat, împreună cu filtrul de intenții, în fișierul
AndroidManifest.xml:
- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest ...> <application ...> <receiver android:name=".view.StartedServiceBroadcastReceiver"> <intent-filter> <action android:name="ro.pub.cs.systems.eim.lab05.startedservice.string" /> </intent-filter> <!-- other intent filters for other activities --> </receiver> </application> </manifest>
onReceive(), prin intermediul unei intenții (datele fiind plasate în câmpul extra):
@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); String data = null; if (Constants.ACTION_STRING.equals(action)) { data = intent.getStringExtra(Constants.DATA); } // ... Intent startedServiceActivityIntent = new Intent(context, StartedServiceActivity.class); startedServiceActivityIntent.putExtra(Constants.MESSAGE, data); startedServiceActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(startedServiceActivityIntent); }Astfel, activitatea va fi invocată prin intermediul intenției, în condițiile în care aceasta este activă (se găsește în memorie, fiind plasată pe stiva de activități, fără a fi vizibilă). De aceea, aceasta va invoca metoda de callback onNewIntent() pe care trebuie realizată afișarea informațiilor transmise în câmpul
extra al intenției.
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); String message = intent.getStringExtra(Constants.MESSAGE); if (message != null) { messageTextView.setText(messageTextView.getText().toString() + "\n" + message); } }</spoiler> 9. Să se încarce în mediul integrat de dezvoltare preferat (Android Studio sau Eclipse) proiectul BoundedServiceActivity din directorul
labtasks/<IDE>/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().
<spoiler|Indicații de Rezolvare>
public class BoundedService extends Service { final private IBinder boundedServiceBinder = new BoundedServiceBinder(); public class BoundedServiceBinder extends Binder { public BoundedService getService() { return BoundedService.this; } } @Override public IBinder onBind(Intent intent) { return boundedServiceBinder; } }</spoiler> 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:- onServiceConnected() - se creează referința la serviciu prin intermediul argumentului de tipul
IBinder(prin intermediul metodeigetService()) și se actualizează variabila care reține starea legăturii dintre serviciu și activitate; - onServiceDisconnected() - se distruge referința la serviciu și se actualizează variabila care reține starea legăturii dintre serviciu și activitate.
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { BoundedService.BoundedServiceBinder binder = (BoundedService.BoundedServiceBinder)service; boundedService = binder.getService(); boundedServiceStatus = Constants.SERVICE_STATUS_BOUND; } @Override public void onServiceDisconnected(ComponentName className) { boundedService = null; boundedServiceStatus = Constants.SERVICE_STATUS_UNBOUND; } };</spoiler> 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.
- pe metoda
onStart()se apelează metoda bindService(), utilizându-se un obiect de tipIntentexplicit (care primește ca argument clasa care urmează să fie invocată); - pe metoda
onStop()se apelează metoda unbindService(); se actualizează variabila care reține starea legăturii dintre serviciu și activitate.
@Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, BoundedService.class); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (boundedServiceStatus == Constants.SERVICE_STATUS_BOUND) { unbindService(serviceConnection); boundedServiceStatus = Constants.SERVICE_STATUS_UNBOUND; } }</spoiler> 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.
<spoiler|Indicații de Rezolvare>
private class GetMessageFromServiceButtonListener implements View.OnClickListener { @Override public void onClick(View view) { if (boundedService != null && boundedServiceStatus == Constants.SERVICE_STATUS_BOUND) { messageFromServiceTextView.setText("[" + new Timestamp(System.currentTimeMillis()) + "] " + boundedService.getMessage() + "\n" + messageFromServiceTextView.getText().toString()); } } }</spoiler> 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? 10. Să se încarce modificările realizate în cadrul depozitului 'Laborator05' de pe contul Github personal, folosind un mesaj sugestiv.
student@eim2016:~/Laborator05$ git add labtasks/* student@eim2016:~/Laborator05$ git commit -m "implemented taks for laboratory 05" student@eim2016:~/Laborator05$ git push Laborator05_perfectstudent master===== Resurse Utile ===== 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)
