Table of Contents

Laborator 04. Structura unei Aplicații (II)

Intenții (obligatoriu)

Conceptul de intenție în Android este destul de complex (și unic), putând fi definit ca o acțiune având asociată o serie de informații, transmisă sistemului de operare Android pentru a fi executată sub forma unui mesaj asincron. În acest fel, intenția asigură interacțiunea între toate aplicațiile instalate pe dispozitivul mobil, chiar dacă fiecare în parte are o existență autonomă. Din această perspectivă, sistemul de operare Android poate fi privit ca o colecție de componente funcționale, independente și interconectate.

De regulă, o intenție poate fi utilizată pentru:

În sistemul de operare Android, evenimente de tipul primirea unui apel telefonic / mesaj, modificarea nivelului de încărcare al bateriei, schimbarea stării legate de conectivitatea la Internet sunt transmise prin intermediul unor intenții prelucrate de aplicațiile specifice. Un utilizator poate înlocui astfel aplicațiile native cu propriile sale aplicații pentru a realiza operațiile specifice acestor tipuri de evenimente.

O intenție reprezintă o instanță a clasei android.content.Intent. Aceasta este transmisă ca parametru unor metode (de tipul startActivity() sau startService(), definite în clasa abstractă android.content.Context), pentru a invoca anumite componente (activități sau servicii). Un astfel de obiect poate încapsula anumite date (împachetate sub forma unui android.os.Bundle), care pot fi utilizate de componenta ce se dorește a fi executată prin intermediul intenției.

În programarea Android, un principiu de bază este de folosi intenții pentru a propaga acțiuni, chiar și în cadrul aceleiași aplicații, în detrimentul încărcării clasei corespunzătoare. În acest fel, se asigură cuplarea slabă a componentelor, oferind flexibilitate în cazul înlocuirii acestora, permițând totodată extinderea funcționalității cu ușurință.

Structura unei Intenții

În structura unei intenții pot fi identificate mai multe elemente, precizate în cadrul secțiunii <intent-filter>, prin intermediul cărora se declară faptul că o componentă a unei aplicații poate realiza o anumită acțiune pentru un anumit set de date (sau pe care un ascultător al unei intenții cu difuzare îl poate procesa):

Între o acțiune și componenta pe care aceasta o invocă nu există o relație de tip 1:1, întrucât o acțiune poate determina execuția unor componente diferite, în funcție de tipul de date care sunt atașate acesteia. Astfel, fiecare activitate va defini ca atribute ale alementului data din <intent-filter> informații legate de categoria MIME ale datelor procesate (mimeType), de locația la care se găsesc (path, host, port), de schema utilizată (scheme).

În situația în care se specifică un nume de componentă, intenția se numește explicită, iar în situația în care aceasta este determinată în funcție de acțiune și de date, intenția se numește implicită.

Dintre aceste componente, esențiale sunt acțiunea și datele:

După ce a fost construit obiectul de tip Intent prin specificarea acestor parametri, acțiunea poate fi executată prin transmiterea acestuia ca parametru al metodei startActivity() sau startService(), disponibile în clasa android.content.Context.

startActivity(intent);
startService(intent);

Terminarea unei activități (moment în care se realizează revenirea la activitatea părinte) este realizată prin apelul metodei finish().

Controlul fluxului de activități prin intenții

Intenții construite prin precizarea clasei încărcate

În fișierul AndroidManifest.xml, orice activitate definește în cadrul elementului <intent-filter>, denumirea unei acțiuni care va putea fi folosită de o intenție pentru a o invoca.

<activity
  android:name="ro.pub.cs.systems.eim.lab04.MainActivity"
  android:label="@string/app_name" >
  <intent-filter>
    <action android:name="ro.pub.cs.systems.eim.lab04.intent.action.MainActivity" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

Se obișnuiește ca denumirea unei acțiuni să respecte convenția
<pachet-aplicație>.intent.action.ACȚIUNE.

Pentru fiecare activitate trebuie să existe un element de tip <activity> în fișierul AndroidManifest.xml.

Pentru ca o activitate să poată fi invocată, aceasta trebuie să specifice la elementul category din <intent-filter> valoarea android.intent.category.DEFAULT.

Pentru ca o funcționalitatea expusă de o activitate să poată fi invocată (în mod anonim) și din contextul altor componente ale sistemului de operare Android, pentru tipul de acțiune și pentru tipurile de date precizate, în cadrul secțiunii <intent-filter> se poate specifica atributul android:label (șir de caractere care conține o descriere a funcționalității implementate), indicându-se ca tip de categorie valorile ALTERNATIVE, SELECTED_ALTERNATIVE sau ambele.

O activitate este în principiu invocată de o intenție care poate fi identificată prin apelul metodei getIntent(). Rezultatul acestei metode poate fi inclusiv null, în cazul în care activitatea nu a fost pornită prin intermediul unei intenții.

Prin intermediul unei intenții, o aplicație poate invoca atât o activitate din cadrul său, cât și o activitate aparținând altei aplicații.

De remarcat faptul că în situația în care este pornită o activitate din cadrul aceleiași aplicații Android, obiectul de tip Intent primește ca parametru și contextul curent (this), în timp ce în cazul în care este lansată în execuție o activitate din cadrul altei aplicații Android acest parametru este omis.

În momentul în care este invocată metoda startActivity(), activitatea respectivă este lansată în execuție (prin apelul metodelor onCreate(), onStart(), onResume()) și plasată în vârful stivei care conține toate componentele care au rulate anterior, fără a fi fost terminate. În momentul în care se apelează metoda finish() (sau se apasă butonul Back), activitatea este încheiată (prin apelul metodelor onPause(), onStop(), onDestroy()), fiind scoasă din stivă, restaurându-se activitatea anterioară.

Intenții construite prin precizarea acțiunii

O intenție poate fi definită și prin intermediul unei acțiuni care se dorește a fi realizată, pentru care pot fi atașate opțional și anumite date. Utilizatorul care folosește un astfel de mecanism nu cunoaște activitatea (sau aplicația Android) care va fi lansată în execuție pentru realizarea acțiunii respective. Pentru a putea îndeplini o astfel de solicitare, sistemul de operare Android trebuie să identifice, la momentul rulării, activitatea care este cea mai adecvată pentru a rezolva acțiunea dorită. În acest fel, pot fi utilizate funcționalități deja implementate în cadrul sistemului de operare Android, fără a cunoaște în prealabil aplicația responsabilă de aceasta.

În cazul în care există mai multe activități care au specificat la elementul action din intent-filter aceeași valoare care este transmisă ca parametru constructorului clasei Intent, la execuția intenției în cauză utilizatorului i se va prezenta o listă de opțiuni dintre care poate alege. Dacă la realizarea selecției va fi precizată și opțiunea Use by default for this action, preferințele vor fi salvate astfel încât în continuare vor fi utilizate fără a se mai solicita intervenția utilizatorului în acest sens.

Procesul de rezolvare a unei intenții (eng. intent resolution) se face prin intermediul analizei tuturor ascultătorilor înregistrați.

Cele mai frecvent utilizate acțiuni implicite ale unui obiect de tip Intent sunt:

Pentru a fi posibil ca aplicația să realizeze un apel telefonic, în fișierul AndroidManifest.xml trebuie specificată explicit permisiunea pentru o astfel de acțiune
<uses-permission android:name=“android.permission.CALL_PHONE”>.

Alte acțiuni implicite utilizate sunt:

Totuși, un utilizator nu poate avea garanția că acțiunea pe care o transmite ca parametru al unei intenții va putea fi rezolvată, întrucât există posibilitatea să nu existe nici o activitate asociată acesteia sau ca aplicația ce ar fi putut să o execute să nu fie instalată în contextul sistemului de operare Android. Din acest motiv, o practică curentă este de a verifica dacă o acțiune poate fi rezolvată înainte de a apela metoda startActivity(). Astfel, procesul de gestiune a pachetelor poate fi interogat (prin intermediul metodei resolveActivity()) dacă există o activitate ce poate executa o acțiune și în caz afirmativ, care este aceasta.

Intent applicationIntent = new Intent(...);
PackageManager packageManager = new PackageManager();
ComponentName componentName = applicationIntent.resolveActivity(packageManager);
if (componentName == null) {
  Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pname:..."));
  if (marketIntent.resolveActivity(packageManager) != null) {
    startActivity(marketIntent);
  } else {
    Toast.makeText(getApplicationContext(), "Google Play Store is not available", Toast.LENGTH_LONG).show();
  }
} else {
  startActivity(intent);
}

În situația în care nu este identificată nici o activitate asociată acțiunii respective, utilizatorul poate dezactiva componenta grafică asociată până în momentul în care aceasta devine disponibilă, prin descărcarea aplicației Android corespunzătoare din Google Play Store.

Prin intermediul clasei PackageManager poate fi obținută lista tuturor acțiunilor care pot fi realizate pentru un set de date, atașat unei intenții, invocându-se metoda queryIntentActivities():

Intent applicationIntent = new Intent();
intent.setData(...);
intent.addCategory(Intent.CATEGORY_SELECTED_ALTERNATIVE);
 
PackageManager packageManager = new PackageManager();
int flags = PackageManager.MATCH_DEFAULT_ONLY;
 
List<ResolveInfo> availableActions = packageManager.queryIntentActivities(applicationIntent, flags);
for (ResolveInfo availableAction: availableActions) {
  Log.d(Constants.TAG, "An available action for the data is "+getResources().getString(availableAction.labelRes));
}

Procesul de rezolvare a unei intenții pe baza unei acțiuni implică următoarele etape:

  1. se construiește o listă cu toate filtrele de intenții asociate componentelor din aplicațiile Android existente;
  2. sunt eliminate toate filtrele de intenții care nu corespund acțiunii sau categoriei intenției care se dorește a fi rezolvată:
    1. verificările în privința acțiunii sunt realizate numai în situația în care filtrul de intenție specifică o acțiune; sunt eliminate acele filtre de intenții pentru care nici una dintre acțiunile pe care le include nu corespund acțiunii intenției care se dorește a fi rezolvată;
    2. verificările în privința categorie sunt realizate numai în situația în care filtrul de intenție specifică o categorie sau în cazul în care nu specifică nici o categorie, dacă nici intenția care se dorește a fi rezolvată nu include nici o categorie; sunt eliminate acele filtre de intenții care nu includ toate categoriile pe care le conține și intenția care se dorește a fi rezolvată (putând conține totuși și categorii suplimentare);
  3. fiecare parte a URI-ului datelor corespunzătoare intenției care se dorește a fi rezolvată este comparată cu secțiunea data din filtrul de intenție; gazda, autoritatea, tipul MIME, calea, portul, schema), orice neconcordanță conducând la eliminarea acestuia din listă (în situația în care filtrul de intenții nu specifică proprietăți în secțiunea data, acesta va fi considerat compatibil cu intenția care se dorește a fi rezolvată;
  4. în situația în care, ca urmare a acestui proces, există mai multe componente rămase în listă, utilizatorul va trebui să aleagă dintre toate aceste posibilități.

Aplicațiile Android native sunt supuse aceluiași proces în momentul în care se realizează rezolvarea unei intenții ca și aplicațiile Android instalate din alte surse, având aceeiași prioritate și putând fi chiar înlocuite de acestea, dacă definesc filtre de intenții cu aceleași acțiuni / categorii.

În cazul în care o componentă a unei activități este lansată în execuție prin intermediul unei intenții, aceasta trebuie să identifice acțiunea pe care trebuie să o realizeze și datele pe care trebuie sp le proceseze. În acest sens, clasa Intent pune la dispoziție metodele getAction(), getData() și getExtras().

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  setContentView(R.layout.activity_main);
  Intent intent = getIntent();
  if (intent != null) {
    String action = intent.getAction();
    Uri data = intent.getData();
    Bundle extras = intent.getExtras();
  }
}

În anumite situații, o componentă poate primi și alte intenții după ce a fost creată. De fiecare dată, va fi apelată în mod automat metoda onNewIntent():

@Override
public void onNewIntent(Intent newIntent) {
  super.onNewIntent(newIntent);
  // ...
}

O componentă are de asemenea posibilitatea de a transfera responsabilitatea cu privire la gestiunea unei intenții către altă componentă care corespunde criteriilor legate de acțiune și categorie, prin intermediul metodei startNextMatchingActivity():

Intent intent = getIntent();
if (intent != null) {
  startNextMatchingActivity(intent);
}

În acest mod, o componentă poate indica condiții suplimentare cu privire la tratarea unei anumite acțiuni, în situația în care acestea nu pot fi exprimate în cadrul filtrului de intenții, pentru a putea fi luate în considerare în cadrul procesului automat de identificare a componentei care deservește o intenție.

Intenții construite prin intermediul unui URI (Optional)

Click to display ⇲

Click to hide ⇱

De asemenea, un obiect de tip Intent poate fi creat și prin intermediul unui URI care identifică în mod unic o anumită activitate:

Uri uri = Uri.parse("myprotocol://mynamespace/myactivity");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra("someKey", someValue);
startActivity(intent);

Pentru a putea fi apelată folosind acest mecanism, activitatea va trebui să definească elementul data în cadrul <intent-filter>:

<activity
  android:name="ro.pub.cs.systems.eim.lab04.AnotherActivity"
  android:label="@string/app_name" >
  <intent-filter>
    <action android:name="ro.pub.cs.systems.eim.lab04.intent.action.AnotherActivity" />
    <category android:name="android.intent.category.DEFAULT" />
    <data
      android:scheme="myprotocol"
      android:host="mynamespace" />
  </intent-filter>
</activity>

De remarcat este faptul că în structura URI-ului, partea de după schemă://protocol/ poate conține orice șir de caractere, rolul său fiind strict acela de a respecta forma unui astfel de obiect (estetic), fără a influența în vreo formă funcționalitatea acestuia.

Transmiterea de informații între componente prin intermediul intențiilor

Intențiile pot încapsula anumite informații care pot fi partajate de componentele între care fac legătura (însă unidirecțional, de la componenta care invocă spre componenta invocată!) prin intermediul secțiunii extra care conține un obiect de tip Bundle. Obținerea valorii secțiunii extra corespunzătoare unei intenții poate fi obținute folosind metoda getExtras(), în timp ce specificarea unor informații care vor fi asociate unei intenții poate fi realizată printr-un apel al metodei putExtras().

În cazul în care o intenție are deja atașat un obiect de tip Bundle în momentul în care se apelează metoda putExtras(), perechile (cheie, valoare) vor fi transferate din cadrul parametrului metodei în obiectul deja existent.

Construirea unui obiect de tip Bundle care să fie transmis ca parametru al metodei putExtras() poate fi evitată prin utilizarea metodei putExtra() apelată pe obiectul Intent, primind ca parametrii denumirea cheii prin care datele vor fi identificate și o valoare având un tip compatibil cu android.os.Parcelable. Obținerea datelor se realizează apelând metoda pereche getExtra() căreia i se transmite denumirea cheii ce identifică în mod unic informațiile respective. De asemenea, sunt implementate și metode specifice pentru fiecare tip de dată (put<type>Extra(), respectiv get<type>Extra()).

Se recomandă ca pentru cheie să se utilizeze o denumire prefixată de pachetul aplicației.

O activitate copil, lansată în execuție prin intermediul metodei startActivity(), este independentă de activitatea părinte, astfel încât aceasta nu va fi notificată cu privire la terminarea sa. În situațiile în care un astfel de comportament este necesar, activitatea copil va fi pornită de activitatea părinte ca subactivitate care transmite înapoi un rezultat. Acest lucru se realizează prin lansarea în execuție a activității copil prin intermediul metodei startActivityForResult(). În momentul în care este finalizată, va fi invocată automat metoda onActivityResult() de la nivelul activității părinte.

La nivelul activității părinte, vor trebui implementate:

final private static int ANOTHER_ACTIVITY_REQUEST_CODE = 2017;
 
@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  setContentView(R.layout.activity_main);
  Intent intent = new Intent("ro.pub.cs.systems.eim.lab04.AnotherActivity");
  intent.putExtra("ro.pub.cs.systems.eim.eim.someKey", someValue);
  startActivityForResult(intent, ANOTHER_ACTIVITY_REQUEST_CODE);
  // start another activities with their own request codes
}
 
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
  switch(requestCode) {
    case ANOTHER_ACTIVITY_REQUEST_CODE:
      if (resultCode == Activity.RESULT_OK) {
        Bundle data = intent.getExtras();
        // process information from data ...
      }
      break;
 
      // process other request codes
  }
}

În activitatea copil, înainte de apelul metodei finish(), va trebui transmis activității părinte codul de rezultat (Activity.RESULT_OK, Activity.RESULT_CANCELED sau orice fel de rezultat de tip întreg) și obiectul de tip intenție care conține datele (opțional, în situația în care trebuie întoarse rezultate explicit), ca parametrii ai metodei setResult().

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  setContentView(R.layout.activity_another);
 
  // intent from parent
  Intent intentFromParent = getIntent();
  Bundle data = intentFromParent.getExtras();
  // process information from data ...
 
  // intent to parent
  Intent intentToParent = new Intent();
  intent.putExtra("ro.pub.cs.systems.eim.lab04.anotherKey", anotherValue);
  setResult(RESULT_OK, intentToParent);
  finish();
}

În cazul folosirii unor intenții în care activitățile sunt invocate prin intermediul unor URI-uri, datele vor putea fi concatenate direct în cadrul acestuia (fără a utiliza un obiect de tip Bundle), restricția constând în faptul că pot fi utilizate numai șiruri de caractere:

Gestiunea evenimentelor cu difuzare prin intermediul intențiilor (Optional)

Click to display ⇲

Click to hide ⇱

Intențiile reprezintă și un mecanism de comunicație inter-proces, asigurând transferul unor mesaje structurate. Astfel, intențiile pot fi distribuite către toate componentele de la nivelul sistemului de operare Android, pentru a notifica producerea unui eveniment (legat de starea dispozitivului mobil sau a unor aplicații), fiind procesate în cadrul unor obiecte ascultător dedicate tipului de mesaj respectiv.

Și sistemul de operare Android folosește acest mecanism pentru a notifica producerea unor modificări la nivelul stării curente (primirea unui apel telefonic / mesaj, schimbarea nivelului de încărcare al bateriei sau a conectivității).

Trebuie realizată distincția între intențiile cu difuzare transmise la nivelul întregului sistem de operare Android și a celor transmise doar la nivelul aplicației, prin intermediul unui obiect de tipul LocalBroadcastManager (a cărui instanță se obține prin intermediul metodei statice getInstance() ce primește ca parametru contextul aplicației curente). Acesta operează într-un mod similar, implementând metodele sendBroadcast() și registerReceiver(). În plus, dispune de o metodă ce permite trimiterea sincronă a notificărilor, apelul acesteia fiind blocant până la momentul în care toți ascultătorii le-au primit.

Pentru o aplicație Android, în momentul rulării, pot fi activate / dezactivate oricare dintre componente (deci inclusiv ascultătorii pentru intențiile cu difuzare) prin intermediul metodei setComponentEnabledSetting() din cadrul clasei PackageManager. Un astfel de comportament este util pentru a optimiza performanțele aplicației atunci când o anumită funcționalitate nu este necesară.

PackageManager packageManager = getPackageManager();
 
ComponentName someEventBroadcastReceiver = new ComponentName(this, SomeEventBroadcastReceiver.class);
 
packageManager.setComponentEnabledSetting(someEventBroadcastReceiver ,
  PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
  PackageManager.DONT_KILL_APP);
 
packageManager.setComponentEnabledSetting(someEventBroadcastReceiver ,
  PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
  PackageManager.DONT_KILL_APP);

Trimiterea unei intenții cu difuzare

Click to display ⇲

Click to hide ⇱

Construirea unei intenții care urmează să fie difuzată la nivelul sistemului de operare Android poate fi realizată prin definirea unui obiect de tipul Intent, pentru care se vor specifica acțiunea, datele și categoria, astfel încât obiectele de tip ascultător să îl poată identifica cât mai exact. Ulterior, acesta va fi trimis tuturor proceselor aferente aplicațiilor instalate pe dispozitivul mobil prin intermediul metodei sendBroadcast(), căreia îi este atașat ca parametru.

Pot fi utilizate atât acțiuni predefinite (care vor fi procesate atât de aplicațiile Android native cât și de eventuale aplicații instalate din alte surse) cât și acțiuni definite de utilizator, pentru care trebuie implementate aplicații dedicate, responsabile cu procesarea acestora.

final public static String SOME_ACTION = "ro.pub.cs.systems.eim.lab04.SomeAction.SOME_ACTION";
 
Intent intent = new Intent(SOME_ACTION);
intent.putExtra("ro.pub.cs.systems.eim.lab04.someKey", someValue);
sendBroadcast(intent);

Primirea unui intenții cu difuzare

Click to display ⇲

Click to hide ⇱

Pentru a putea primi o intenție cu difuzare, o componentă trebuie să fie înregistrată în acest sens, definind un filtru de intenții pentru a specifica ce tipuri de acțiuni și ce tipuri de date asociate intenției poate procesa.

Acesta poate fi precizat:

  • în fișierul AndroidManifest.xml (caz în care nu este necesar ca aplicația să ruleze la momentul în care se produce evenimentul cu difuzare pentru a-l putea procesa); elementul <receiver> trebuie să conțină în mod obligatoriu filtrul de intenții prin care se indică acțiunea care poate fi procesată:
    AndroidManifest.xml
    <manifest ... >
      <application ... >
        <receiver
          android:name=".SomeEventBroadcastReceiver">
          <intent-filter>
            <action android:name="ro.pub.cs.systems.eim.lab04.SomeAction.SOME_ACTION" />
          </intent-filter> 
        </receiver>
      </application>
    </manifest>
  • programatic, în codul sursă (caz în care aplicația trebuie să fie în execuție la momentul în care se produce evenimentul cu difuzare pentru a-l putea procesa); o astfel de abordare este utilă când procesarea intenției cu difuzare implică actualizarea unor componente din cadrul interfeței grafice asociate activității:
    private SomeEventBroadcastReceiver someEventBroadcastReceiver = new SomeEventBroadcastReceiver();
    private IntentFilter intentFilter = new IntentFilter(SOME_ACTION);
     
    @Override
    protected void onResume() {
      super.onResume();
     
      registerReceiver(someEventBroadcastReceiver, intentFilter);
    }
     
    @Override
    protected void onPause() {
      super.onPause();
     
      unregisterReceiver(someEventBroadcastReceiver);
    }

O regulă este de a înregistra obiectul ascultător pe metoda onResume() și de a-l deînregistra pe metoda onPause(), astfel încât acesta să nu reacționeze decât atunci când activitatea este vizibilă.

O clasă capabilă să proceseze intenții cu difuzare este derivată din android.content.BroadcastReceiver, implementând metoda onReceive() pe care realizează rutina de tratare propriu-zisă:

SomeEventBroadcastReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
 
public class SomeEventBroadcastReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // ...
  }
}

Metoda onReceive() va fi invocată în mod automat în momentul în care este primită o intenție cu difuzare, fiind executată pe firul de execuție principal al aplicației. De regulă, în cadrul acestei metode utilizatorul este anunțat asupra producerii evenimentului prin intermediul serviciului de notificare (Notification Manager), este lansat în execuție un serviciu sau sunt actualizate componente din cadrul interfeței grafice.

Este necesar ca metoda onReceive() să se termine în maximum 5 secunde, în caz contrar fiind afișată o fereastră de dialog pentru a determina oprirea sa forțată.

Tipuri particulare de intenții cu difuzare

Click to display ⇲

Click to hide ⇱

Există și tipuri particulare de intenții cu difuzare:

  1. intenții cu difuzare ordonate, utile în situația în care o intenție cu difuzare trebuie să fie procesată secvențial de mai mulți ascultători, fiecare dintre aceștia având posibilitatea de a modifica intenția respectivă;
  2. intenții cu difuzare persistente, care mențin valoarea care a fost transmisă cel mai recent.
Intenții cu difuzare ordonate

Click to display ⇲

Click to hide ⇱

Pentru ca o intenție cu difuzare să poate fi procesată de mai mulți ascultători într-o anumită ordine, ar trebui să fie transmisă prin intermediul metodei sendOrderedBroadcast() căreia i se poate atașa (opțional) și o anumită permisiune, pe care clasa ascultător trebuie să o dețină:

final public static String SOME_ORDERED_ACTION = "ro.pub.cs.systems.eim.lab04.SomeOrderedAction.SOME_ORDERED_ACTION";
 
Intent intent = new Intent(SOME_ORDERED_ACTION);
intent.putExtra("ro.pub.cs.systems.eim.lab04.someKey", someValue);
sendOrderedBroadcast(intent, "ro.pub.cs.systems.eim.lab04.SOME_PERMISSION");

Ordinea în care va fi procesată intenția de clasele ascultător înregistrate în acest sens este dată de prioritatea pe care acestea o precizează în filtrul de intenții, convenția fiind aceea că aceasta este direct proporțională cu valoarea (ascultătorii cu prioritate mai mare vor procesa intenția cu difuzare înaintea ascultătorilor cu prioritate mai mică):

AndroidManifest.xml
<manifest ... >
  <application ... >
    <receiver
      android:name=".SomeEventOrderedBroadcastReceiver"
      android:permission="ro.pub.cs.systems.eim.lab04.SOME_PERMISSION">
      <intent-filter
        android:permission="100">
        <action android:name="ro.pub.cs.systems.eim.lab04.SomeOrderedAction.SOME_ORDERED_ACTION" />
      </intent-filter> 
    </receiver>
  </application>
</manifest>

Frecvent, intențiile cu difuzare ordonate sunt folosite atunci când se dorește transmiterea unor rezultate înapoi, către aplicația Android care le-a transmis, după ce toți ascultătorii au realizat procesarea acestora. În acest sens, metoda sendOrderedBroadcast() va primi suplimentar ca parametrii și obiectul de tip BroadcastReceiver care va procesa ultimul intenția cu difuzare, un obiect de tip Handler care va primi rezultatul final (se transmite valoarea null dacă se dorește ca acesta să fie aplicația care a transmis intenția), rezultatul, secțiunile data / extra ale intenției pe care se poate opera.

Intenții cu difuzare persistente

Click to display ⇲

Click to hide ⇱

Pentru ca o intenție cu difuzare să își mențină cea mai recentă valoare, ar trebui să fie transmisă prin intermediul metodei sendStickyBroadcast().

final public static String SOME_STICKY_ACTION = "ro.pub.cs.systems.eim.lab04.SomeStickyAction.SOME_STICKY_ACTION";
 
Intent intent = new Intent(SOME_STICKY_ACTION);
intent.putExtra("ro.pub.cs.systems.eim.lab04.someKey", someValue);
sendStickyBroadcast(intent);

Pentru a fi posibil ca aplicația să transmită o intenție cu difuzare persistentă, în fișierul AndroidManifest.xml trebuie specificată explicit permisiunea pentru o astfel de acțiune
<uses-permission android:name=“android.permission.BROADCAST_STICKY”>.

În momentul în care menținerea stării nu mai este necesară, se poate apela metoda removeStickyBroadcast().

Obținerea celei mai recente valori a intenției cu difuzare poate fi obținută chiar fără a preciza un obiect ascultător, doar prin indicarea filtrului de intenții:

IntentFilter intentFilter = new IntentFilter(SOME_STICKY_ACTION);
Intent intent = registerReceiver(null, intentFilter);

În Android, astfel de intenții sunt utilizate de sistemul de operare pentru a indica starea dispozitivului mobil (nivelul de încărcare al bateriei, conectivitatea la Internet) pentru a îmbunătăți eficiența.

Gestiunea intențiilor cu difuzare native

Click to display ⇲

Click to hide ⇱

Cele mai multe servicii de sistem transmit intenții cu difuzare pentru a semnala faptul că s-au produs anumite modificări la nivelul stării dispozitivului mobil sau al aplicațiilor (primirea unui apel telefonic / mesaj, schimbarea nivelului de încărcare al bateriei, conectivitatea la Internet).

ACȚIUNE DESCRIERE
ACTION_BATTERY_CHANGED acțiune transmisă în momentul în care se modifică nivelul de încărcare al bateriei; starea bateriei este disponibilă în secțiunea extra, prin intermediul cheii EXTRA_STATUS, putând avea valorile:
BatteryManager.BATTERY_STATUS_CHARGING
BatteryManager.BATTERY_STATUS_FULL
ACTION_BATTERY_LOW acțiune transmisă în momentul în care nivelul de încărcare al bateriei este scăzut, impunându-se încărcarea acesteia
ACTION_BATTERY_OKAY acțiune transmisă în momentul în care nivelul de încărcare al bateriei este acceptabil
ACTION_BATTERY_CONNECTED acțiune transmisă în momentul în care bateria este conectată la o sursă de energie externă
ACTION_BATTERY_DISCONNECTED acțiune transmisă în momentul în care bateria este deconectată de la o sursă de energie externă
ACTION_BOOT_COMPLETED acțiune transmisă în momentul în care a fost realizată complet secvența de pornire a dispozitivului mobil (aplicația poate primi o astfel de intenție cu difuzare dacă deține permisiunea RECEIVE_BOOT_COMPLETED)
ACTION_CAMERA_BUTTON acțiune transmisă în momentul în momentul în care este accesat butonul pentru pornirea camerei foto
ACTION_DATE_CHANGED / ACTION_TIME_CHANGED acțiuni transmise în momentul în care data calendaristică sau timpul sunt modificate manual (nu datorită progresului său natural)
ACTION_DOCK_EVENT acțiune transmisă în momentul în care dispozitivul mobil este ancorat, printr-un dispozitiv de birou sau de mașină, stare plasată în secțiunea extra prin intermediul cheii ETRA_DOCK_STATE
ACTION_MEDIA_EJECT acțiune transmisă în momentul în care este îndepărtat un mediu de stocare extern (util în situația în care aplicația scrie / citește de pe acesta, pentru a salva conținutul și pentru a le închide)
ACTION_MEDIA_MOUNTED / ACTION_MEDIA_UNMOUNTED acțiuni transmise de fiecare dată când dispozitive de stocare externe sunt adăugate sau îndepărtate cu succes
ACTION_NEW_OUTGOING_CALL acțiune transmisă în momentul în care urmează să fie format un număr de telefon, a cărui valoare este plasată în secțiunea extra, prin intermediul cheii EXTRA_PHONE_NUMBER (aplicația poate primi o astfel de intenție cu difuzare dacă deține permisiunea PROCESS_OUTGOING_CALLS
ACTION_SCREEN_OFF / ACTION_SCREEN_ON acțiuni transmise în momentul în care ecranul este închis, respectiv este deschis
ACTION_TIMEZONE_CHANGED acțiune transmisă în momentul în care zona de timp a telefonului este modificată, a cărui valoare (identificator) este plasată în secțiunea extra prin intermediul cheii time-zone

Pentru aceste tipuri de intenții cu difuzare, înregistrarea și deînregistrarea unor obiecte de tip ascultător poate fi realizată numai programatic, în codul sursă.

În cazul unei aplicații Android, foarte importante sunt și modificările în privința conectivității la Internet (inclusiv parametrii precum lățimea de bandă, latența) întrucât acestea pot fi semnificative în privința luării unor decizii legate de realizarea anumitor actualizări sau de descărcarea unor fișiere având dimensiuni mari. O astfel de funcționalitate poate fi definită prin implementarea unui obiect ascultător, care procesează acțiunea android.net.conn.CONNECTIVITY_CHANGE (ConnectivityManager.CONNECTIVITY_ACTION). Se transmise o intenție cu difuzie nepersistentă care nu conține informații suplimentare cu privire la schimbarea stării.

ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
boolean isConnected = networkInfo.isConnectedOrConnecting();
boolean isMobile = (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE);

</spoiler>

Activitate de Laborator

Se dorește implementarea unei aplicații Android, conținând o activitate care să ofere utilizatorilor funcționalitatea necesară pentru a stoca un număr de telefon în agenda de contacte, specificând pentru acesta mai multe informații.

1. În contul Github personal, să se creeze un depozit denumit 'Laborator04'. Acesta trebuie să conțină unui fișier README.md, un fișier .gitignore specific unei aplicații Android și un fișier LICENSE care să descrie condițiile pentru utilizarea aplicației.

2. Să se cloneze într-un director de pe discul local conținutul depozitului la distanță astfel creat. În urma acestei operații, directorul Laborator04 va trebui să se conțină fișierele README.md, .gitignore care indică tipurile de fișiere (extensiile) ignorate și LICENSE.

student@eim-lab:~$ git clone https://www.github.com/perfectstudent/Laborator04.git

3. În directorul Laborator04 de pe discul local, să se creeze un proiect Android Studio denumit ContactsManager (se selectează Start a new Android Studio project).

Se indică detaliile proiectului:

Se indică platforma pentru care se dezvoltă aplicația Android (se bifează doar Phone and Tablet), iar SDK-ul Android (minim) pentru care se garantează funcționarea este API 24 (Nougat).

Se creează o activitate care inițial nu va conține nimic (Empty Activity):

pentru care se precizează:

De asemenea:

4. În fișierul activity_contacts_manager din directorul res/layout să se construiască interfața grafică folosind:

Acesta va fi format din două containere după cum urmează:

Fiecare container poate fi inclus într-un mecanism de dispunere a conținutului de tip LinearLayout cu dispunere verticală. Acestea vor fi incluse într-un container de tip ScrollView, pentru a preîntâmpina situația în care interfața grafică nu poate fi afișată pe ecranul dispozitivului mobil.

Să se implementeaze interacțiunea cu utilizatorul a aplicației.

5. Să se modifice aplicația Android Phone Dialer astfel încât să conțină un buton suplimentar prin care este invocată aplicația Contacts Manager căreia îi transmite numărul de telefon format și așteptând un rezultat cu privire la stocarea contactului în agenda telefonică.

Ca imagine pentru butonul care invocă aplicația Contacts Manager se poate folosi această resursă.

Metoda de tratare a evenimentului de tip accesare a butonului de stocare a numărului de telefon în agenda telefonică invocă o intenție asociată aplicației Contacts Manager, transmițând și numărul de telefon în câmpul extra asociat acesteia, identificabil prin intermediul unei chei.

String phoneNumber = phoneNumberEditText.getText().toString();
if (phoneNumber.length() > 0) {
  Intent intent = new Intent("ro.pub.cs.systems.eim.lab04.contactsmanager.intent.action.ContactsManagerActivity");
  intent.putExtra("ro.pub.cs.systems.eim.lab04.contactsmanager.PHONE_NUMBER_KEY", phoneNumber);
  startActivityForResult(intent, Constants.CONTACTS_MANAGER_REQUEST_CODE);
} else {
  Toast.makeText(getApplication(), getResources().getString(R.string.phone_error), Toast.LENGTH_LONG).show();
}

Definiți în prealabil constanta CONTACTS_MANAGER_REQUEST_CODE și valoarea string phone_error în fișierele corespunzătoare din folderul de resurse statice res.

6. Să se modifice aplicația Android Contacts Manager astfel încât să poată fi lansată în execuție doar din contextul altei activități, prin intermediul unei intenții care conține în câmpul extra un număr de telefon, identificabil prin cheia ro.pub.cs.systems.eim.lab04.contactsmanager.PHONE_NUMBER_KEY, acesta fiind plasat în câmpul text needitabil corespunzător. Totodată, va transmite înapoi rezultatul operației de stocare (Activity.RESULT_OK sau Activity.RESULT_CANCELED).

Datorită faptului că aplicația Android Contacts Manager nu dispune de o activitate principală (implicită), aceasta nu va mai putea fi lansată în execuție folosind mediul integrat de dezvoltare Android Studio. Pentru ca aceasta să fie doar instalată pe dispozitivul mobil, se accesează meniul RunEdit Configurations…, iar în secțiunea Launch Options de pe panoul General, se selectează opțiunea Launch: Nothing.

student@eg106:~$ adb shell 
vbox86p:/ # pm list packages -f

7. Să se încarce modificările realizate în cadrul depozitului 'Laborator04' de pe contul Github personal, folosind un mesaj sugestiv.

student@eim-lab:~/Laborator04$ git add *
student@eim-lab:~/Laborator04$ git commit -m "implemented taks for laboratory 04"
student@eim-lab:~/Laborator04$ git push origin master

Resurse Utile

Intents and Intent Filters(android.com)
Joseph ANNUZZI, Jr, Lauren DARCEY, Shane CONDER, Introduction to Android Application Development - Developer's Library, 4th Edition, Addison-Wesley, 2013 - capitolul 4, subcapitolele Organizing Activity Components with Fragments, Managing Activity Transition with Intents, Working with Services, Receiving and Broadcasting Intents
Bill PHILLIPS, Brian HARDY, Android Programming. The Big Nerd Ranch Guide, Pearson Technology Group, 2013 - capitolele 5, 7, 10, 21, 23, 29, 30
Reto MEIER, Professional Android for Application Development, John Wiley & Sons, 2012 - capitolul 4 (Introducing Fragments), 5 (Introducing Intents)
Ronan SCHWARZ, Phil DUTSON, James STEELE, Nelson TO, Android Developer's Cookbook, Building Applications with the Android SDK, 2nd Edition, Addison Wesley, 2013 - capitolele 2, 7
Wei Meng LEE, Beginning Android 4 Application Development, Wiley, 2012
Satya KOMATINENI, Dave MACLEAN, Pro Android 4, Apress, 2012
Dezvoltarea aplicațiilor pentru Android
Android Programming Tutorials - Core Servlets - secțiunile Intents - part I, II & III
Android Intents - Tutorial

Android Fundamentals

Android Intents Explicit and Implicit Intents