Î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).
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.
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:
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:
AndroidManifest.xml
trebuie să se specifice permisiunile necesare pentru a se putea accesa rețeaua de calculatoare.
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
:
android.permission.INTERNET
: controlează drepturile de acces referitoare la comunicația prin rețea, chiar și în situația în care aceasta se realizează local sau pentru utilizarea unor servicii de rețea;android.permission.ACCESS_NETWORK_STATE
: furnizează informații cu privire la conectivitatea dispozitivului mobil.<?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.
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.
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 tipGET /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 webPOST /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 |
Cel mai frecvent, se utilizează metodele GET
(folosită implicit, dacă nu se specifică altfel) și 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:
GET
poate fi reținută în cache, fapt ce nu este valabil și pentru o cerere POST
;GET
rămâne în istoricul aplicației de navigare, fapt ce nu este valabil și pentru o cerere POST
;GET
poate fi reținută printre paginile Internet favorite din cadrul programului de navigare, fapt ce nu este valabil și pentru o cerere POST
;GET
impune unele restricții cu privire la lungimea (maxim 2048 caractere) și la tipul de date (doar caractere ASCII) transmise (prin URL), fapt ce nu este valabil și pentru o cerere POST
;GET
nu trebuie folosită atunci când sunt implicate informații critice (acestea fiind vizibile în URL), fapt ce nu este valabil și pentru o cerere POST
;GET
ar trebui să fie folosită doar pentru obținerea unei resurse, fapt ce nu este valabil și pentru o cerere POST
.
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:
User-Agent
- informații cu privire la browser-ul utilizat și la platforma pe care rulează acestaAccept
- tipul MIME Accept-Charset
- setul de caractereAccept-Encoding
- mecanismul de codificareAccept-Language
- limbaHost
(obligatoriu) - denumirea gazdei pe care se găsește resursa (specificată în URL); necesară întrucât o adresă IP poate fi asociată mai multor nume de DNSAuthorization
- informații de autentificare în cazul unor operații care necesită drepturi privilegiateCookie
- transmite un cookie primit anteriorDate
- data și ora la care a fost transmisă cerereaUn 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:
Server
- informații cu privire la mașina care găzduiește resursa care este transmisăContent-Encoding
- mecanismul de codificareContent-Language
- limbaContent-Length
- dimensiuneaContent-Type
- tipul MIMELast-Modified
- ora și data la care pagina Internet a fost modificatăLocation
- informație prin care serverul web informează clientul de faptul că ar trebui folosit alt URL (resursa a fost mutată sau trebuie accesată o pagină Internet localizată în funcție de anumite preferințe)Accept-Ranges
- informație referitoare la transmiterea conținutului solicitat în mai multe părți, corespunzătoare unor intervale de octețiSet-Cookie
- transmiterea unui cookie de la serverul web la client, acesta trebuind să fie inclus în antetele ulterioare ale mesajelor schimbate între cele două entitățiVolley 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:
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:
student@dandroid2015:~$ git clone https://android.googlesource.com/platform/frameworks/volley
:volley
) sub care va fi referit ... 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') }
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
:
Volley.newRequestQueue(Context)
- construiește o coadă de cereri folosind implementările standard pentru implementarea mecanismului de cache și a comunicației prin rețea:DiskBasedCache
: pentru fiecare răspuns care a fost solicitat prin intermediul unei cereri se construiește un fișier dedicat, căruia îi corespunde un index stocat în memorie;BasicNetwork
: implementează funcționalitate de transport folosind un client HTTP (tipic, un obiect HttpURLConnection
);Volley.newRequestQueue(Cache, Network)
permite utilizarea unui mecanism de cache și a unei modalități de transmisie prin rețea ai căror comportament și parametri sunt definiți de utilizator.O coadă de cereri implementează următoarele metode:
add()
- transmite o cerere care este inițial verificată de firul de execuție care verifică cache-ul astfel încât dacă răspunsul există stocat, va fi furnizat fără a se mai realiza comunicația prin reșea; în caz contrar, el va fi plasat în coada gestionată de firele de execuție responsabile de comunicația prin rețea, acesta fiind preluat în momentul în care unul dintre acestea devine disponibil;cancel()
- anulează o cerere care a fost transmisă anterior, împiedicând să se obțină un răspuns pe baza sa (metoda este invocată pe obiectul Request
);cancelAll()
- anulează toate cererile care au atașată o anumită etichetă, specificată prin intermediul metodei setTag()
; o astfel de abordare poate fi utilă atunci când o activitate nu mai este vizibilă (pe metoda onStop()
) astfel încât să nu se mai proceseze răspunsuri care nu pot fi afișate; în acest fel, utilizatorul este scutit să verifice dacă instanța activității există sau nu pe metodele de callback apelate în mod automat în momentul în care resursa solicitată devine disponibilă sau în momentul în care s-a produs o excepție.
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); } }
newRequestQueue()
trebuie să se refere la contextul aplicației, de accea se utilizează metoda getApplicationContext()
.
Volley folosește mai multe fire de execuție:
O utilizare tipică Volley (algoritmul pe care îl utilizează această platformă) este următoarea:
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:
În situația în care se dorește implementarea unui tip de cerere definită de utilizator, trebuie să se respecte câteva condiții:
parseNetworkResponse(NetworkResponse)
care construiește rezultatul (Response.success()
, respectiv Response.error()
) în funcție de răspunsul primit;deliverResponse(T)
care apelează metoda de callback (onResponse(T)
) atunci când răspunsul este disponibil.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:
Request.Method
;Response.Listener<T>
:onResponse(T)
;ErrorListener
:onErrorResponse(VolleyError)
.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);
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:
JSONObject
, pe baza unor denumiri de atribute existente.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.
JSONObject(Bean bean)
care permite construirea unui obiect JSON pe baza unei componente Java Bean.
Gestiunea obiectelor JSON se face:
Object
), aceasta fiind supraîncărcată și pentru tipurile boolean
, double
, int
, long
, String
, JSONObject
, JSONArray
;JSONObject
, existând și implementări specifice pentru tipurile boolean
, double
, int
, long
.
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: