Table of Contents

Laborator 6 - Biblioteci

Fragmente

În laboratoarele precedente ați învățat despre activități, cum funcționează acestea, și cum le putem conecta între ele. Astăzi vom lucra cu Fragmente. În Android, un Fragment este o clasă ce reprezintă o componentă independentă ce se execută în contextul unei activități.

De ce folosim fragmente? Fragmentele au fost introduse începând cu Android 3.0 (API level 11), iar scopul lor este de a facilita crearea de interefețe ce se pot adapta diferitelor tipuri de ecrane.

Mai pe scurt, un fragment poate fi văzut ca o porțiune de interfață ce poate fi refolosită în cadrul mai multor activități sau pentru o poziționare diferită a aceleiași interfețe - dacă folosim alte tipuri de ecrane (exemplu în figura de mai jos).

Ciclul de viață al unui fragment

Deși se execută în cadrul contextului unei activități, fragmentele au propriul lor ciclu de viață, propriile evenimente și pot fi adăugate sau șterse, chiar dacă activitatea de care aparține este în curs de execuție. Ciclul de viață al fragmentului este dependent de cel al activității sale. De aceea, dacă o activitate apelează onPause(), atunci toate fragmentele sale vor avea această stare.

Atenție: Fragmentele nu moștenesc clasa Context. Pentru a avea acces la activitatea “gazdă”, trebuie să folosiți metoda getActivity()

Crearea unui fragment

Pentru a scrie un fragment, trebuie să creați o clasă care să moștenească clasa Fragment. Așa cum ați văzut în laboratorul despre activități, puteți suprascrie diverse metode (descrise mai sus) care pot modifica un fragment de-a lungul ciclului său de viață.

public class MyFirstFragment extends Fragment {
 
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
      // Inflate the layout for this fragment
      return inflater.inflate(R.layout.my_first_fragment, container, false);
  }
}

În exemplul de mai sus am creat o clasă de tip Fragment, în care putem observa că am suprascris metoda onCreateView(). Așa cum am spus mai sus, această metodă este folosită pentru a crea componentele interfeței(din fișierul .xml) corespunzătoare fragmentului.

Parametrii acestei metode sunt următorii:

Fragmentele sunt de două tipuri: statice(definite o singură dată în fișierul layout - nu pot fi suprimate sau inlocuite) și dinamice. În acest laborator nu vom intra în detalii în ceea ce privește fragmentele statice, însă este important să știți de existența lor.

Comuncații în rețea

Indiferent de protocolul pe care îl folosește pentru a accesa resursele disponibile prin intermediul rețelei, o aplicație Android trebuie să respecte anumite constrângeri:

  1. orice comunicație prin rețea trebuie să se realizeze prin intermediul unui fir de execuție dedicat;
  2. în fișierul AndroidManifest.xml trebuie să se specifice permisiunile necesare pentru a se putea accesa rețeaua de calculatoare.

Specificarea Permisiunilor de Utilizare a Rețelei

O aplicație Android nu are acces la comunicația prin rețea decât prin intermediul unor permisiuni speciale, acestea fiind precizate în fișierul AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="org.rosedu.dandroid.messageme" >
 
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
  <!-- other application properties or components --> 
 
</manifest>

De asemenea, este necesar ca în configurațiile dispozitivului mobil, în secțiunea Wireless & networks să existe activate fie rețelele fără fir (Wi-Fi), fie serviciul de date (Mobile networks). Câteodată unele aplicații sunt restricționate în situația în care au utilizat un anumit trafic sau nu li se permite deloc accesul la rețea. Și aceste configurații trebuie să fie verificate în situația în care operațiile ce implică accesarea unor resurse la distanță nu pot fi realizate.

Comunicația prin HTTP

De multe ori, funcționalitatea pe care o pun la dispoziție aplicațiile Android este preluată din alte surse, datorită limitărilor impuse de capacitatea de procesare și memorie disponibilă ale unui dispozitiv mobil. O strategie posibilă în acest sens este utilizarea HTTP, pentru interogarea unor servicii web, al căror rezultat este de cele mai multe ori oferit în format JSON sau XML. De asemenea, descărcarea unor resurse se poate face prin inspectarea codului sursă al unor pagini Internet (documente HTML), în urma acestei operații detectându-se locația la care acestea sunt disponibile.

HTTP (Hypertext Transfer Protocol) este un protocol de comunicație responsabil cu transferul de hipertext (text structurat ce conține legături) dintre un client (de regulă, un navigator) și un server web, interacțiunea dintre acestea (prin intermediul unei conexiuni TCP persistente pe portul 80) fiind reglementată de RFC 2616. HTTP este un protocol fără stare, pentru persistența informațiilor între accesări fiind necesar să se utilizeze soluții adiacente (cookie, sesiuni, rescrierea URL-urilor, câmpuri ascunse).

Principalele concepte cu care lucrează acest protocol sunt cererea și răspunsul.

Structura unei Cereri HTTP

O cerere HTTP conține una sau mai multe linii de text ASCII, precedate în mod necesar de denumirea metodei specificând operația ce se va realiza asupra conținutului respectiv:

DENUMIRE METODĂ DESCRIERE
GET descărcarea resursei specificate de pe serverul web pe client; majoritatea cererilor către un server web sunt de acest tip
GET /page.html HTTP/1.1
Host: www.server.com
HEAD obținerea antetului unei pagini Internet, pentru a se verifica parametrii acesteia sau doar pentru a testa corectitudinea unui URL
PUT încărcarea resursei specificate de pe client pe serverul web (cu suprascrierea acesteia, în cazul în care există deja); trebuie specificate și datele de autentificare, utilizatorul respectiv trebuind să aibă permisiunile necesare pentru o astfel de operașie
POST transferul de informații de către client cu privire la resursa specificată, acestea urmând a fi prelucrate de serverul web
POST /page.html HTTP/1.1
Host: www.server.com
attribute1=value1&...&attributen=valuen
DELETE ștergerea resursei specificate de pe serverul web, rezultatul operației depinzând de permisiunile pe care le deține utilizatorul ale cărui date de autentificare au fost transmise în antete
TRACE solicitare de retransmitere a cererii primite de serverul web de la client, pentru a se testa corectitudinea acesteia
CONNECT rezervat pentru o utilizare ulterioară
OPTIONS interogare cu privire la atributele serverului web sau ale unei resurse găzduite de acesta

Capitalizarea este importantă atunci când se precizează denumirea metodei folosite, făcându-se distincție între minuscule și majuscule.

Cel mai frecvent, se utilizează metodele GET (folosită implicit, dacă nu se specifică altfel) și POST.

GET vs. POST

Deși atât metoda GET cât și metoda POST pot fi utilizate pentru descărcarea conținutului unei pagini Internet, transmițând către serverul web valorile unor anumite atribute, între acestea există anumite diferențe:

O linie de cerere HTTP poate fi succedată de unele informații suplimentare, reprezentând antetele de cerere, acestea având forma atribut:valoare, fiind definite următoarele proprietăți:

Structura unui Răspuns HTTP

Un răspuns HTTP este format din linia de stare, antetele de răspuns și posibile informații suplimentare, conținând o parte sau toată resursa care a fost solicitată de client de pe serverul web.

În cadrul liniei de stare este inclus un cod din trei cifre care indică dacă solicitarea a putut fi îndeplinită sau nu (situație în care este indicată și cauza).

FAMILIE DE CODURI SEMNIFICAȚIE DESCRIERE
1xx Informație răspuns provizoriu, constând din linia de stare și alte antete (fără conținut, terminat de o linie vidă), indicând faptul că cererea a fost primită, procesarea sa fiind încă în desfășurare; nu este utilizată în HTTP/1.0
2xx Succes răspuns ce indică faptul că cererea a fost primită, înțeleasă, acceptată și procesată cu succes
3xx Redirectare răspuns transmis de serverul web ce indică faptul că trebuie realizate acțiuni suplimentare din partea clientului (cu sau fără interacțiunea utilizatorului, în funcție de metoda folosită) pentru ca cererea să poată fi îndeplinită; în cazul în care redirectarea se repetă de mai multe ori, se poate suspecta o buclă infinită
4xx Eroare la client răspuns transmis de serverul web ce indică faptul că cererea nu a putut fi îndeplinită, datorită unei erori la nivelul clientului; mesajul include și o entitate ce conține o descriere a situației, inclusiv tipul acesteia (permanentă sau temporară)
5xx Eroare la server cod de răspuns ce indică clientului faptul că cererea nu a putut fi îndeplinită, datorită unei erori la nivelul serverului web; mesajul include și o entitate ce conține o descriere a situației, inclusiv tipul acesteia (permanentă sau temporară)

Mesajul conține și antetele de răspuns, având forma atribut:valoare, fiind definite următoarele proprietăți:

Volley

Volley este o bibliotecă open-source (disponibilă prin intermediul Android Open-Source Project) ce oferă facilități pentru interogarea de resurse disponibile prin intermediul protocolului HTTP.

Câteva dintre facilitățile puse la dispoziție de această platformă sunt:

  1. gestiune automată a cererilor (implementare sistem de priorități, posibilitate de manipulare a cererilor concurente, funcționalitate pentru anulare a unei cereri transmise anterior);
  2. utilizarea transparentă a unui sistem de caching implementat atât în memorie cât și pe discul local;
  3. posibilitatea de implementare a unor mecanisme specifice pentru retransmitere;
  4. implementarea de operații de tip RPC (Remote Procedure Call) executate asincron pentru popularea conținutului unor elemente din interfața grafică;
  5. utilitare pentru depanare.

Volley nu este adecvat pentru operații de intrare / ieșire intensive tocmai datorită sistemului de caching, conținutul resurselor solicitate fiind menținut în memorie în vederea realizării operației de procesare a acestuia.

Pentru utilizarea bibliotecii Volley este necesar să se realizeze următoarele operații:

  1. descărcarea codului sursă de la depozitul unde este întreținută
    student@dandroid2015:~$ git clone https://android.googlesource.com/platform/frameworks/volley
  2. referirea proiectului ca bibliotecă în Android Studio
    • FileNewImport Module…
    • indicarea locației la care au fost descărcate sursele în prelabil și o denumire a proiectului (tipic, :volley) sub care va fi referit
    • specificarea dependenței față de acest proiect precum și faptul că este necesar ca acesta să fie compilat
      build.gradle
      ...
      dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:23.1.1'
        compile 'com.android.support:design:23.1.1'
        compile project(':volley')
      }
    • dacă pentru Volley se folosește API Level 23, este necesar să se utilizeze biblioteca org.apache.http.legacy întrucât implementarea sa utilizează Apache HTTP Components.

Volley gestionează o structură de date RequestQueue care are rolul de a controla modul în care sunt tratate cererile. Aceasta distribuie cererile care trebuie procesate concurent către firele de execuție care se ocupă de comunicația prin rețea, realizând de asemenea interogarea informațiilor care se regăsest în cache. Există două moduri în care poate fi construit un obiect de tip RequestQueue:

O coadă de cereri implementează următoarele metode:

Este recomandat ca la nivelul unei aplicații Android care utilizează comunicația prin rețea folosind protocolul HTTP să se utilizeze o singură instanță a unui obiect de tip RequestQueue a cărui durată de viață se întinde pe întreaga perioadă de execuție. Abordarea cea mai adecvată este utilizarea unei clase Singleton care conține un obiect de tip RequestQueue precum și alte funcționalități Volley, dacă este necesar.

package org.rosedu.dandroid.messageme.network;
 
import android.content.Context;
 
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
 
public class VolleyController {
 
  private static VolleyController instance;
  private static Context context;
  private RequestQueue requestQueue;
 
  private VolleyController(Context context) {
    this.context = context;
    this.requestQueue = getRequestQueue();
  }
 
  public static synchronized VolleyController getInstance(Context context) {
    if (instance == null) {
      instance = new VolleyController(context);
    }
    return instance;
  }
 
  public RequestQueue getRequestQueue() {
    if (requestQueue == null) {
      requestQueue = Volley.newRequestQueue(context.getApplicationContext());
    }
    return requestQueue;
  }
 
  public <T> void addToRequestQueue(Request<T> request) {
    getRequestQueue().add(request);
  }
 
}

Contextul transmis ca argument metodei newRequestQueue() trebuie să se refere la contextul aplicației, de accea se utilizează metoda getApplicationContext().

Volley folosește mai multe fire de execuție:

  1. firul de execuție principal (al interfeței grafice), prin intermediul căruia este trimisă o cerere și pe care este primit răspunsul (pentru a fi vizualizat, de regulă, în cadrul interfeței grafice);
  2. firul de execuție responsabil cu implementarea mecanismului de caching, care verifică dacă există un răspuns pentru cerere, astfel încât să nu mai fie necesară comunicația prin rețea; în momentul în care este disponibil un răspuns pentru o cerere, aceasta este indexată tot de acest fir de execuție;
  3. mai multe fire de execuție care realizează comunicația prin rețea, care trimit o cerere și primesc un răspuns pe care îl procesează (operațiile de parsare sau codificare/decodificare necesită resurse și o perioadă de timp considerabilă).

O utilizare tipică Volley (algoritmul pe care îl utilizează această platformă) este următoarea:

  1. firul de execuție principal plasează o cerere în coadă
  2. firul de execuție responsabil cu implementarea mecanismului de caching verifică dacă există un răspuns pentru cerere
    1. dacă răspunsul pentru cerere se află în cache
      1. se citește răspunsul din cache
      2. se scrie rezultatul către firul de execuție principal
    2. dacă răspunsul pentru cerere NU se află în cache
      1. se transmite cerere în coadă
      2. un fir de execuție care realizează comunicația prin rețea trimite cererea, atunci când devine disponibil, preluând răspunsul
        1. răspunsul este stocat în cache, după ce poate suferi procesări ulterioare (parsare, codificare/decodificare)
        2. se scrie rezultatul către firul de execuție principal

Volley definește mai multe tipuri de cerere standard, în funcție de tipul de date pe care îl are resursa care va fi accesată prin intermediul protocolului HTTP:

  1. StringRequest furnizează un rezultat de tip șir de caractere, în orice format, folosind un mecanism de codificare;
  2. ImageRequest întoarce o resursă de tip imagine, disponibilă la o anumită locație (URL), aceasta fiind transmisă sub formă de bitmap decodificat (și posibil redimensionat, dacă se solicită acest lucru); operațiile costisitoare sunt realizate de firele de execuție responsabile de comunicația prin rețea;
    1. ImageLoader oferă funcționalitatea de gestiune a cache-ului, care este stocat în memorie (dacă s-ar fi utilizat discul local, ar fi existat un impact asupra firului de execuție principal), transmițând mai multe resurse la un moment dat;
    2. NetworkImageView reprezintă un tip de element al interfeței grafice, specializat pentru resurse de tip imagine disponibilă la o locație la distanță, accesibilă prin intermediul comunicației în rețea.
  3. JsonRequest suportă două implementări:

În situația în care se dorește implementarea unui tip de cerere definită de utilizator, trebuie să se respecte câteva condiții:

public class CustomRequest<T> extends Request<T> {
 
  private Map<String, String> params;
  private Response.Listener<T> responseListener;
  private Response.ErrorListener errorListener;
  private Class<T> classType;
 
  public CustomRequest(String url, Map<String, String> params, Response.Listener<T> responseListener, Response.ErrorListener errorListener, Class<T> classType) {
    this(Method.GET, url, params, responseListener, errorListener, classType);
  }
 
  public CustomRequest(int method, String url, Map<String, String> params, Response.Listener<T> responseListener, Response.ErrorListener errorListener, Class<T> classType) {
    super(method, url, errorListener);
    this.params = params;
    this.responseListener = responseListener;
    this.errorListener = errorListener;
    this.classType = classType;
  }
 
  @Override
  protected Map<String, String> getParams() throws AuthFailureError {
    return params;
  }
 
  @Override
  protected Response<T> parseNetworkResponse(NetworkResponse networkResponse) {
    try {
      String response = new String(networkResponse.data, HttpHeaderParser.parseCharset(networkResponse.headers));
      if (classType.equals(JSONObject.class)) {
        return Response.success((T)new JSONObject(response), HttpHeaderParser.parseCacheHeaders(networkResponse));
      }
      if (classType.equals(JSONArray.class)) {
        return Response.success((T)new JSONArray(response), HttpHeaderParser.parseCacheHeaders(networkResponse));
      }
      return null;
    } catch (UnsupportedEncodingException unsupportedEncodingException) {
      return Response.error(new ParseError(unsupportedEncodingException));
    } catch (JSONException jsonException) {
      return Response.error(new ParseError(jsonException));
    }
  }
 
  @Override
  protected void deliverResponse(T jsonObject) {
    responseListener.onResponse(jsonObject);
  }
 
  @Override
  public void deliverError(VolleyError volleyError) {
    errorListener.onErrorResponse(volleyError);
  }
 
}

Pentru definirea unui obiect de tip Request este necesar să se precizeze următoarele elemente:

Map<String, String> parameters = new HashMap<>();
parameters.put(Constants.USERNAME_ATTRIBUTE, senderUsername);
CustomRequest<JSONArray> customRequest = new CustomRequest(
  Request.Method.POST,
  Constants.USER_LIST_WEB_SERVICE_ADDRESS,
  parameters,
  new Response.Listener<JSONArray>() {
    @Override
    public void onResponse(JSONArray response) {
      try {
        for (int position = 0; position < response.length(); position++) {
          JSONObject user = response.getJSONObject(position);
          final String recipientId = user.get(Constants.USER_ID_ATTRIBUTE).toString();
          final String recipientUsername = user.get(Constants.USERNAME_ATTRIBUTE).toString();
          contact = (RelativeLayout)inflater.inflate(R.layout.contact, null);
          TextView usernameTextView = (TextView)contact.findViewById(R.id.username_text_view);
          usernameTextView.setText(recipientUsername);
          Button writeMessageButton = (Button)contact.findViewById(R.id.write_message_button);
          contactsList.addView(contact);
        }
      } catch (JSONException jsonException) {
        Log.e(Constants.TAG, jsonException.getMessage());
        if (Constants.DEBUG) {
          jsonException.printStackTrace();
        }
      }
    }
  },
  new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError volleyError) {
      Snackbar.make(contactsTextView, getResources().getString(R.string.an_error_has_occurred), Snackbar.LENGTH_LONG)
              .show();
    }
  },
  JSONArray.class
);
VolleyController.getInstance(getActivity().getApplicationContext()).addToRequestQueue(customRequest);

Prelucrarea Rezultatelor întoarse de Servicii Web

Formatul JSON (JavaScript Object Notation)

Unele servicii web folosesc formatul JSON, specificat de RFC 7159, pentru transferul de date întrucât, spre diferență de XML care implică numeroase informații suplimentare, acesta optimizează cantitatea de date implicate. De asemenea, este foarte ușor pentru un utilizator uman să prelucreze informațiile reprezentate într-un astfel de format.

Acest mecanism de reprezentare a datelor a fost utilizat inițial în JavaScript, unde acesta descria literali sau obiecte anonime, folosite o singură dată (fără posibilitatea de a fi reutilizate), informațiile fiind transmise prin intermediul unor șiruri de caractere. Ulterior, a fost preliat pe scară largă.

În principiu, informațiile reprezentate în format JSON au structura unei colecții de perechi de tip (cheie, valoare), fiecare dintre acestea fiind grupate într-o listă de obiecte ordonate. Nu se folosesc denumiri de etichete, utilizându-se doar caracterele , ,, {, }, [ și ].

[
  {
    "attribute1": "value11",
    "attribute2": "value12",
    ...,
    "attributen": "value1n"
  },
  {
    "attribute1": "value21",
    "attribute2": "value22",
    ...,
    "attributen": "value2n"
  },
  ...
  {
    "attribute1": "valuem1",
    "attribute2": "valuem2",
    ...,
    "attributen": "valuemn"
  }  
]

În Android este suportat formatul JSON strict, așa cum este descris pe http://json.org/:

Potrivit RFC 4627, tipul MIME al documentelor reprezentate în format JSON treuie să fie în mod necesar application/json.

Există numeroase servicii web care își expun funcționalitatea prin intermediul unor documente JSON:

În Android, prelucrarea documentelor reprezentate în format JSON este realizată prin intermediul clasei JSONObject care a fost integrată parțial (cu unele funcționalități lipsă), fără a se specifica clar versiunea care este utilizată.

Un document JSON poate fi construit în mai multe moduri:

De regulă, metodele care operează cu obiecte JSON pot genera excepția JSONException în situația în care sunt întâlnite documente care nu sunt bine formate.

În Android nu este implementată metoda JSONObject(Bean bean) care permite construirea unui obiect JSON pe baza unei componente Java Bean.

Gestiunea obiectelor JSON se face:

Verificarea faptului că un document JSON deține un anumit atribut se face prin invocarea metodei has(String name). Funcționalitatea inversă este furnizată de metoda isNull(String name), aceasta verificând și dacă valoarea asociată atributului respectiv este null.

Dimensiunea unui obiect JSONObject, reprezentat sub forma numărului de asocieri de tip (cheie, valoare) este furnizat de metoda length().

Reprezentarea unui obiect JSON sub forma unui șir de caractere poate fi obținută prin intermediul metodelor:

Unele metode furnizează vectori de obiecte JSON, reprezentate de clasa JSONArray, în care accesul la obiecte se face indexat, începând cu 0:

Exercitii

  1. Modificati laboratoarele anterioare astfel incat sa folositi ButterKnife
  2. Afisati pe harta (Google Maps) unde este facultatea Google Maps
  3. Afisati data si imaginea cele mai recente imagini facute de NASA casei voastre https://api.nasa.gov/api.html#earth