Dezvoltarea unei aplica ții mobile pentru comunicare vocală și textuală prin intermediul internetului propusă de Dudu Mihai -Ștefan Sesiunea: iulie,… [605450]
UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAȘI
FACULTATEA DE INFORMATICĂ
LUCRARE DE LICENȚĂ
Dezvoltarea unei aplica ții mobile pentru
comunicare vocală și textuală prin
intermediul internetului
propusă de
Dudu Mihai -Ștefan
Sesiunea: iulie, 2016
Coordonator științific
Asist. Dr. Vasile Alaiba
2
UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAȘI
FACULTATEA DE INFORMATICĂ
Dezvoltarea unei aplica ții mobile pentru
comunicare vocală și textuală prin
intermediul internetului
Dudu Mihai -Ștefan
Sesiunea: iulie, 2016
Coordonator științific
Asist. Dr. Vasile Alaiba
3
DECLARAȚIE PRIVIND ORIGINALITATE ȘI RESPECTAREA
DREPTURILOR DE AUTOR
Prin prezenta declar că Lucrarea de licență cu titlul „ Dezvoltarea unei aplicații mobile pentru
comunicare vocală și textuală prin intermediul internetului ” este scrisă de mine și nu a mai fost
prezentată niciodată la o altă facultate sau instituție de înv ățământ superior din țară sau
străinătate. De asemenea, declar că toate sursele utilizate, inclusiv cele preluate de pe
Internet, sunt indicate în lucrare, cu respectarea regulilor de evitare a plagiatului:
toate fragmentele de text reproduse exact, chiar și în traducere proprie din altă limbă,
sunt scrise între ghilimele și dețin referința precisă a sursei;
reformularea în cuvinte proprii a textelor scrise de către alți autori deține referința
precisă;
codul sursă, imaginile etc. preluate din proiecte open -source sau alte surse sunt
utilizate cu respectarea drepturilor de autor și dețin referințe precise;
rezumarea ideilor altor autori precizează referința precisă la textul original.
Iași,
Absolvent: [anonimizat]
___________________________
4
DECLARAȚIE DE CONSIMȚĂMÂNT
Prin prezenta declar că sunt de acord ca Lucrarea de licență cu titlul „ Dezvoltarea unei
aplicații mobile pentru comunicare vocală și textuală prin intermediul interne tului ”, codul
sursă al programelor și celelalte conținuturi (grafice, multimedia, date de test etc.) care
însoțesc această lucrare să fie utilizate în cadrul Facultății de Informatică.
De asemenea, sunt de acord ca Facultatea de Informatică de la Universitatea „Alexandru
Ioan Cuza” Iași să utilizeze, modifice, reproducă și să distribuie în scopuri necomerciale
programele -calculator, format executabil și sursă, realizate de mine în cadrul prezentei
lucrări de licență.
Iași,
Absolvent: [anonimizat]
_________________________
5
Introducere și motiva ție
În această lucrare propun o soluție (atât pentru client cât ș i pentru server) pentru
dezvoltarea unei aplicații mobile care să faciliteze procesul de comunicare textuală și
vocală prin intermediu l internetului.
Popularitatea acestui tip aplicații a crescut enorm î n ultima vrem e, creșterea fiind
sustinută atât de dorinț a uti lizatorului de a comunica mai ușor ș i mai ieftin dar și de
investițiile dezvoltatorilor importanți de pe piața . Unele estimări arată că aplicațiile de
mesagerie au întrecut (ca numă r de utiliza tori activi lunar) chiar aplicațiile reț elelor
sociale1. Alți factor i importan ți sunt faptul ca acest tip de aplicație oferă utilizatorului
posibilitatea de a comunica cu al ți utilizatori afla ți chiar în alte țări fără a plăti taxe
suplimentare ca în cazul convorbirilor tradi ționale, aceste aplica ții utiliz ând internetul ca
suport și disponibilitatea internetului mobil este în continu ă creștere (re țelele wireless care
oferă internet gratuit au o disponibilitate foarte mare acum ).
Pentru a ajunge la un număr câ t mai mare de utilizatori de dispozitive mobile am
hotarâ t implementarea c lientului pe platforma Android întrucâ t statis ticile arată că Android
detine peste 70% din piaț a dispozitivelor mobile2.
Având astfel o idee despre popularitatea acestu i tip de aplicații este de așteptat ca în
eventualitatea intrării pe piață cu o astfel de aplicație aceasta trebuie să respecte anumite
cerințe ca să poată fi competitivă :
utilizarea aplicației să fie intuitivă ;
să accelereze procesul de comunicare;
să nu utilizeze prea multe resurse (resurse energetice limitate);
să fie stabilă în condiții de încă rcare mare.
În capitolele ce urmeaz ă am prezentat modali tățile identificate de mine a c ăror utilizare
duce la îndeplinirea cerin țelor enumerate.
1 Popularitatea aplica țiilor de mesagerie – http://www.businessinsider.com/the -messaging -app-report –
2015 -11
2 Popularitatea platformei Android – https://www.netmarketshare.com/operating -system -market –
share.aspx?qprid=8&qpcustomd=1
6
Contribu ții
Am decis împărțirea lucr ării în dou ă capitole mari care tratează , pe rând, procesul de
dezvoltare din cele două perspective (client și server) motivâ nd deciziile luate pe parcursul
procesului de dezvoltare, o ferind detalii de implementare și unele comparații între
implementă ri diferite ale aceleaș i componente:
Capitolul 1: Dezvoltarea aplicației client pe platforma Android
Capitolul 2: Dezvoltarea aplicației server
În procesul de dezvoltare a l clientului am analizat diverse tehnici pe ntru a minimiza
amprenta aplicaț iei asupra resurselor clientului dar fără să afecteze negativ calitatea
procesului de comunicare al utilizatorului și am ales metodele pe care le -am considerat
benefice:
Utilizarea fragmentelor în procesul de implementare al interfe ței grafice;
Utilizarea unui serviciu care rulează în fundal;
Încărcarea asincron ă a anumitor p ărți necesare interfe ței grafice;
Implementarea unui codec audio pentru a economisi banda de inter net;
Implementarea anumitor componente în C/C++ pentru a reduce consumul de
resurse.
Dezvoltarea aplicației server a avut în vedere faptul c ă este necesar ca aplicația să făcă față,
unui num ăr mare de utilizatori astfel c ă am analizat diverse posibilit ăți pentru a ob ține
acest lucru și am ajuns la o implementare ce utilizeaza:
Un server TCP asincron implementat cu epoll3;
Procesarea asincrona a cererilor;
Un sistem asincron de lansare și executare a interog ărilor.
Implementarea în mod asincron a diverselor componente a dus la cre șterea num ărului de
cereri procesate concurent de c ătre server, cresc ând astfel implicit și capacitatea serverului.
3epoll – http://linux.die.net/man/4/epoll
7
Cuprins
Introducere și motivație ………………………….. ………………………….. ……………………….. 5
Contribuții ………………………….. ………………………….. ………………………….. ………………. 6
Capitolul 1: Dezvoltarea aplicației client pe platforma Android ……………………… 9
1.1. Arhitectura generală a aplicației ………………………….. ………………………….. …. 9
1.2. Interfață grafică ………………………….. ………………………….. ……………………….. 10
1.2.1. Fereastra de autentificare/înregistrare ………………………….. …………….. 11
1.2.2. Fereastra principală ………………………….. ………………………….. …………… 12
1.2.3. Fereastra pentru setări ………………………….. ………………………….. ……….. 13
1.2.4. Fereastra pentru selectarea imaginii de profil ………………………….. ….. 13
1.2.5. Fereastra de apel ………………………….. ………………………….. ………………… 15
1.2.6. Fereastra de conversație text ………………………….. ………………………….. . 15
1.2.7. Incărcarea asincrona a imaginilor de profil ………………………….. …….. 16
1.3. Serviciu permanent ce rulează în fundal ………………………….. ………………… 17
1.4. Comunicarea între serviciu și activitate ………………………….. …………………. 19
1.5. Protocolul de comunicare între client și server ………………………….. ………. 20
1.6. Java Native Interface (JNI) ………………………….. ………………………….. ………. 21
1.6.1. Clientul TCP ………………………….. ………………………….. ……………………… 21
1.6.2. OpenSL ES pe platforma Android ………………………….. ………………….. 22
1.6.3. P reluarea datelor de la microfon și redarea acestora ……………………. 23
1.6.4. Comprimarea datelor audio ………………………….. ………………………….. .. 23
1.6.5. Concluzii ………………………….. ………………………….. ………………………….. .. 28
Capitolul 2: Dezvoltarea aplicației server ………………………….. ………………………… 30
2.1. Arhitectura generala a aplicației ………………………….. ………………………….. .. 30
2.2. Detalii de implementare ………………………….. ………………………….. ……………. 31
2.2.1. Serverul TCP ………………………….. ………………………….. …………………….. 31
2.2.1.1. Sincron versus asincron ………………………….. ………………………….. .. 31
2.2.1.2. Tratarea concurentă a clienților ………………………….. ……………….. 32
2.2.1.3. Implementare cu epo ll ………………………….. ………………………….. ….. 34
2.2.1.4. Recepționarea datelor în mod asincron ………………………….. ……… 35
8
2.2.1.5. Procesarea cererilor în mod asincron ………………………….. ………… 35
2.2.1.6. Expedierea datelor în mod asincron ………………………….. ………….. 35
2.2.1.7. Concluzii ………………………….. ………………………….. ……………………… 36
2.2.2. Baza de date persistentă ………………………….. ………………………….. ……… 36
2.2.2.1. Execuția clasică a interogărilor ………………………….. …………………. 36
2.2.2.2. Lansarea asincrona a interogărilor ………………………….. …………… 37
2.2.2.3. Interogări irelevante ………………………….. ………………………….. …….. 38
2.2.2.4. Concluzii ………………………….. ………………………….. ……………………… 38
2.2.3. Server HTTP pentru imaginile de profil ………………………….. ………….. 39
Concluzii generale ………………………….. ………………………….. ………………………….. …. 41
Bibliografie ………………………….. ………………………….. ………………………….. ……………. 42
9
Capitolul 1: Dezvoltarea aplicației client pe platforma Android
Aplicaț ia client are ca scop facilitarea procesului de comunicare textuală și vocală
între doi utilizatori de dispozitive mobile Android prin intermediul unei conexiuni la
internet. Această aplicație a fost dezvoltată cu ajutorul mediului de dezvoltare integrat
pentru platforma Android – Andro id Studio. Pe parcursul dezvoltării aplicației am urmă rit
utilizarea unor tehni ci specifice, descrise pe larg în subcapitolele următoare, astfel încât
aplicația finală să poată fi utilizata în condiții reale ș i consumul de resurse să fie redus.
1.1. Arhitectura generală a aplicației
Arhitectura unei aplicații de acest tip este diferită de cea a unei aplicații obișnuite.
Aplicaț ia client trebuie să fie capabil ă să ofere utilizatorului o experiență plăcută la
utilizare, să notifice utilizatorul de fiecare dată când este nevoie (în cazul în care acesta
primește un apel, mesaj sau cerere de contact) si fiindcă o as tfel de aplicație are, de obicei,
un timp î ndelungat de utilizare trebuie să folosească cu moderaț ie resursele puse la
dispoziț ie de dispozitivul utilizatorului întrucât este bine -cunoscut faptul că dispozitivele
mobile contemporane dispun de resurse energ etice relativ reduse.
Figura 1: Arhitectura generala a aplicației client
10
Pentru a î ndeplini obiectivele enumerate mai sus am recurs cateva tehnici destul de
frecvent utilizate în randul dezvoltatorilor de aplicatii mobile pentru platforma Android si
nu numai:
Utilizarea obiectelor de tip Fragment4 pentru interfață grafica;
Incarcarea asincrona a anumitor parti din interfață grafica;
Portarea unor porțiuni de cod din Java în C/C++ (cod nativ);
Utilizarea unui serviciu ce va rula în permanenta în fundal.
Astfel am ajuns la arhitectura ilustrata în Figura 1 :
1.2. Interfa ță grafic ă
Utilizatorul are la dispoziție o interfață grafic ă simpl ă și intuitiv ă, care utilizeaz ă pe
cât posibil pictograme sugestive și gesturile utilizatorului pentru a face nagivarea în cadrul
aplicației cât mai simpl ă și cursivă .
Pentru a pă stra consumul de resurse al aplicației client la un nivel cât mai scă zut am
recurs la utilizarea obiectelor de tip Fragment în detrimentul activităț ilor separate pentru
fiecare fereastră a aplicației . Obiectele de tip Fragment au o amprent ă mult mai pu țin
vizibil ă asupra consumului de resurse, fiind reutilizabile în cadrul aplicației , și permit
crearea cu ușurința a unei interfeț e grafice dinamice (de exemplu fereastra principală a
aplicației descrisă în subcapitolul 1.2.2 ) care poate chiar să î mbine mai multe fragmente în
cadrul aceleași activităț i. Similar activit ăților obiectele de tip Fragment au un ciclu de viață
propriu în cadrul aplicației dar reacționează și la evenimentele activității gazdă .
Pe lângă interfața grafică oferită de aplicație utilizatorul poate interacționa cu
aplicația și prin i ntermediul notifică rilor l ansate de se rviciul care rulează în permanență în
fundal . Acest serviciu poa te lansa trei tipuri de notifică ri:
Pentru mesaje primite;
Pentru apeluri primite (doar în cazul în care telefonul are ecranul aprins și este
deblocat);
Pentru cereri de contact.
Notifică rile pentru apeluri permit accept area sau respingerea apelului fără ca
utilizatorul să fie nevoit să -și întrerupă activitatea curentă .
4 Object Fragme nt – https://developer.android.com/training/basics/fragments/creating.html
11
1.2.1. Fereastra de autentificare /înregistrare
Fereastra de autentificare este fereastră de st art a aplicației client. Acestă fereastră
permite utilizatorului să se autentifice sau să -și creeze un nou cont. Î n cazul în care
utilizatorul este deja autentificat d într-o sesiunea anteriora (terminarea unei sesiuni se
poate face din fereastră principală din meniul lateral) fereastra d e autentificare va fi închisă
automat și va lansa fereastra principală .
Detaliile de autentificare sunt memorate în memoria persistent ă a dispozitivului prin
intermediul interfe ței SharedPreferences5. Verificarea existen ței unei sesiuni active se face
prin intermediul serviciului ce ruleaz ă în fundal . În cazul în care acest serviciu a fost închis
sau conexiunea TCP a fost inchis ă sesiunea se poate recupera efectu ând o reautentificare
cu detaliile salvate cu ajutorul interfe ței SharedPreferences .
Interfa ța SharedPreferences oferă acces spre citirea sau modificarea unor informații
salvate în prealabil. Pentru a p ăstra consisten ța valorilor modific ările acestora trebuie
efectuate prin intermediul obiectului SharedPreferences.Editor . Accesul unor anumite
valori se face prin apelul la func ții get specifice tipului de date.
În Secțiunea de cod 1 poate fi observat procesul de salvare persistent ă a datelor de
autentificare spre a fi utilizate ulterior la automatizarea autentific ării.
public void saveLoginState (Context context , String email , String
password ) {
SharedPreferences preferences =
context.getSharedPreferences ("detalii_login" , Context.MODE_PRIVATE );
SharedPreferences .Editor editor = preferences .edit();
editor.putString ("email", email);
editor.putString ("password" , password );
editor.commit();
}
Secțiunea de cod 1: Salvarea persistent ă a datelor de autentificare
În Secțiunea de cod 2 poate fi observat procesul de ob ținere a datelor de autentificare
salvate, dac ă este cazul.
5 SharedPreferences –
https://developer.android.com/reference/android/content/SharedPreferences.html
12
public void loadLoginSavedState (Context context ) {
SharedPreferences preferences =
context.getSharedPreferences ("detalii_login" , Context.MODE_PRIVATE );
String email = preferences .getString ("email", null);
String password = preferences .getString ("password" , null);
if (email == null || password == null) {
saveLoginState (context, null, null);
}
else {
mHasSavedLoginState = true;
mEmail = email;
mPassword = password ;
}
}
Secțiunea de cod 2: Ob ținerea unor date salvate
1.2.2. Fereastra principal ă
Fereastra principal ă folose ște tranzi ții de tipul screen -slider pentru a facilita
navigarea rapid ă și intuitiv ă între sec țiunile principale ale aplicației . Aceste tranzi ții sunt
ușor accesibile programatorului prin intermediul componentei ViewPager6 și utilizarea
obiectelor de tip Fragment din Android Support Library7.
Fiecare sec țiune vizibil ă în fereastr a principal ă are asociat un obiect de tip Fragment
care este responsabil pentru afi șarea con ținutului vizual corespunz ător sec țiunii sale.
Managementul tuturor acestor fragmente se face automat în cadrul componentei
ViewPager prin intermediul unui adaptor de tipul FragmentPagerAdapter8 modificat
corespunz ător necesit ăților aplicației curente. Acest adaptor este responsabil de
inițializarea tuturor fragmentelor pe baza unei pozi ții numerice (num ărul de ordine al
fragmentului curent). Fragmentele tuturor paginilor vizitate de utilizator sunt p ăstrate în
memorie , ceea ce ar putea rezulta într-un consum crescut de memorie în cazul în care am
avea un num ăr mare de pagini, dar ierarhie de viewuri poate fi distrus ă cand fragmentele
nu sunt vizibile [1].
Din fereastr a principal ă utilizatorul are acces și la un panou lateral care ofer ă
utilizatorului informații despre profilul s ău, acces la fereastr a pentru set ări dar și
posibilitatea de a p ărăsi sesiunea curent ă.
6 ViewPager – https://developer.android.com/reference/android/support/v4/view/Vi ewPager.html
7 Android Support Library – https://developer.android.com/topic/libraries/support -library/index.html
8 FragmentPageAdapter –
https://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
13
Figura 2: Panou lateral
1.2.3. Fereastra pentru set ări
Prin intermediul acestei ferestre utilizatorul are posibilitatea de a controla
urmatoarele set ări:
Rămânerea în fundal a aplicației după ce a fost închis ă – on/off
Salvarea datelor de logare – on/off
Setar ea sunetelor pentru notific are/apel
Persisten ța set ărilor este asigurat ă cu ajutorul interfe ței SharedPreferences care a fost
descris ă în detaliu în subcapitolul 1.2.1.
1.2.4. Fereastra pentru selectarea imaginii de profil
Aceast ă fereastră este co mpus ă din dou ă fragmente: unul d ă posibilitatea
utilizatorului s ă aleag ă sursa imaginii, al doilea ofer ă posibilitatea selectarii por țiunii din
imagine care va fi afi șată drept imagine de profil.
Utilizatorul are la dispoziție două surse din care poate alege imaginea de profil:
Galeria foto a dispozitivului
Camera foto a dispozitivului
În ambele cazuri dup ă selectarea/capturarea imaginii utilizatorului ii este afi șat fragmentul
în care trebuie s ă aleag ă o zon ă de dimensiune fix ă care să reprezinte imaginea sa de profil.
14
Figura 3: Selectarea zonei pentru imaginea de profil
Efectul de selec ție a fost creat prin intermediul unui obiect View personalizat9
(CustomImageSelectionView ). Mai exact am suprascris meto dele onTouchEvent , pentru ca
utilizatorul sa poata muta „zona de interes” a imaginii dupa cum dore ște, și onDraw pentru
a desena zona întunecat ă din exteriorul zonei de interes (Secțiunea de cod 3 ).
protected void onDraw(final Canvas canvas ) {
super.onDraw(canvas);
canvas.clipPath (mCirclePath , Region.Op.DIFFERENCE );
canvas.drawRect (0, 0, canvas.getWidth (), canvas.getHeight (),
mBackgroundPaint );
}
Secțiunea de cod 3: Desenarea zonei întunecate
A se observa ordinea efectu ării opera țiilor: întâi se creeaz ă o masc ă în form ă de cerc cu
parametrul DIFFERENCE dupa care este desenat un dreptunghi pe toat ă dimensiunea
obiectului Canvas10. Din cauza ma știi în form ă de cerc, în procesul de desenare a
dreptunghiului zon a ma știi va fi ignorat ă.
9 Obiect View personalizat – https://developer.android.com/training/custom -views/index.html
10 Obiect Canvas – https://developer.android.com/reference/android/graphics/Canvas.html
15
1.2.5. Fereastra de apel
Fereastra de apel este afi șata utilizatorului în trei cazuri:
Utilizatorul efectueaz ă un apel;
Utilizatorul primește un apel;
Utilizatorul este angajat intr -un apel activ.
În cazul în care utilizatorul efectueaz ă un apel sau este deja angajat într-un apel activ
ecranul dispozitivului va fi î nchis la apropierea acestuia de urechea utilizatorului, evitâ nd
astfel ap ăsarea accidental ă a vreunui control dar ajut ă și la economisirea energiei. Pentru a
realiza acest lucru este folosit un WakeLock11 care face uz de senzorul de proximitate al
dispozitivului pentru a închide/aprinde ecranul .
powerManager = (PowerManager ) getSystemService (POWER_SERVICE );
wakeLock =
powerManager .newWakeLock (PowerManager .PROXIMITY_SCREEN_OFF_WAKE_LOCK ,
getLocalClassName ());
wakeLock .setReferenceCounted (false);
wakeLock .acquire();
Secțiunea de cod 4: Crearea și obținerea unui WakeLock pentru închiderea ecranului
Pentru ca Secțiunea de cod 4 să poată fi folosit ă este necesar ă declararea unor
permisiuni speciale în fișierul manifest.
<uses-permission android:name ="android.permission.WAKE_LOCK" />
<uses-permission android:name ="android.permission.DISABLE_KEYGUARD" />
Secțiunea de cod 5: Permisiunile necesare blocarii/deblocarii ecranului
1.2.6. Fereastra de conversa ție text
Pentru a afi șa fundalul mesajelor text au fost folosite imagini 9 -patch12. Aceste
imagini permit redimensionarea fundalului f ără a introduce distorsion ări. Acest lucru este
posibil prin împărțirea imaginii originale în 9 regiuni (Figura 1.2.4. 2.).
Figura 4: Împărț irea pe regiuni a unei imagini 9 -patch
11 WakeLock – https://developer.android.com/reference/android/os/PowerManager.WakeLock.html
12 Imagini 9 -patch – https://software.intel.com/en -us/xdk/articles/android -splash -screens -using -nine-
patch -png
16
Regiunile numerotate cu 1, 3, 7 și 9 nu sunt redimensionate, regiunile numerotate cu
2 și 8 sunt redimensionate numai pe l ățime, regiunile numerotate cu 4 și 6 sunt
redimensionate numai pe înălțime iar regiunea numerot ata cu 5 este redimensionata atât pe
inălțime cât și pe lăț ime.
Imaginile 9 -patch utilizate de către această aplicație au fost generate cu ajutorul
utilitarului disponibil în suita de dezvoltare pentru platforma Android. Acest utilitar
introduce o linie de un pixel grosime, atât pe verticală cât și pe orizontală , pentru a putea
identifica cu ușurință cele 9 regiuni.
Utilizarea imaginilor 9 -patch p entru fundaluri nu este diferită de cea a imaginilor
obișnuite.
1.2.7. Incă rcarea asincrona a imaginilor de profil
Imaginile de profil sunt asociate independent fiecărui utilizator în parte. Aplicaț ia
client trebuie să procure imaginile de profil, corespunză toare persoanelor din lista de
contacte a utilizatorului curent, de la un server HTTP extern. Acest lucru ar pute a genera
întârzieri neprevă zute și nepredictibile în timpul încărcării interfeței grafice astfel că pentru
a evita blocarea aplicației am recurs la o metodă asincrona de incă rcare a acestor imagini.
În acest mod threadul responsabil cu interfața grafică nu este blocat pe durata descă rcării
imaginilor .
Încărcarea asincrona a imaginilor se face prin intermediul clasei singleton
ProfilePicManager . Această clasă are rolul de a manageria descă rcarea imaginilor d e pe
serverul HTTP extern prin î ncap sularea fiec arei cereri de descărcare î ntr-un obiect de tipul
Runnable13 și executarea lor î ntr-un thr ead pool de dimensiune variabilă, dimensiunea
maximă fiind de 4 threaduri.
În momentul în care este nec esară descă rcarea unei imagini (pentru a fi atribuită unui
obiec t de tipul ImageView14) se trimit că tre ProfilePicManager un id entificator numeric
reprezentând chiar identificatorul contactului (a că rei imagini de profil va fi descărcată ),
obiectul ImageView țintă și un callback ce va fi apelat în momentul în care procesul de
descărcare a imag inii a luat sfârșit. Pentru fiecare persoană din lista de contacte a
utilizatorului poate există o singură cerere activă pentru descă rcarea imaginii de profil la
un moment dat, astfel dacă mai multe obiecte ImageView au nevoie de aceeași imagine în
acela și timp nu este necesar ă crearea mai multor instan țe de obiecte ci doar adă ugarea lor
13 Obiect Runnable – https://developer.android.com/reference/java/lang/Runnable.html
14 Obiect ImageView – https://developer.android.com/reference/android/widget/I mageView.html
17
într-o listă de așteptare și la momentul terminarii procesului de descarcarea toate obiectele
de tipul ImageView din lista de așteptare vor fi actualizate corespunzator.
Pentru a nu împiedica Garbage Collectorul15 să colecteze obiectele de tipul
ImageView care poate nu mai sunt utilizate, dar se afl ă în lista de așteptare , vom re ține
obiectele ca WeakReference16. Acest lucru ne permite s ă reținem o list ă de obiecte
ImageView fără a ne face griji pentru eventuale probleme legate de Garbage Collector.
Dacă vreun obiect ImageView va fi colectat în timp ce se afl ă în lista de așteptare acesta va
deveni null. Astfel la momentul actualiz ării obiecte lor ImageView din list ă vom verifica
obiectele daca sunt null și le vom ignora.
Obiectele ImageView care necesit ă descărcarea unor imagini de profil provin de
regul ă dintr -un ListView ceea ce înseamn ă că este posibil ca aceste obiecte s ă fi fost
reciclate. Acest lucru duce la o problem ă destul de grav ă – este posibil ca la momentul
termin ării procesului de desc ărcare a unei imagini obiectul ImageView ce trebuie actualizat
să nu mai fie relevant. Pentru a verifica daca obiectul ImageView mai este relevant vom
utiliza etichete contin ând id entificatorul contactului c ăruia îi corespunde imaginea de
profil. Eticheta este setat ă în momentul cre ării obiectului ImageView (și actualizat ă la
reciclare) iar verificarea se va face înaintea actualiz ării obiectului din lista de așteptare .
Pentru actualizarea propriu -zisă a obiectelor ImageView este trimis ă o instan ță a
clasei ImageViewUpdateTask spre a fi rulat ă în cadrul threadului responsabil cu interfața
grafic ă.
1.3. Serviciu permanent ce ruleaz ă în fundal
Aplica ția client necesit ă o conexiune TCP deschis ă mereu pentru a putea primi în
timp real informații de la server (atunci c ând utilizatorul primește un mesaj, un apel, o
cerere de contact) și pentru a p ăstra un consum sc ăzut de resurse am decis delegarea
managementului acestei conexiuni unui serviciu ce ruleaz ă în fundal chiar dac ă activitatea
principal ă a aplicației este inchis ă.
Crearea unui serviciu se face prin crearea unei clase ce extinde clasa de baz ă
Service17. Aceast ă clasă de baz ă oferă posibilitatea suprascrierii unor callbackuri astfel
încât să putem trata corect evenimentele dorite pe durata de via ța a serviciului. Cele mai
importante callbackuri pentru aplicația de fa ță sunt:
15 Garbage Collector – https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
16 WeakReference – https://developer.android.com/reference/java/lang/ref/WeakReference.html
17 Service – https://developer.android.com/guide/components/services.html
18
onStartCommand ;
onBind .
Primul callback este apelat de sistem c ând o alt ă component ă a aplicației porne ște serviciul
printr -un apel la funcția startService . Cel de -al doilea callback este apelat cand o
component ă vrea s ă creeze o leg ătură cu serviciul curent prin intermediul unui apel la
funcția bindService . Acest callback trebuie s ă întoarc ă o interfața pe care cealalt ă
component ă să o poat ă utiliza în procesul de comunicare descris mai în detaliu în
următorul subcapitol.
Pe lângă crearea clasei serviciului este necesar ă declararea sa și în cadrul fi șierului
manifest:
<service android:name =".stalker.Stalker" android:enabled ="true" />
Secțiunea de cod 6: Declararea unui serviciu
Perioada de via ță a unui serviciu coincide deobicei cu durata de via ță a aplicației . În
cazul de față se dore ște păstrarea serviciului activ în fundal chiar și după închiderea
aplicației vizuale. Acest lucru este posibil prin specificarea valorii START_STICKY18 la
returnarea din funcția onStartCommand . Astfel dupa închiderea serviciului sistemul va
încerca s ă-l reporneasc ă. Închiderea serviciului ar putea rezulta din mai multe cauze:
aplicația a fost inchis ă;
sistemul opre ște serviciul pentru a conserva resurse.
Șansele ca serviciul s ă fie oprit pentru a conserva resurse ar putea fi sc ăzute considerabil
transform ând serviciul nostru într-un serviciu foreground19.
Un serviciu foreground, spre deosebire de un serviciu background, presupune faptul
că utilizatorul este con știent de faptul c ă serviciul este activ astfel c ă sistemul nu va mai
opri serviciul atât de ușor pentru a conserva resurse. Un serviciu foreground trebuie s ă
lanseze o notificare care nu poate fi ascuns ă sau inchis ă decât cand serviciul iese din
foreground sau este oprit. Trimiterea unui serviciu în foreground se face printr -un apel la
funcț ia startForeground() , iar pentru a scoate un serviciu din foreground se apeleaz ă
funcția stopForeground() . Func ția stopForeground primește ca parametru o valoare
boolean ă care indica faptul c ă notificarea trebuie ștears ă.
Pentru aplicația curent ă am decis utilizarea serviciului în background cât timp
aplica ția este pornit ă și trecerea sa în foreground la momentul închiderii aplicației .
18 START_STICKY –
https://developer.android.com/reference/android/app/Service.html#START_STICKY
19 Foreground Service – https://developer.android.com/guide/components/services.html#Foreground
19
Utilizatorul va primi o notificare la închiderea aplica îiei care îl va informa faptul c ă
serviciul este activ chi ar daca aplicația este închis ă.
1.4. Comunicarea între serviciu și activitate
Există mai multe modalita ți de a realiza comunicarea între o activitate și un serviciu.
În cazul aplicației curente este de reținut faptul c ă serviciul și activitațile rulează în cadrul
acelu iași proces ceea ce elimin ă necesitatea utilizării tehnicilor IPC20. Acest lucru aduce
două beneficii:
Procesul de comunicare este mult simplificat (din punct de vedere al
program ării);
Procesul de comunicare se realizeaz ă aproape instant ( evitând astfel eventuale
întârzieri în timpul utilizării aplicației ).
Legătura dintre activitate și serviciu se face printr -un apel la funcția bindService21
care primește ca parametru și un obiect de tipul ServiceConnection22 care ne oferă
informații cu privire la momentul stabilirii/ închiderii co nexiunii cu serviciul prin funcț iile:
onServiceConnected ;
onServiceDisconnected .
Mai departe trebuie suprascrise metodele onBind și onUnbind din clasa serviciului. În
cazul de față metoda onBind returnează o instanță a une i clase ce extinde clasa de bază
Binder23 și care oferă acces la instanța curentă a serviciului (Secțiunea de cod 6 ).
public class LocalBinder extends Binder {
public Stalker getService () {
return Stalker.this;
}
}
Secțiunea de cod 7: Declarare binder local
În urma stabilirii conexiunii activitatea va avea acces la instanța clasei serviciului
curent și va putea efectua apeluri către metodele publice ale acestei clase, ca la orice clasa
normală .
20 IPC – https://developer.android.com/guide/components/processes -and-threads.html#IPC
21 Bound services – https://developer.android.com/guide/components/bound -services.html
22 ServiceConnection –
https://developer.android.com/reference/android/content/ServiceConn ection.html
23 Binder – https://developer.android.com/reference/android/os/Binder.html
20
1.5. Protocolul de comunicare între client și server
Comunicarea între aplicația client și server se desfășoară prin interm ediul unui canal
TCP deschis atâ t timp cât utilizatorul este aute ntificat. Am ales ca mesajele să fie transmise
în format binar și nu text pentru a reduce cât mai mult dimensiunea acestora și pentru a
evita parsarea de text pe dispozitivul mobil întrucât aceast ă parsare ar fi introdus un cost de
timp și memorie relativ ridicat comparativ cu cel al interpret ării unui m esaj în format binar.
Un mesaj este format din:
Header cu dimensiunea de 8 octeți care conține dimensiunea mesajului și tipul
acestuia;
Conținutul mesajului cu dimensiunea de p ână la 4,294,967,287 de octeți .
Figura 5: Structura unui pachet de date (mesaj)
Transmisia tipuril or de date de baza ( int, boolean etc.) se face destul de ușor fiindcă
dimensiunea reprezent ării lor este cunoscut ă dar în cazul transmisiei datelor reprezentate
ca tablouri (de exemplu String ) este necesar ă prefixarea secven ței de date cu dimensiunea
acestora pentru a putea fi citite corect.
Construc ția acestor mesaje ( în cod denumite pachete) este realizat ă în cadrul claselor
specifice fiec ărui tip de pachet. În cazul mesajelor ce urmează a fi trimise către server
datele sunt scrise în format binar cu ajutorul unui obiect de tipul ByteArrayOutputStream24.
Obiectul de tipul OutgoingPacket rezultat în urma procesului de construc ție este, prin
intermediului serviciului ce rulează în background, introdus într-o listă de așteptare spre a
fi transmis serverului.
În cazul mesajelor primite de la server mesajul în format binar este memorat cu
ajutorul unui obiect de tipul ByteBuffer25 din care se extrag, în ordinea corespunz ătoare,
informațiile din pachet și sunt memorate pentru a fi utilizate ulterior.
24ByteArrayOutputStream –
https://docs.oracle.com/javase/7/docs/api/java/io/ByteArrayOutputStream.html
25 ByteBuffer – https://docs.oracle.com/javase/7/docs/api/java/nio/Byt eBuffer.html
21
1.6. Java Native Interface (JNI)
Există anumite situații în care implementarea unor anumite porțiuni din aplicație în
C/C++ oferă programatorului acces la anumite detalii de implementare și posibilit ăți mai
avansate de optimizare. În Java există posibilitatea utilizării porțiunilor de cod nativ prin
intermediul Java Native Interface26.
În aplicația curentă am considerat c ă următoarele componente ar putea beneficia de
pe urma implement ării în C/C++:
Clien tul TCP
AudioRecorder – responsabil cu preluarea datelor de la microfon
AudioPlayer – responsabil cu redarea datelor audio
Codecul G.711 – comprimarea datelor audio
În subcapitolele urm ătoare am descris implement ările componentelor iar la final, în
sectiunea de concluzii, vom vedea daca a meritat cre șterea complexita ții aplicației și în ce
masur ă.
1.6.1. Clientul TCP
Pentru această aplicație am ales utilizarea unui canal de comunicare TCP în
detrimentul UDP din cauza faptului că rețelele mobile sunt, de re gulă, mai instabile și
foarte diversificate ca și parametri de securitate ceea ce ar fi putut face protocolul UDP
inutilizabil în unele cazuri (probleme cu pac hetele pierdute sau ajunse în ordine gresit ă,
restrictii privind utilizarea porturilor etc.).
Implementarea unui client TCP în C/C++ pe platforma Android nu este diferit ă de
cea a unui client obi șnuit pentru sistemul de operare Linux. Comunicarea între clientul
TCP (C/C++) și aplicație (Java) se face prin intermediul unor callbackuri setate la
mome ntul ini țializării clientului.
Am optat pentru folosirea în mod blocant a socketului, ajutat de dou ă threaduri –
unul dedicat opera țiilor recv și celălalt pentru send. Threadul pentru send are la dispoziție o
coadă de așteptare din care extrage datele ce t rebuiesc trimise pe re țea. Datele sunt
introduse în aceast ă coadă prin apelarea func ției Send a clasei CTcpClient (pachetele nu
sunt trimise imediat pentru a evita blocarea apelantului pe durata trimiterii) .
26 Java Native Interface –
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/intro.html#wp725
22
1.6.2. OpenSL ES pe platforma Android
OpenSL ES este o interfața de programare audio (în C) standardizata care oferă
performan ță ridicat ă și laten ță scăzută pentru a accesa func ționalit ățile audio ale
dispozitivelor mobile în cadrul aplica țiilor native27.
Func ționalit ățile oferite de OpenSL ES sunt disponibile pe platforma Android
încep ând cu versiunea 2.3 și sunt similare cu cele oferite de interfe țele de programare
(scrise în Java)28:
android. Media.MediaPlayer29;
android.Media.MediaRecorder30.
În subcapitolele urm ătoare vom vedea cum pot fi folosite func ționalit ățile de
înregistrare și redare de sunet din OpenSL ES și ce eventuale beneficii ar putea aduce
comparativ cu implement ările lor disponibile deja în Java.
Un lucru important de reținut este c ă obiectele OpenSL ES sunt accesibile doar p rin
intermediul interfe țelor de tipul SLObjectItf . Obiectele OpenSL ES trebuiesc distruse
(Secțiunea de cod 9 ) în ordinea inve rsă creării astfel încât să nu fie distruse obiecte încă
utilizate. Android OpenSL ES nu oferă niciun mecanism de detec ție a utilizării incorecte a
interfe țelor obiectelor și se poate ajunge în unele cazuri ca aplicația sa aiba un
comportament nedefinit sau chiar s ă înceteze s ă mai funcț ioneze31. Ultimul obiect distrus
este obiectul engine.
În Secțiunea de cod 8 avem un exemplu d e creare și inițializare a obiectului engine:
slCreateEngine (&engineObject , 0, NULL, 0, NULL, NULL);
(*engineObject )->Realize(engineObject , SL_BOOLEAN_FALSE );
(*engineObject )->GetInterface (engineObject , SL_IID_ENGINE , &engineItf );
Secțiunea de cod 8: Crearea și inițializarea obiectului engine
Variabila engineObject este de tipul SLObjectItf iar variabila engineItf este de tipul
SLEngineItf . Crearea altor obiecte se face ulterior prin intermediul interfe ței către obiectul
engine ( engineItf ).
(*engineObject )->Destroy(engineObject );
engineObject = NULL;
engineItf = NULL;
Secțiunea de cod 9: Distrugerea unui obiect și invalidarea referin țelor
27 OpenSL ES – https://www.khronos.org/opensles/
28 OpenSL ES pe platforma Android – https://developer.android.com/ndk/guides/audio/opensl -for-
android.htm
29 MediaPlayer – https://developer.android.com/reference/android/media/MediaPlayer.html
30 MediaRecorder – https://developer.android.com/reference/android/media/MediaRecorder.html
31 Distrugerea obiectelor OpenSL ES – https://developer.android .com/ndk/guides/audio/opensl -prog-
notes.html#destroy
23
Având în vedere c ă manipularea obiectelor OpenSL ES este destul de greoaie în cod
și duce ușor la confuzii am hot ărât încapsularea detaliilor de implementare în două clase cu
denumiri sugestive:
CAudioRecorder – preluarea datelor de la microfon;
CAudioPlayer – redarea datelor.
1.6.3. Preluarea datelor de la microfon și redarea acestora
Implementarea în C/C++ prin intermediul OpenSL ES ne d ă posibilitatea de a
controla managementul bufferelor ce urmează a fi populate cu date de la microfon. At ât
dimensiunea bufferelor cât și numărul lor poate afecta laten ța sunetului astfel c ă trebuie
incercate diferite set ări până se ajunge la ni ște valori convenabile.
Implementarea cozii pus ă la dispoziție de platofrma Android permite setarea unui
callback care este apelat dup ă ce un buffer a fost utilizat ceea ce ne permite sa -l proces ăm
cu ușurință. În cazul de față procesarea bufferului const ă în codarea acestuia cu codecul
G.711 și introducerea sa într-o coadă de așteptare din care va ajunge prin JNI într-un
callback din Java. Am luat decizia implement ării cozii de așteptare în cazul datelor audio
înregistrate deoarece codul din interiorul callbackului trebuie s ă fie executat cât mai rapid
și cu o durat ă cât mai predictibil ă altfel vor ap ărea efecte nepl ăcute cum ar fi întârzieri sau
întreruperi în procesul de înregistrare32, utiliz ând coad a de așteptare datele vor fi
consumate prin intermediul unui alt thread elimin ând astfel pauza necesar ă consum ării
bufferelor .
După ce sunt înregistrate datele sunt trimise printr -un callback din Java către server
apoi serverul trimite mai departe datele către destinatar. Pe partea receptorului datele sunt
trimise prin JNI către clasa CAudioPlayer spre a fi redate. Similar ca în cazul înregistr ării
datelor avem acces la o coadă în care sunt introduse datele ce urmează a fi redate cu
mentiunea ca în cazul nostru înainte de introducerea datelor în coadă este necasar ă
decodarea lor utiliz ând codecul G.711 .
1.6.4. Comprimarea datelor audio
În timpul unei convorbiri audio se genereaz ă un trafic foarte mare pe re țea datorit ă
cantit ății mari de date audio ce trebuiesc transmise între interlocutori . Acest trafic se
reflect ă într-un consum ridicat de resurse (implicit cre ște și consumul energetic) și în cazul
folosirii unei conexiuni limitate la internet factura clientului poate sa creasc ă.
32 Callbackuri – https://developer.android.com/ndk/guides/audio/opensl -prog-notes.html#callbacks
24
Pentru a rezolva aceast ă problem ă în cazul sistemelor de comunica ție tradi ționale s -a
recurs la utilizarea unor codecuri , capabile sa comprime datele audio și chiar să crească
gradul de calitate al convorbirii (prin eliminarea zgomotelor) dar trebuie luat ă în
considerare și viteza de codare/decodare a codecului astfel încât implementarea lui s ă fie
benefic ă utilizatorului și din punctul de vedere al resurselor computa ționale utilizate .
Pentru aceast ă aplicație am decis utilizarea codecului G.71133 varianta cu compresie
A-Law, fiind în acest moment codecul standard utilizat în Europa pentru convorbirile
telefonice34 și pentru c ă oferă vitez ă mare de codare/decodare. Un alt motiv important
pentru care am ales acest codec este disponibilitatea sa spre implementare inca din 1972.
Acest codec mai este reg ăsit și sub numele de „ Pulse Modulation Code of voice
frequencies ”.34
Metoda de compresie A -Law este descris ă de Ecuația 1, unde A este numit
parametru de compresie și are valoarea 87.6 în Europa33, și 𝑥 este reprezentarea
normalizat ă a valorii ce urmează a fi comprimat ă.
𝐹(𝑥)=
{ 𝐴∗|𝑥|
1+ln(𝐴),0≤|𝑥|<1
𝐴
𝑠𝑒𝑚𝑛(𝑥)∗(1+ln(𝐴∗|𝑥|))
1+ln(𝐴),1
𝐴≤|𝑥|≤1
Ecuația 1: Ecuația de compresie A -Law
În Tabelul 1 poate fi observat tabelul de codare A -Law. Prima coloan ă conține datele
de intrare în format liniar pe 13 bi ți (fiind complementul față de 235 pe 13 bi ți) și a doua
coloan ă conține datele codate pe 8 b iți. Biții de pe pozi țiile marcat e cu X vor fi ignora ți.
Date de intrare Date codate cu A -Law
S 0 0 0 0 0 0 0 A B C D X S 0 0 0 A B C D
S 0 0 0 0 0 0 1 A B C D X S 0 0 1 A B C D
S 0 0 0 0 0 1 A B C D X X S 0 1 0 A B C D
S 0 0 0 0 1 A B C D X X X S 0 1 1 A B C D
S 0 0 0 1 A B C D X X X X S 1 0 0 A B C D
S 0 0 1 A B C D X X X X X S 1 0 1 A B C D
S 0 1 A B C D X X X X X X S 1 1 0 A B C D
S 1 A B C D X X X X X X X S 1 1 1 A B C D
Tabelul 1: Codare A -Law
33 Codec G.711 – http://www.en.voipforo.com/codec/codecs -g711 -alaw.php
34 Detalii despre utilizarea codecului G.711 – https://en.wikipedia.org/wiki/G.711
35 Complement față de 2 – https://en.wikipedia.org/wiki/Two%27s_complement
25
Implementarea procesului de codare conform datelor din Tabelul 1 este destul de simpl ă:
inline static void encode(const short * src, int len, unsigned char * dst) {
short pcm, exponent , mask, mantissa ;
unsigned char sign;
for (int i = 0; i < len; ++i) {
pcm = src[i];
sign = (pcm & 0x8000) >> 8;
if (sign != 0) {
pcm = -pcm;
}
if (pcm > MAX) {
pcm = MAX;
}
// extrage exponentul
exponent = 7;
mask = 0x4000;
while (((pcm & mask) == 0) && (exponent > 0)) {
–exponent ;
mask >>= 1;
}
// extrage mantisa
if (exponent == 0) {
mantissa = pcm >> 4;
}
else {
mantissa = pcm >> ((exponent + 3) & 0x0F);
}
// compune valoarea alaw
unsigned char alaw = (unsigned char)(sign | exponent << 4 | mantissa );
dst[i] = alaw ^ 0xD5;
}
}
Secțiunea de cod 10: Implementarea codarii G.711 A -Law în C
În Secțiunea de cod 6 se pot observa cei trei pa și principali în procesul de codare. La pasul
de extragere a exponentului este c ăutat primul bit setat pe 1 dupa bitul de semn . La găsirea
primului bit ne oprim și setăm exponentul ca fiind pozi ția bitului respectiv (num ărând
descresc ător, bitul de semn av ând pozi ția 8, urm ătorul bit la dreapta pozi ția 7 și tot așa).
Mantisa este reprezentat ă de urmatorii 4 bi ți de dup ă bitul care a dat exponentul. Pentru a
extrage mantisa mutăm to ți biții la dreapta cu (exponent + 3) pozi ții și extragem primii 4
biți. În cazul în care exponentul este 0 mutăm toți biții la dreapta cu 4 pozi ții.
26
Procesul de decodare A -Law36 este descris de Ecuația 2, unde A este parametrul de
compresie ( în Europa A = 87.6), iar y este reprezentarea normalizat ă e valorii ce urmează a
fi decodat ă.
𝐹−1(𝑦)=𝑠𝑒𝑚𝑛(𝑦)∗
{ |𝑦|∗(1+ln(𝐴))
𝐴,|𝑦|<1
1+ln(𝐴)
exp(|𝑦|∗(1+ln(𝐴))−1)
𝐴,1
1+ln(𝐴)≤|𝑦|<1
Ecuația 2: Ecuația de decompresie A -Law
În Tabelul 2 poate fi observat procesul de decodare A -Law.
Date de intrare codate Date de iesire liniare
S 0 0 0 A B C D S 0 0 0 0 0 0 0 A B C D 1
S 0 0 1 A B C D S 0 0 0 0 0 0 1 A B C D 1
S 0 1 0 A B C D S 0 0 0 0 0 1 A B C D 1 0
S 0 1 1 A B C D S 0 0 0 0 1 A B C D 1 0 0
S 1 0 0 A B C D S 0 0 0 1 A B C D 1 0 0 0
S 1 0 1 A B C D S 0 0 1 A B C D 1 0 0 0 0
S 1 1 0 A B C D S 0 1 A B C D 1 0 0 0 0 0
S 1 1 1 A B C D S 1 A B C D 1 0 0 0 0 0 0
Tabelul 2: Decodare A -Law
Implementarea procesului de decodare se rezum ă doar la a extrage exponentul și mantisa
setate la pasul de codare, setarea primului bit de dupa mantisa pe 1 și așezarea în ordine
într-o variabil ă de tip short.
36 Procesul de decodare A -Law – https://en.wikipedia.org/wiki/A -law_algorithm
27
inline static void decode(const unsigned char * src, int len, short * dst) {
unsigned char alaw, sign;
short exponent , data;
for (int i = 0; i < len; ++i) {
alaw = src[i] ^ 0xD5;
sign = alaw & 0x80;
exponent = (alaw & 0x70) >> 4;
data = alaw & 0x0f;
data <<= 4;
data += 8;
if (exponent != 0) { data += 0x100; }
if (exponent > 1) { data <<= (exponent – 1); }
if (sign == 0) { dst[i] = data; }
else { dst[i] = -data; }
}
}
Secțiunea de cod 11: Implementarea procesului de decodare G.711 A -Law în C
Se poate observa c ă procesul de decodare expandeaz ă o valoare de tip byte la o
valoare de tip short . Asta înseamn ă că procesul de decodare ar putea fi redus la o c ăutare
într-o tabel ă cu valori precalculate, evitând astfel efectuarea inutil ă a unor calcule. În urma
optimiz ării pasul de decodare devine:
inline static void decode_optimized (const unsigned char * src, int len, short * dst)
{
for (int i = 0; i < len; ++i) {
dst[i] = cached_alaw_to_linear [src[i]];
}
}
Secțiunea de cod 12: Procesul de codare optimizat cu tabel ă de valori precalculate
Unde cached_alaw_to_linear reprezint ă o tabel ă cu 256 de v alori de tip short precalculate .
Având în vedere c ă opera țiile efectuate pentru a coda/decoda datele sunt
preponderent opera ții pe bi ți am suspectat faptul c ă implementarea codecului în C ar putea
reduce timpul de executie astfel c ă am conceput o serie de teste pentru a m ăsura num ărul
de opera ții (de codare/decodare) executate în fiecare secund ă în cazurile în care codecul
este implementat în Java sau în C/C++.
Codul testat este similar pentru cele dou ă limbaje cu men țiunea c ă versiunea pentru
Java a fost incapsulat ă într-o clasă cu metode statice.
28
02000004000006000008000001000000
Java C/C++ (JNI)Normal
Grafic ul 1: Num ăr de codari pe secund ă
02000004000006000008000001000000120000014000001600000
Java C/C++ (JNI)Normal
Optimizat cu
valori
precalculate
Grafic ul 2: Num ăr de decodari pe secund ă
Conform rezultatelor ob ținute observ ăm un ca știg de performan ță de peste 100%
datorat implement ării în C/C++ . Testele rulate nu au luat în calcul costul apelurilor JNI dar
acesta este irelevant av ând în vedere c p apelurile către codec vor fi facute din clasele
CAudioRecorder și CAudioPlayer implementate de asemenea în C/C++. Mașina gazd ă pe
care au fost rulate testele este un telefon emulat37.
1.6.5. Concluzii
În urma implement ării și testarii componentelor descrise în subcapitolele anterioare
am v ăzut că implementarea unor componente intr -un limbaj de nivel sc ăzut și apelarea lor
prin JNI poate fi benefic ă în cazul în care avem nevoie de un plus de performan ță pentru
anumite metode utilizate frecvent (de exemplu codecu l G.711), dar este foarte posibil ca
beneficiile ob ținute sa nu merite efortul cre șterii complexita ții aplicației .
În cazul claselor CAudioRecorder și CAudioPlayer obținem, prin implementarea lor
în C/C++, acces la detaliile de implementare ceea ce ne perm ite s ă configuram
componentele astfel inc ât să obtinem o întârziere minim ă a sunetului dar diferen ța dintre
clasele din Java puse la dispoziție de platforma Android și implementarea curentă în
37 Specifica ții telefon emulat – Intel Atom (x86) , 1 GB RAM, sistem de operare Android 5.0.1. Ma șina
gazd ă a emulatorului dispune de un procesor Intel i7 4790k 4GHz si 16 GB RAM.
29
C/C++ nu este semnificativ ă, în schimb complexitatea aplicației creșste destul de mult
(ingreun ând mult și procesul de debug) .
În concluzie implementarea componentelor CAudioRecorder , CAudioPlayer și a
codecului G.711 în C/C++ aduce beneficii aplicației curente (av ând în vedere ca apelurile
către metodele de codare/de codare a codecului pot fi optimizate de compilator ul C/C++ ,
nefiind astfel nevoie de apeluri costisitoare prin JNI).
30
Capitolul 2: Dezvoltarea aplicației server
Aplica ția server a unui serviciu de comunicare în timp real trebuie s ă facă față cu
ușurință unui num ăr mare de clien ți conecta ți simultan și să onoreze cererile acestora cât
mai repede pentru a evita apari ția unor întârzieri neplăcute în timpul comunic ării (de
exemplu c omunicare audio) dintre doi clien ți.
În ideea de a îndeplini acest obiectiv am recurs la implementarea serverului în C/C++
pe sistemul de operare Linux pentru a avea acces la cât mai multe detalii de implementare
și optimizare.
2.1. Arhitectura generala a aplicației
Figura 6: Arhitectura generala a aplicației server
31
În Figura 4 sistemul asincron de lansare și executare a interogarilor este reprezentat
sumar. Mai multe detalii despre acest sistem se afl ă în subcapitolul 2.2.3.
Serverul HTTP extern este folosit pentru a servi c lienților imaginile de profil ale
utilizatorilor în funcție de un identificator numeric (mai multe detalii în subcapitolul 2.2.4 ).
2.2. Detalii de implementare
Serverul are doua componente: una implementat ă în C/C++ și cealalta (serverul
HTTP extern) implementat ă în Javascript pentru platforma node.js. În subcapitolele
următoare vor fi prezentate cele mai importante componente ale aplicației server, detalii
legate de implement ările lor și eventuale imbunata țiri aduse.
2.2.1. Server ul TCP
Există mai multe modalit ăți de a implementa un server TCP. Dac ă luăm în
considerare modul de utilizare a socke ților avem urmoatoarea clasificare:
sincron;
asincron.
În subcapitolele urm ătoare vom vedea diferen țele între sincron și asincron, avantajele și
dezavantajele fiecărui a și modalit ăți de monitorizare a socke ților pentru a putea decide
modelul de server care ar performa cel mai bine în cazul aplicației curente.
2.2.1.1. Sincron versus asincron
Utilizarea socke ților în mod sincron presupune ca threadul care efectueaz ă un apel
către o func ție (de exemplu recv) ce utilizeaz ă un socket anume s ă fie blocat pana c ând are
loc un eveniment pentru acel socket. În cazul func ției recv threadul va fi blocat p ână măcar
un octet est e citit în buffer sau are loc un eveniment (eroare de exemplu).
Modul asincron nu blocheaz ă threadul ci returnează imediat fie -1 fie informațiile
disponibile . În cazul în care este returnat -1 în variabila errno va fi pus codul de eroare. În
plus față de codurile obi șnuite de eroare valabile pentru socketi mai avem și EAGAIN sau
EWOULDBLOCK care înseamn ă că informațiile sau socketul nu sunt disponibile și ar fi
trebuit blocat ă execu ția. Setarea unui socket în modul neblocant se face astfel (unde sfd
este descriptorul) :
32
int flags = fcntl (sfd, F_GETFL, 0);
if (flags == -1) {
return;
}
flags |= O_NONBLOCK ;
if (fcntl (sfd, F_SETFL, flags) == -1) {
return;
}
Secțiunea de cod 13: Setarea unui descriptor în modul neblocant
Este evident c ă modul asincron deschide noi posibilit ăți de procesare concurent ă a
evenimentelor socke ților dar ridic ă alte probleme descrise în subcapitolele urm ătoare.
2.2.1.2. Tratarea concurent ă a clien ților
Având în vedere natura aplicației este imperios necesar ă tratarea în mod concurent a
clien ților. Cele mai populare modele de server capabil s ă trateze clien ții în mod concurent
sunt:
Cate un thread pe conexiune;
Cate un proces copil pe conexiune( fork38);
Prethreaded – threadurile sunt create în prealabil ;
Preforked – procesele copil sunt create în prealabil .
Aceste patru modele sunt folosite frecvent în practic ă și sunt eficiente în cazul în care nu
este necesar ă monitorizarea unui num ăr mare de socke ți. Ultimele dou ă modele reprezint ă
îmbun ătățiri ale primelor dou ă întrucât este eliminat costul cre ării unui thread/proces copil
în momentul stabilirii unei noi conexiuni. În ciuda imbun ătățirilor aduse de ultimele dou ă
modele n iciunul din cele patru nu va scala bine pentru numere mari (peste 1000) de socketi
din cauz ă că resursele sistemului ar fi folosite în mare parte pentru managementul
threadurilor și al proceselor copil create iar procesarea propriu -zisă ar fi întârziată
semnificativ, ceea ce nu este a cceptabil într-o aplicație care oferă comunicare în timp real.
Pentru a combate aceast ă problem ă au fost introduse diverse sisteme de
monitorizare a socketilor. În Linux cele mai populare sisteme sunt:
select39;
poll40;
38 fork() – http://linux.die.net/man/2/fork
39 Documenta ție select() – http://man7.org/linux/man -pages/man2/select.2.html
40 Documentaț ie poll() – http://man7.org/linux/man -pages/man2/poll.2.html
33
epoll41.
Mecanismul select permite monitorizarea mai multor socke ți în acela și timp. Apelul către
select blocheaz ă până când cel pu țin unul dintre socke ții monitoriza ți este „pregatit” pentru
a fi procesat sau apelul este întrerup de un semnal sau expir ă timpul alocat apelului . O
limitare destul de important ă a acestui sistem este faptul c ă este posibil ă monitorizarea
descriptorilor mai mici decat FD_SETSIZE (valoare implicita 1024) .
În cele mai multe cazuri mecanismul select va face față fără probleme însă în cazul
de față limita de 1024 de descriptori limiteaz ă drastic capacitatea serverului. Chiar daca ar
fi marit ă valoarea FD_SETSIZE tot ar există probleme din cauza modului în care este
definit ă structura fd_set :
typedef struct {
long int fds_bits [32];
} fd_set;
Secțiunea de cod 14: Defini ția structurii fd_set
Variabila fds_bits nu poate ține eviden ța a mai mult de 1024 de descriptori.
În cazul în care aceast ă limit ă este prea mic ă este preferat ă utilizarea mecanismului
poll. Acest mecanism nu prezint ă nicio limit ă referitor la num ărul de descriptori ce pot fi
monitoriza ți, sarcina aloc ării listei de descriptori revenind utilizatorului, însă nu este foarte
portabil.
Mecanismul epoll este cel mai recent mecanism de acest gen in trodus în Linux.
Comportamentul s ău este similar cu cel al mecanismului poll cu men țiunea c ă poate
funcționa at ât în modul edge -triggered cât și în modul level -triggered. Pentru a înțelege
diferen ța între cele dou ă moduri voi folosi un exemplu similar cu c el dat de dezvoltator:
1. Adaug un descriptor într-o instanță epoll spre a fi monitorizat;
2. Descriptorul introdus este gata pentru citire (200 octeți );
3. Descriptorul este returnat de un apel la funcția epoll_wait ;
4. Citesc 100 de octeți ;
5. Apelez epoll_wait .
În cazul edge -triggered apelul spre epoll_wait de la pasul 5 va bloca, în ciuda faptului c ă
există inca 100 de octeți pentru acel descriptor care ar putea fi citi ți. Acest eveniment ar
putea duce la blocaje majore pe partea clientului care ar putea astepta u n raspuns de la
server (care este blocat desi are datele necesare disponibile).
41 Documenta ție epoll() – http://man7.org/linux/man -pages/man7/epoll.7.html
34
În cazul utilizării în modul level -triggered epoll se va comporta similar cu poll.
Este de reținut faptul c ă apelul epoll_wait va returna doar descriptorii care sunt gata de
procesare ceea ce reprezint ă o imbunata țire major ă în cazul unui server care are un num ăr
mare de conexiuni deschise dar majoritatea au o activitate redus ă.
2.2.1.3. Implementare cu epoll
Pentru a simplifica structura codului am încapsulat în clasa CEpoll funcționalit ățile
oferite de mecanismul epoll . Tratarea evenimentelor descriptorilor se face printr -un obiect
ce implementeaz ă interfață IEpollEventsListener (Secțiunea de cod 15 ).
class IEpollEventsListener {
public:
virtual void OnReadReady (const epoll_event & event) = 0;
virtual void OnReadyToWrite (const epoll_event & event) = 0;
virtual void OnClose(const epoll_event & event) = 0;
};
Secțiunea de cod 15: Declara ția interfe ței IEpollEventsListener
Bucla principal ă de tratare a evenimentelor cu ajutorul mecanismului epoll arată astfel:
inline void CEpoll::Worker()
{
epoll_event * events = static_cast <epoll_event *>(calloc(16,
sizeof(epoll_event )));
epoll_event * current_event = nullptr;
while (_running ) {
int n = epoll_wait (_epoll_fd , events, 16, 0);
for (int i = 0; i < n; ++i)
{
current_event = &events[i];
if (_eventsListener != nullptr)
{
if (current_event ->events & EPOLLIN)
{
_eventsListener ->OnReadReady (*current_event );
}
if (current_event ->events & EPOLLOUT )
{
_eventsListener ->OnReadyToWrite (*current_event );
}
// …
}
}
}
delete [] events;
}
Secțiunea de cod 16: Bucla principala de tratare a evenimentelor
35
2.2.1.4. Recepț ionarea datelor în mod asincron
Rece pționarea datelor în mod asincron ridica o problema logistica – datele pot fi
recep ționate partial, fragmentate sau integral.
Ca solu ție la aceast ă problem ă am implementat clasa CIncomingPacket care are
capacitatea de a reconstrui un pachet de date din fragmentele sale. Fiecare utilizator
conectat are asociat ă o instanță a acestei clase denumit ă „pending incoming packet”.
Aceast ă instanță retine datele recep ționate incomplet p ână în prezent. Cand datele sunt
recep ționate complet instanț a acestei clase este trimis ă sistemului de procesare a cererilor
și instanț a „pending incoming packet” este reinitializata astfel încât datele recep ționate
ulterior sa fie și ele reconstruite corect în pachete.
Reconstruc ția pachetelo r este posibil ă pentru c ă stim dimensiunea ini țială a
pachetului (vezi subcapitolul 1.5). Pe masur ă ce primim fragmente dintr -un pachet este
actualizat un indicator care ține minte pozi ția curentă în buffer. C ând indicatorul ajunge la
dimensiunea cunoscut ă a pachetului înseamn ă că pachetul este complet și că poate fi
procesat.
2.2.1.5. Procesarea cererilor în mod asincron
Pentru procesarea în mod asincron a cererilor am recurs la implementarea unui
thread pool care utilizeaz ă o coadă blocant ă de așteptare . Cand o cerere este introdusa în
coad a de așteptare un thread este notificat pentru a o procesa.
Având în vedere natura aplicației este posibil să apară situații de desincronizare a
accesului asupra acelea și instan țe a unui obiect astfel c ă a fost necesar ă sincronizarea
anumitor par ți. Pentru a minimiza impactul negativ asupra performan ței a sincroniz ării am
recurs la utilizarea obiectelor de tipul std::atomic<T>42 cât de mult posibil.
Obiectele de tipul std::atomic<T> pot încapsula alte tipuri de date și oferă intr-un
mod neblocant acces la o variabil ă în cadrul programarii concurente.
2.2.1.6. Expedierea datelor în mod asincron
Similar recep ționării datelor există posibilitatea ca datele s ă nu poată fi expediate
intr-un singur apel spre funcția send ceea ce impune implementarea unui sistem similar cu
cel din subcapitolul 2.2.1.4 pentru tratarea acestor cazuri.
42 Biblioteca pentru opera ții atomice – http://en.cppreference.com/w/cpp/atomic
36
Spre deosebire de cazul recep ționării datelor aici trebuie s ă spunem instan ței de epoll
că dorim să primim notificare în cazul în care un sock et devine preg ătit pentru expedierea
datelor:
epoll_event event ;
event.data.ptr = this;
event.events = EPOLLIN | EPOLLOUT | EPOLLET;
epoll_ctl (_epollFd , EPOLL_CTL_MOD , _fd, &event);
Secțiunea de cod 17: Semnalarea instan ței de epoll ca ne intereseaza evenimentul EPOLL
În rest tratarea expedierii datelor incomplete se face similar ca în cazul recep ționării
(ținând eviden ța datelor transmise deja prin intermediul unui indicator).
2.2.1.7. Concluzii
Având în vedere cele prezentate anterior putem concluziona c ă în cazul aplicației
curente mecanismul epoll reprezint ă cea mai bun ă opțiune de tratare în mod concurent a
unui num ăr mare de clien ți într-un mod cât mai eficient.
Un dezavantaj al utilizării mecanismul ui epoll este lipsa portabilităț ii întrucât acesta
este un mecanism specific platformei Linux.
2.2.2. Baza de date persistent ă
Pentru a ține eviden ța utilizatorilor, a conversa țiilor și a istoricului apelurilor este
necesar ă reținerea unor anumite informații într-o bază de date persistent ă. Există mai multe
soluții disponibile dar pentru aceast ă aplicație am ales sa folosesc MySQL pentru c ă este
un produs matur care dispune de un grad ridicat de compatibilitate cu diverse pl atforme și
medii de dezvoltare.
2.2.2.1. Execu ția clasic ă a interog ărilor
Comunicarea între aplicația server și baza de date MySQL se faca prin intermediul
interfe ței de programare C++ pus p la dispoziție de dezvoltatori. Executia interog ărilor prin
intermediul acestei interfe țe are patru pași:
Crearea conexiunii ;
Crearea interog ării;
Execu ția interog ării;
Eliberarea resurselor .
37
sql::Driver * dr = get_driver_instance ();
sql::Connection * con = dr->connect("tcp://127.0.0.1:3306" , "user", "parola" );
con->setSchema ("licenta" );
sql::PreparedStatement * stmt = con->PrepareStatement ("SELECT * FROM users" );
sql::ResultSet * res = stmt->executeQuery ();
delete res; delete stmt;
con->close();
delete con;
Secțiunea de cod 18: Exemplu de lansare și executie clasica a unei interog ări[7]
Aceast ă modalitate de execu ție a interog ărilor este simpl ă dar are un dezavantaj
major în cadrul aplica țiilor asincrone – blocheaz ă threadul curent p ână la sosirea
rezultatelor interog ării. Acest lucru poate duce la întârzieri însemnate în procesarea
cererilor și implicit reducerea dra stică a capacita ții serverului.
2.2.2.2. Lansarea asincrona a interog ărilor
Din p ăcate aceast ă interfață de programare nu permite comunicarea asincron ă cu
baza de date astfel c ă am creat , peste interfață de programare pus ă la dispoziție , un sistem
de execu ție al interog ărilor care nu necesit ă blocarea threadului care lanseaz ă interogarea.
Figura 7: Diagrama sistemului de interog ări
Acest sistem dispune de o coadă de așteptare în care sunt introduse interog ările ce
urmează a fi efectuate și de mai multe threaduri responsabile cu procesarea interog ărilor
din coadă . Fiecare thread are la dispoziție o conexiune proprie cu baza de date . Pasul de
crearea a unei conexiuni cu baza de date este destul de costisitor și de aceea am hot ărât
reciclarea conexiunilor prin intermediul unui object pool cu dimensiunea fix ă. Procesarea
38
unei interog ări are dou ă etape:
Pregatirea interog ării;
Execu ția interog ării.
Programatorul poate atribui cate un callback pentru fiecare etap ă astfel inc â să poată
acționa corespunz ător (de exemplu dac ă o interogare necesit ă parametri dinamici ace știa
vor fi seta ți în callbackul corespunz ător preg ătirii interog ării).
Dupa executia interog ării este ape lat cel de -al doilea callback, av ând ca parametru
rezultat ele interog ării.
2.2.2.3. Interog ări irelevante
Dat fiind faptul c ă serverul este asincron este posibil ca o interogare întârziată să
devin ă irelevant ă. De exemplu la momentul lans ării interog ării se doresc anumite
informații despre Utilizatorul A și programatorul construie ște callbackurile în consecin ță
dar la momentul finaliz ării execu ției interog ării instanț a clasei CUser corespunz ătoare cu
Utilizatorul A este asociata cu Utilizatorul B (din cauza sistemului de reciclare a obiectelor
CUser ). Pentru a evita astfel de probleme utilizatorul trebuie sa verifice în callbackuri
relevan ța interog ării.
În cazul în care interogarea devine irelevant ă înainte de execu ția ei (la pasul de
pregătire a interog ării) programatorul are posibilitatea anul ării execu ției interog ării salv ând
astfel timp și resurse.
int currentUserId = GetId();
db.EnqueueQuery (this, "SELECT * FROM history WHERE caller = ?" ,
[this, currentUserId ](sql::PreparedStatement * stmt) {
if (GetId() != currentUserId ) {
return false;
}
stmt->setInt(1, currentUserId );
return true;
},
[this, currentUserId ](sql::ResultSet * res) {
if (GetId() != currentUserId ) {
return;
}
//…
});
Secțiunea de cod 19: Exemplu lansare interogare
2.2.2.4. Concluzii
Pentru a decide dac ă utilizarea sistemului de lansare asincron ă interog ărilor a adus
plusul de performan ță dorit am rulat un test pe parcursul c ăruia am m ăsurat durata de
39
execu ție a 10, 50, 100, 1000 de interog ări atât cu sistemul clasic cât și cu cel propus,
asincron. Pe axa X se afl ă numărul de interog ări iar pe axa Y se afl ă timpul m ăsurat în
secunde. Sistemul asincron a fost initializat cu 4 threaduri. Fiecare interogare simuleaz ă o
așteptare de o secund ă pentru a simula ni ște interog ări costisitoare.
0200400600800100012001400
10 50 100 1000Clasic
Asincron
Grafic ul 3: Num ăr de interog ări lansate și executate pe secund ă
Conform rezultatelor (Grafic ul 3) obținute utilizarea sistemului de lansare asincron ă
a interog ărilor scade timpul de execuție semnificat iv comparativ cu lansarea și execu ția
clasic ă a interog ărilor. Aceast ă eliminare a timpului de așteptare în threadul care lanseaz ă
interogarea spore ște substan țial capacitatea serverului f ără a cre ște foarte mult
complexitatea aplicației . Putem spune ca sistemul de lansare și execu ție asincron aduce
rezultatul a șteptat cu un minim de efort .
2.2.3. Server HTTP pentru imaginile de profil
Pentru implementarea serverului HTTP responsabil cu imaginile de profil ale
utilizatorilor am recurs la platforma node.js43 fiind ușor de configurat și rulat în aproape
orice mediu cu un impact minim asupra resurselor sistemului. Un server HTTP care oferă
utilizatorului imagini ar putea gener a un consum ridicat de band ă de internet dar platforma
node.js se bucur ă de suport din partea celor mai mari furnizori de servicii cloud astfel c ă
portarea acestui server în cloud nu ar ridica nicio problem ă.
Acest server are dou î roluri:
Oferî utilizatorului imagini de profil în funcție de un identificator numeric unic
Actualizeaz ă imaginea de profil a utilizatorului curent .
Pentru a îndeplini primul rol serverul se comport ă ca un serve r HTTP pentru fi șiere statice
– caută pe disc poza corespunz ătoare identificatorului numeric iar dac ă nu o gase ște trimite
utilizator ului o poz ă de profil implicită .
43 Node.js – https://nodejs.org/en/about/
40
Node.js pune la dispozi ția dezvoltatorilor tot felul de extensii care simplific ă mult
procesul de dezvoltare al aplica țiilor. U na dintre cele mai populare extensii este
Express.js44. Aceast ă extensie permite creare unui server HTTP și rutarea rapid ă în funcție
de URL. Mai jos este un exemplu[ 10] de aplicație scrisă cu ajutorul Express.js care va crea
un server HTTP care intoare un mesaj către client:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send( 'Salut!');
});
app.listen( 8080, function () {
console.log( 'Serverul asteapta la portul 8080!');
});
Secțiunea de cod 20: Exemplu de aplicație scris ă cu Express.js
Cel de -al doilea rol presupune autentificarea utilizatorului pentru a preveni situatia în
care un utilizator mali țios modific ă abuziv imaginea de profil al altui utilizator. Aceasta
autentificare se face prin intermediul bazei de date persistent ă, valid ând informațiile
primite de la utilizatorul ce dore ște actualizarea unei imagini.
44 Express.js – http://expressjs.com/
41
Concluzii generale
Scopul acestei lucrari a fost acela de a identifica provoc ările ridicate de
implementarea unei aplica ții de mesagerie vocal ă și textual ă și de a g ăsi solu ții optime
pentru rezolvarea lor.
Implementarea clientului pentru platforma Android a devenit usoar ă din momentul
ințelegerii arhitecturii unei asemenea aplica ții. Partea de imp lementare nativ ă a anumitor
componente nu a ridicat probleme majore de și suportul acestora pe platforma Android este
încă marcat ca fiind experimental. Am v ăzut cum implementarea unui codec relativ simplu
ca și complexitate reduce la jum ătate c antitatea de date audio generată în timpul unei
convorbiri și de asemenea am v ăzut cum implementarea nativ ă și optimizarea cu tabela de
valori precalculate a dus la un plus de performan ța însemnat.
Implementarea aplicației server a ridicat mai mult probleme logistice. Fiind un server
a cărui func ționare depinde de mai multe componente ce func ționeaz ă asincron a fost
nevoie de delimitarea foarte clar ă a zonei de activitate a fiec ărei componente astfel încâ t
ele s ă lucreze eficient impreun ă. Am v ăzut implementarea unui server TCP asincron
utiliz ând mecanismul de monitorizare epoll și problemele ridicate în urma utilizării
asincrone dar și rezolv ările lor. În capitolul 2 am v ăzut de asemenea avantajele majore
aduse de implementarea asincron ă și utilizarea unui object pool în cadrul componentei de
lansare și executare a interog ărilor dar și eventuale probleme care ar putea surveni .
42
Bibliografie
[1] Google Inc. , Android Developers Documentation –
https://developer.android.com/reference/packages.html
[2] Oracle Corporation , Java Native Interface Specification –
http://docs.oracle.com/javase/7/docs/technotes/gu ides/jni/spec/jniTOC.html
[3] Google Inc. , OpenSL ES for Android –
https://developer.android.com/ndk/guides/audio/opensl -for-android.html
[4] Intel Corporation , 9-Patch Images for Android – https://software.intel.com/en –
us/xdk/articles/android -splash -screens -using -nine-patch -png
[5] Linux Kernel Organization , man -page s – https://www.kernel.org/doc/man -pages/
[6] Robert Love , Septembrie 2007, „The Event Pool Interface” din „Linux System
Programming”, O'Reilly Media Inc. –
https://www.safaribooksonline.com/library/view/linux -system –
programming/0596009585/ch04s02.html
[7] Oracle Corporation , MySQL Connector/C++ Develo per Guide –
https://dev.mysql.com/doc/connector -cpp/en/
[8] cppreference.com , C++ Reference – http://en.cppreference.com/w/cpp
[9] Node.js Foundati on, Node.js Docs – https://nodejs.org/en/docs/
[10] Node.js Foundation , Express.js API reference – http://expressjs.com/en/4x/api.html
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Dezvoltarea unei aplica ții mobile pentru comunicare vocală și textuală prin intermediul internetului propusă de Dudu Mihai -Ștefan Sesiunea: iulie,… [605450] (ID: 605450)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
