O aplicație Android poate conține mai multe componente:
android.app.Application
;
Contexul este utilizat pentru a implementa diferite funcționalități la nivelul întregii aplicații:
În clasele de tip Activity
sau Service
, contextul aplicației poate fi obținut printr-un apel de forma:
Context context = getApplicationContext(); // definita in clasa Context
Context context = getApplication(); // definita in clasa Activity
Activitatea reprezintă o componenta a aplicației Android ce oferă o interfață grafică cu care utilizatorul poate să interacționeze. Cele mai multe activități ocupă întreaga suprafață de afișare, însă acest lucru nu este obligatoriu.
O aplicație Android este formată din una sau mai multe activități (slab cuplate între ele). Există întotdeauna o activitate principală care este afișată atunci când aplicația Android este lansată în execuție inițial.
O activitate poate invoca o altă activitate pentru a realiza diferite sarcini, prin intermediul unei intenții. În acest moment, activitatea veche este oprită și salvată pe stivă (eng. back stack), după care este pornită activitatea nouă. Restaurarea și (re)începerea activității vechi este realizată în momentul în care activitatea nouă (curentă) este terminată. Un comportament similar are loc în momentul în care se produce un eveniment (primirea unui apel telefonic, apăsarea tastelor Home sau Back, lansarea altei aplicații).
O activitate poate fi utilizată numai dacă este definită în fișierul AndroidManifest.xml
, în cadrul elementului de tip <application>
. Pentru fiecare activitate trebuie creată o intrare de tip <activity> pentru care se specifică diferite atribute, dintre care obligatoriu este numai denumirea activității (android:name
). Din momentul în care aplicația Android este publicată, conținutul manifestului devine un contract față de utilizatori, iar denumirile componentelor nu mai pot fi modificate întrucât pot genera erori în cazul producerii unor actualizări.
Pentru o activitate se poate specifica și un filtru de intenții, în cadrul elementului <intent-filter>, spre a indica modul în care componentele aplicației o pot accesa. Acesta este necesar pentru ca activitatea să poată fi rulată folosind intenții implicite (furnizate de alte aplicații). Așadar, o astfel de intrare va fi necesară pentru fiecare tip de intenție, precizând elementele <action>
și opțional <category>
/ <data>
. În cazul activităților care nu vor fi accesibile din cadrul altor aplicații Android, nu este necesară definirea unui filtru de intenții.
android.intent.action.MAIN
, întrucât reprezintă punctul de intrare al aplicației Android;android.intent.category.LAUNCHER
, întrucât activitatea trebuie inclusă în meniul dispozitivului mobil pentru a putea fi lansată în execuție.
<?xml version="1.0" encoding="utf-8"?> <manifest ... > <!-- ... --> <application ... > <activity android:name=".LifecycleMonitorActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Ulterior, se va implementa și clasa identificată prin denumirea activității. Aceasta trebuie să fie derivată din android.app.Activity
și să implementeze cel puțin metoda onCreate()
în care sunt inițializate componentele sale.
public class LifecycleMonitorActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_lifecycle_monitor); // ... } // ... }
Este obligatoriu ca metoda onCreate()
să apeleze metoda părinte, în caz contrar generându-se o excepție.
Pentru fiecare activitate, este necesar să se descrie interfața grafică în cadrul unui fișier .xml (încărcat manual, în cadrul metodei onCreate()
, printr-un apel al metodei setContentView()
) în care elementul părinte este un mecanism de dispunere a conținutului (derivat din clasa android.view.ViewGroup
). Acest fișier este plasat în directorul res/layout
și conține referințe către toate obiectele care vor fi afișate în cadrul ferestrei. În urma compilării, vor fi generate niște referințe (adrese în cadrul pachetului de resurse) prin intermediul cărora resursele vor putea fi accesate.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context="ro.pub.cs.systems.eim.lab02.graphicuserinterface.LifecycleMonitorActivity" > <!-- ... --> </LinearLayout>
Prin urmare, componentele definitorii ale unei activități sunt clasa în care este implementat comportamentul în urma interacțiunii cu utilizatorul și fișierul .xml care descrie modelul static al interfeței grafice.
O activitate poate fi pornită în mod explicit prin intermediul metodei startActivity()
care primește ca parametru un obiect de tip intenție (căruia i se pot atașa și niște date, transferate activității). În cazul în care se așteaptă ca activitatea să producă unele rezultate, se va apela metoda startActivityForResult()
- furnizându-se și un cod de cerere -, fiind necesar ca activitatea astfel invocată să implementeze metoda onActivityResult()
.
O activitate poate fi oprită în mod explicit prin intermediul metodei finish()
. De regulă, folosirea unei astfel de metode este descurajată, datorită impactului pe care îl poate avea asupra experienței utilizatorului.
Din momentul în care activitatea este creată și până la momentul în care este distrusă, ea trece printr-o serie de etape, cunoscute sub denumirea de ciclul de viață al activității:
Activity
este stocat în memorie, fiind atașată în continuare procesului responsabil cu gestiunea ferestrelor și menținându-se starea tuturor componentelor sale; totuși, ea poate fi distrusă de sistemul de operare dacă necesarul de memorie disponibilă nu poate fi întrunit din cauza sa;Activity
fiind stocat în memorie, menținându-se starea tuturor componentelor sale, dar detașându-se de procesul responsabil cu gestiunea ferestrelor; ea poate fi distrusă de sistemul de operare dacă necesarul de memorie disponibilă nu poate fi întrunit din cauza sa;
Tranziția unei activități dintr-o stare în alta este notificată prin intermediul unor metode (eng. callbacks), care pot fi suprascrise pentru a realiza diferite operații necesare pentru gestiunea memoriei, asigurarea persistenței informațiilor și a consistenței aplicației Android în situația producerii de diferite evenimente:
onCreate(Bundle)
- apelată în momentul în care activitatea este creată; această metodă va fi folosită pentru inițializări statice:setContentView(int)
(al cărei parametru reprezintă referința către resursa de tip .xml
care descrie interfața grafică);findViewById(int)
(al cărei parametru reprezintă referința către componenta respectivă - eng. widget - așa cum apare în resurse);
onCreate()
este întotdeauna urmată de metoda onStart()
.
onRestart()
- apelată atunci când activitatea a fost oprită și ulterior repornită; este urmată întotdeauna de metoda onStart()
;onStart()
- apelată înainte ca activitatea să apară pe ecran; poate fi urmată de metoda onResume()
dacă activitatea trece în prim-plan sau de metoda onPause()
dacă activitatea trece în fundal;onResume()
- apelată înainte ca activitatea să interacționeze cu utilizatorul; această metodă va fi folosită pentru a porni servicii sau cod care trebuie să ruleze atâta timp cât aplicația este afișată pe ecran; este urmată întotdeauna de metoda onPause()
;onPause()
- apelată înainte ca activitatea să fie întreruptă temporar, iar o altă activitate să fie reluată; această metodă va fi utilizată pentru a opri servicii sau cod care nu trebuie să ruleze atâta timp cât activitatea se află în fundal (întrucât consumă timp de procesor) și pentru a salva starea diferitelor componente în vederea asigurării persistenței și a consistenței aplicației înainte și după evenimentul care a produs suspendarea sa; poate fi urmată de metoda onResume()
dacă activitatea trece în prim-plan sau de metoda onStop()
dacă activitatea este ascunsă;
onPause()
nu trebuie să ocupe un interval de timp prea mare întrucât în caz contrar ar bloca noua activitate care urmează să fie lansată în execuție.
onPause()
, aceasta fiind singura metodă care este apelată în mod garantat înainte de a se solicita memoria pe care activitatea o folosește.
onStop()
- apelată în momentul în care activitatea este ascunsă, fie din cauză că urmează să fie distrusă, fie din cauză că o altă activitate, a cărei interfață grafică ocupă întreaga suprafață a dispozitivului de afișare, urmează să devină vizibilă; poate fi urmată de metoda onRestart()
, dacă activitatea urmează să interacționeze (din nou) cu utilizatorul, sau de metoda onDestroy()
dacă activitatea urmează să fie terminată sau distrusă de sistemul de operare;onDestroy()
- apelată înainte ca activitatea să se termine sau să fie distrusă de către sistemul de operare (fie manual, fie automat) din lipsă de memorie; această metodă va fi utilizată pentru a elibera resursele ocupate.
isFinishing()
.
onCreate()
, este necesar ca acest lucru să fie realizat la începutul metodei.
public class LifecycleMonitorActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... } @Override protected void onStart() { super.onStart(); // ... } @Override protected void onResume() { super.onResume(); // ... } @Override protected void onPause() { super.onPause(); // ... } @Override protected void onStop() { super.onStop(); // ... } @Override protected void onDestroy() { super.onDestroy(); // ... } @Override protected void onRestart() { super.onRestart(); // ... } // metode folosite pentru salvarea si restaurarea starii @Override protected void onSaveInstanceState(Bundle savedInstanceState) { // apelarea metodei din activitatea parinte este recomandata, dar nu obligatorie super.onSaveInstanceState(savedInstanceState); // ... } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // apelarea metodei din activitatea parinte este recomandata, dar nu obligatorie super.onRestoreInstanceState(savedInstanceState); // ... } }
Unele dintre metodele care gestionează ciclul de viață al unei activități primesc ca parametru un obiect de tip Bundle, utilizat pentru gestiunea stării în cazul în care activitatea este distrusă din lipsă de memorie:
onCreate()
- parametrul savedInstance
poate să fie null
, daca activitatea nu a mai fost rulată anterior, fie este o instanță a unui obiect de tip Bundle
în care se găsește starea anterioară (așa cum a fost reținută de metoda onSaveInstanceState()
);onSaveInstanceState()
- este apelată când activitatea urmează să fie ascunsă și există posibilitatea ca procesul acesteia să fie terminat din lipsă de memorie, pentru a salva starea activității;onRestoreInstanceState()
- este apelată doar dacă există o stare a activității care ar trebui să fie restaurată.
Obiectul de tip Bundle
este o formă de hash map, în care cheia este întotdeauna de tip String
, iar valorile au tipul android.os.Parcelable
(asemănător cu Serializable
din Java, deși acesta nu este un mecanism de serializare în sine). Astfel, tipurile de date care pot fi salvate și încărcate prin intermediul unui obiect de tip Bundle
sunt:
boolean | boolean[] | |
Bundle |
||
byte | byte[] | |
char | char[] | |
CharSequence | CharSequence[] | ArrayList<CharSequence> |
double | double[] | |
float | float[] | |
int | int[] | ArrayList<Integer> |
long | long[] | |
Serializable |
||
short | short[] | |
SparseArray |
||
String | String[] | ArrayList<String> |
Starea unei activități este menținută atâta vreme cât ea este activă (deci inclusiv când au fost apelate metodele onPause()
și onStop()
), aceasta putând fi restaurată corespunzător. Necesitatea asigurării consistenței activității de către programator apare în momentul în care activitatea este terminată/distrusă și apoi (re)pornită. O astfel de situație este frecventă în cazul în care se produce o schimbare de configurație (se modifică orientarea dispozitivului de afișare - portrait vs. landscape, limba, disponibilitatea tastaturii), întrucât de fiecare dată este necesar să se (re)încarce resursele specifice pentru valorile respective.
Ținându-se cont de faptul că activitatea poate fi terminată/distrusă în orice moment după ce nu se mai găsește în prim-plan / este ascunsă, asigurarea consistenței trebuie realizată de fiecare dată.
Metoda onSaveInstanceState()
este înainte de metoda onStop()
și posibil înainte de metoda onPause()
, deși acest lucru nu este garantat. Ea primește ca parametru un obiect de tip Bundle
în care vor fi plasate datele din cadrul activității care se doresc a fi salvate, acestea putând fi identificate prin intermediul unei chei (de tip String
).
onSaveInstanceState()
nu este garantat să fie realizat de fiecare dată întrucât pot fi situații în care nu este necesar ca starea activității să fie restaurată (utilizatorul a terminat activitatea prin apăsarea butonului Back).
În cazul în care se dorește salvarea explicită a conținutului unui obiect EditText
, se poate proceda astfel:
@Override protected void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); EditText usernameEditText = (EditText)findViewById(R.id.username_edit_text); savedInstanceState.putString(Constants.USERNAME_EDIT_TEXT, usernameEditText.getText().toString()); }
Valoarea introdusă de utilizator va fi salvată în obiectul de tip Bundle
sub denumirea userNameEditText
, acesta fiind menținut și prin urmare utilizat între mai multe instanțe ale acestei activități.
Apelarea metodei părinte este necesară întrucât API-ul Android furnizează o implementare implicită pentru salvarea stării unei activități, parcurgând ierarhia de componente grafice (obiecte de tip View
) care au asociat un identificator (android:id
), folosit drept cheie în obiectul Bundle
. Astfel, de regulă, pentru elementele interfeței grafice, nu este necesar să se mențină starea, acest lucru fiind realizat în mod automat, cu respectarea condiției menționate. De aceea, în metoda onSaveInstanceState
, va fi realizată salvarea unor informații (obiecte ale clasei) pentru care procesul de salvare a stării nu este apelat. Totuși, asigurarea persistenței datelor nu se va realiza niciodată aici (întrucât nu se garantează apelarea sa), ci în metoda onPause()
.
În obiectul de tip Bundle
, prin cheia android:viewHierarchyState
se va reține un alt obiect de tip Bundle
care reține starea tuturor obiectelor de tip View
din cadrul activității (care sunt identificate prin intermediul unui câmp android:id
). În cadrul acestuia, prin cheia android:views
se reține un obiect de tip SparseArray
(un tip de dată specific Android care realizează mapări între întregi și obiecte, mai eficient decât un hashmap) care conține starea fiecărui obiect de tip View
prin intermediul identificatorului său.
Bundle viewHierarchy = savedInstanceState.getBundle("android:viewHierarchyState"); if (viewHierarchy != null) { SparseArray<? extends Parcelable> views = viewHierarchy.getSparseParcelableArray("android:views"); for (int k=0; k < views.size(); k++) { Log.d(Constants.TAG, views.get(k) + "->" + views.valueAt(k)); } } else { Log.d(Constants.TAG, "No view information was saved!"); }
Prin urmare, dacă aplicația este oprită și apoi pornită din nou, obiectele de tip View
vor avea conținutul existent în prealabil. Pentru a dezactiva această opțiune, trebuie specificată în fișierul .xml care descrie interfața grafică a activității, în elementul corespunzător obiectului de tip View
proprietatea android:saveEnabled=“false”
.
setSaveEnabled(boolean)
.
Încărcarea conținutului din obiectul de tip Bundle
(în vederea restaurării stării) poate fi realizată:
onCreate()
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_lifecycle_monitor); EditText usernameEditText = (EditText)findViewById(R.id.username_edit_text); if ((savedInstanceState != null) && (savedInstanceState.getString(Constants.USERNAME_EDIT_TEXT) != null)) { usernameEditText.setText(bundle.getString(Constants.USERNAME_EDIT_TEXT)); } }
onRestoreInstanceState()
, apelată în mod automat între metodele onStart()
și onResume()
; această abordare permite separarea dintre codul folosit la crearea ferestrei și codul utilizat la restaurarea stării unei ferestre@Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(bundle); EditText usernameEditText= (EditText)findViewById(R.id.username_edit_text); if (savedInstanceState.getString(Constants.USERNAME_EDIT_TEXT) != null) { usernameEditText.setText(savedInstanceState.getString(Constants.USERNAME_EDIT_TEXT)); } }
Android Studio folosește un mecanism automat pentru construirea aplicației Android, denumit Gradle, responsabil cu aducerea bibliotecilor referite de pe un depozit la distanță, cu definirea proprietăților aplicației Android, cu compilarea și împachetarea tuturor resurselor folosite, pentru rularea și instalarea aplicației astfel rezultate.
Regulile pentru construiea aplicației Android sunt precizate în fișiere build.gradle
, care se definesc pentru fiecare modul și proiect constituent.
Un fișier de configurare Gradle pentru o aplicație Android conține de regulă două secțiuni:
android
- conține proprietățile aplicației AndroidcompileSdkVersion
- reprezintă versiunea de SDK care va fi utilizată pentru compilarea proiectului AndroidbuildToolsVersion
- reprezintă versiunea de Android SDK Build Tools folosită pentru construirea fișierului care va fi instalat pe dispozitivul mobilapplicationId
- pachetul care identifică în mod unic aplicația AndroidmidSdkVersion
- platforma minimă pe care se garantează că aplicația Android va rula; astfel, se vor folosi numai funcționalități definite la acest nivel sau funcționalități definite în API-uri superioare, dar care sunt disponibile la nivelul bibliotecilor de suport;targetSdkVersion
- platforma maximă la care se garantează că aplicația Android va rula (de regulă, este versiunea cea mai recentă și este aceeași versiune folosită pentru compilarea codului sursă);versionCode
- versiunea curentă a aplicației (număr întreg)versionName
- versiunea curentă a aplicației, afișată către utilizator (format lizibil, de tip șir de caractere)testInstrumentationRunner
- biblioteca folosită pentru testele aplicației Androiddependencies
- reprezintă bibliotecile de care depinde aplicația Android pentru a putea fi compilată / rulată, precum și reguli de compilarecompile
- se precizează care fișiere sunt luate în considerare pentru classpath include
se folosește pentru a indica tipuri de fișiere care conțin diverse biblioteci);fileTree
este utilizată pentru a indica o structura de directoaretestCompile
și androidTestCompile
- indică pachete care conțin biblioteci pentru teste unitare.apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "ro.pub.systems.eim.lab02.activitylifecyclemonitor" minSdkVersion 16 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:25.2.0' }
Pentru a rula aplicația, este necesar un dispozitiv mobil fizic sau un dispozitiv mobil virtual (emulator).
Ctrl+6
).
Ulterior se poate rula aplicația, accesând din meniul contextual al proiectului (click dreapta) Run As.. → Android Application (sau Ctrl+F11
). În acest moment, se vor identifica toate dispozitivele Android (fizice sau virtuale) care sunt disponibile (conectate) și care rulează un sistem de operare având cel puțin un nivel de API specificat de proprietatea android:minSdkVersion
în cadrul fișierului AndroidManifest.xml
, afișându-se lista acestora, astfel încât utilizatorul să își poată alege unul dintre ele.
Depanarea unei aplicații Android poate fi realizată în mod clasic, ca în cazul oricărui proiect (prin stabilirea unor puncte în care execuția codului sursă este întreruptă și rularea pas cu pas) sau prin intermediul unor utilitare specifice.
Android Debug Bridge este un utilitar în linie de comandă care permite comunicarea cu cu un dispozitiv mobil fizic sau cu un emulator, prin intermediul unui program client-server ce include 3 componente:
adb
(alți clienți sunt plugin-ul ADT, ADM-ul, Layout Inspector);
ADB este integrat în SDK-ul de Android, regăsindu-se în directorul platform-tools
.
Comenzi ADB
student@eim-lab:/opt/android-sdk-linux/platform-tools$ adb [-d|-e|-s <serialNumber>] <command>
adb
este important să fie cunoscut identificatorul dispozitivului care este conectat la serverul adb, acesta putând fi identificat prin comanda adb devices
:student@eim-lab:/opt/android-sdk-linux/platform-tools$ adb devices List of devices attached emulator-5556 device 192.168.56.101:5555 device 0123456789ABCDEF device
adb -s <serialNumber> shell
Depanarea este foarte importantă în procesul de realizare a aplicațiilor pentru dispozitive mobile. Există însă unele diferențe față de depanarea programelor pentru calculator, întrucât aplicațiile rulează pe un alt dispozitiv, fiind necesare programe specializate. De asemenea, fiind vorba de dispozitive mobile, apar și anumite evenimente specifice, cum ar fi apeluri telefonice, primirea unui mesaj, descărcărea bateriei, întreruperi ce trebuie tratate intr-un fel sau altul.
Jurnalele sistemului de operare conțin cele mai importante informații pentru programator. Acestea descriu toate acțiunile realizate de către dispozitivul mobil, excepțiile apărute precum și alte informații necesare depanării. Ele pot fi vizualizate în panoul denumit LogCat.
De regulă, la pornirea unei aplicații, utilizatorul este întrebat dacă dorește să monitorizeze mesajele provenind de la dispozitivul mobil, indicându-se și nivelul de prioritate prin care acestea vor fi filtrate (verbose
, debug
, info
, warning
, error
, assert
).
Ulterior, acest Window → Show View → Other… → Android → LogCat
Fiecare mesaj din această listă este insoțit de următoarele informații (fiecare pe cate o coloană):
Este de remarcat faptul că fiecare mesaj reprezintă câte o linie de text. În cazul excepțiilor, întrucât acestea conțin în general mai multe linii, cuprinzând foarte multă informație, ele vor fi reprezentate de mai multe mesaje.
Există două mecanisme prin care utilizatorii pot genera astfel de mesaje:
Log.[wdiea] (Log.DEBUG, "log sample", "this is a log message using 'log sample' tag");
Pentru a se evita specificarea priorității mesajului de fiecare dată, se pot utiliza metode specifice, care primesc ca parametrii doar denumirea etichetei și mesajul ce se dorește a fi jurnalizat:
Nivel de Prioritate | Metodă | Observații |
---|---|---|
ERROR | Log.e(…) | erori |
WARNING | Log.w(…) | avertismente |
INFO | Log.i(…) | mesaje de informare |
DEBUG | Log.d(…) | mesaje utilizate pentru depanare; pot fi filtrate (ignorate) |
VERBOSE | Log.v(…) | utilizate doar de programatori, pentru dezvoltarea aplicațiilor |
System.out.println("this is a message to the standard console");
Mesajele transmise sub această formă vor fi de tipul Information
și vor avea eticheta System.out
.
O altă funcționalitate importantă este posibilitatea de filtrare a mesajelor de tip jurnal. Deoarece LogCat afișează toate mesajele de log din sistem, urmărirea unor anumite mesaje poate fi dificilă. Pentru a facilita această sarcină, se pot genera filtre în funcție de anumite valori ale:
verbose
→ assert
)Un filtru se creează prin apăsarea butonului plus de culoare verde din bara panoului Log (respectiv LogCat în Eclipse). In Android Studio, LogCat se poate accesa din partea stânga jos a IDE-ului.
Se recomandă să se configureze un număr cât mai mare de mesaje de jurnalizare care să fie stocate în memoria tampon (Window → Preferences → Android → LogCat) întrucât după depășirea valorii respective, monitorizarea nu va mai fi posibilă.
Simularea unor evenimente de tip întrerupere pentru emulator
1. În contul Github personal, să se creeze un depozit denumit 'Laborator02'. Inițial, acesta trebuie să fie gol (nu trebuie să bifați nici adăugarea unui fișier README.md
, nici a fișierului .gitignore
sau a a fișierului LICENSE
).
2. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/eim-lab/Laborator02. În urma acestei operații, directorul Laborator02 va trebui să se conțină un director labtasks
ce va deține proiectul Android Studio denumit ActivityLifecycleMonitor
, fișierul README.md
și un fișier .gitignore
care indică tipurile de fișiere (extensiile) ignorate.
student@eim-lab:~$ git clone https://www.github.com/eim-lab/Laborator02.git
3. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator02' de pe contul Github personal.
student@eim-lab:~$ cd Laborator02 student@eim-lab:~/Laborator02$ git remote add Laborator02_perfectstudent https://github.com/perfectstudent/Laborator02 student@eim-lab:~/Laborator02$ git push Laborator02_perfectstudent master
4. Să se încarce în mediul integrat de dezvoltare Android Studio proiectul ActivityLifecycleMonitor
, folosind opțiunea Open an Existing Android Studio Project.
5. În clasa LifecycleMonitorActivity
din pachetul ro.pub.cs.systems.eim.lab02.activitylifecyclemonitor.graphicuserinterface
, să se suprascrie metodele care monitorizează ciclul de viață al unei activități; fiecare dintre acestea va trebui să apeleze metoda părinte și să notifice apelarea sa prin intermediul unui mesaj, având prioritatea DEBUG
și eticheta activitylifecyclemonitor
:
Log.d(Constants.TAG, "??? method was invoked");
onRestart()
onStart()
onResume()
onPause()
onStop()
onDestroy()
6. Să se creeze un filtru, denumit ActivityLifecycleMonitor
, astfel încât LogCat să afișeze doar mesajele care au eticheta activitylifecycle
, generate de aplicația ro.pub.systems.eim.lab02.activitylifecyclemonitor
și au cel puțin prioritatea debug
.
7. Să se modifice mesajul din metoda onCreate()
, astfel încât să se indice dacă activitatea a mai fost lansată în execuție anterior sau nu (dacă există o stare a activității care trebuie restaurată).
8. Să se inspecteze mesajele care sunt generate la producerea următoarelor evenimente:
student@eim-lab:/opt/android-sdk-linux/platform-tools$ adb devices List of devices attached 192.168.56.101:5555 device student@eim-lab:/opt/android-sdk-linux/platform-tools$ adb -s 192.168.56.101:5555 shell root@android:/ # am start -a android.intent.action.CALL tel:1122334455 Starting: Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxx }
Să se observe ce metode sunt apelate în momentul în care se revine în aplicație.
Pe baza mesajelor, să se completeze tabelul de mai jos, indicând ordinea în care s-au apelat metodele respective:
onCreate() | onRestart() | onStart() | onResume() | onPause() | onStop() | onDestroy() |
|
---|---|---|---|---|---|---|---|
1) buton Home | 3 | 4 | 1 | 2 | |||
2) buton Back | |||||||
3) buton OK din aplicație | |||||||
4) buton lista app | |||||||
5) apel telefonic | |||||||
a) acceptare | |||||||
b) respingere | |||||||
6) rotire ecran |
9. Să se dezactiveze opțiunea de salvare a stării.
activity_lifecycle_monitor.xml
, pentru fiecare dintre elementele grafice pentru care se dorește să se dezactiveze opțiunea de salvare a stării, se va completa proprietatea android:saveEnabled=“false”
.
Să se observe care este comportamentul în privința informațiilor reținute în elementele grafice de tip EditText
, respectiv CheckBox
, în condițiile în care activitatea este distrusă (se apasă butonul Home, astfel încât să se apeleze metodele onPause()
și onStop()
, apoi se închide aplicația. Să se repornească aplicația din meniul dispozitivului mobil.
10. Să se implementeze metoda onSaveInstanceState()
, astfel încât, în condițiile în care este bifat elementul grafic de tip CheckBox
, să se salveze informațiile din interfața cu utilizatorul.
putString()
și putBoolean()
ale clasei Bundle
.Constants
din pachetul ro.pub.cs.systems.eim.lab02.activitylifecyclemonitor.general
.CheckBox
este bifat se face prin intermediul metodei isChecked()
.
Să se observe comportamentul aplicației în condițiile producerii evenimentului descris anterior.
11. Să se implementeze metoda onRestoreInstanceState()
astfel încât să se restaureze starea elementelor grafice. Să se observe comportamentul aplicației în condițiile producerii evenimentului descris anterior.
Să se transfere comportamentul de restaurare a stării pe metoda onCreate()
și să se identifice diferențele de implementare (Hint).
12. Să se încarce modificările realizate în cadrul depozitului 'Laborator02' de pe contul Github personal, folosind un mesaj sugestiv.
student@eim-lab:~/Laborator02$ git add labtasks/* student@eim-lab:~/Laborator02$ git commit -m "implemented tasks for laboratory 02" student@eim-lab:~/Laborator02$ git push Laborator02_perfectstudent master
Joseph ANNUZZI, Jr, Lauren DARCEY, Shane CONDER, Introduction to Android Application Development - Developer's Library, 4th Edition, Addison-Wesley, 2013
Bill PHILLIPS, Brian HARDY, Android Programming. The Big Nerd Ranch Guide, Pearson Technology Group, 2013
Wei Meng LEE, Beginning Android 4 Application Development, Wiley, 2012
Satya KOMATINENI, Dave MACLEAN, Pro Android 4, Apress, 2012
Android Programming Tutorials - Core Servlets
Android Programming Tutorial - Tutorial's Point
Dezvoltarea aplicațiilor pentru Android
Saving (and Retrieving) Android Instance State
Avoiding Memory Leaks
Activities - Android Developers
Things that cannot change