+ All Categories
Home > Documents > Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016...

Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016...

Date post: 02-Sep-2019
Category:
Upload: others
View: 3 times
Download: 0 times
Share this document with a friend
42
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
Transcript
Page 1: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 2: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 3: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 Dudu Mihai-Ștefan

___________________________

Page 4: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 internetului”, 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 Dudu Mihai-Stefan

_________________________

Page 5: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 intermediul internetului.

Popularitatea acestui tip aplicații a crescut enorm în ultima vreme, creșterea fiind

sustinută atât de dorința utilizatorului 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 utilizatori activi lunar) chiar aplicațiile rețelelor

sociale1. Alți factori 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 clientului pe platforma Android întrucât statisticile arată că Android

detine peste 70% din piața dispozitivelor mobile2.

Având astfel o idee despre popularitatea acestui 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 modalităț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

Page 6: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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, oferind 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 al clientului am analizat diverse tehnici pentru 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 internet;

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

Page 7: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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. Preluarea 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 epoll ..................................................................... 34

2.2.1.4. Recepționarea datelor în mod asincron ......................................... 35

Page 8: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 9: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 – Android Studio. Pe parcursul dezvoltării aplicației am urmărit

utilizarea unor tehnici 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 astfel 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 energetice relativ reduse.

Figura 1: Arhitectura generala a aplicației client

Page 10: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 intermediul notificărilor lansate de serviciul care rulează în permanență în

fundal. Acest serviciu poate 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 acceptarea sau respingerea apelului fără ca

utilizatorul să fie nevoit să-și întrerupă activitatea curentă.

4 Object Fragment - https://developer.android.com/training/basics/fragments/creating.html

Page 11: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

11

1.2.1. Fereastra de autentificare/înregistrare

Fereastra de autentificare este fereastră de start 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 de 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

Page 12: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 fereastra 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 fereastra principală utilizatorul are acces și la un panou lateral care oferă

utilizatorului informații despre profilul său, acces la fereastra pentru setări dar și

posibilitatea de a părăsi sesiunea curentă.

6 ViewPager - https://developer.android.com/reference/android/support/v4/view/ViewPager.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

Page 13: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Setarea sunetelor pentru notificare/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 compusă 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.

Page 14: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 metodele 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 zona 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

Page 15: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 16: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 numerotata 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 pentru 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 putea 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 de pe

serverul HTTP extern prin încapsularea fiecarei cereri de descărcare într-un obiect de tipul

Runnable13 și executarea lor într-un thread pool de dimensiune variabilă, dimensiunea

maximă fiind de 4 threaduri.

În momentul în care este necesară descărcarea unei imagini (pentru a fi atribuită unui

obiect de tipul ImageView14) se trimit către ProfilePicManager un identificator 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 imaginii 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/ImageView.html

Page 17: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 obiectelor 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 identificatorul 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

Page 18: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 19: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

19

Utilizatorul va primi o notificare la închiderea aplicaîiei care îl va informa faptul că

serviciul este activ chiar 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

aceluiaș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 conexiunii 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 unei 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/ServiceConnection.html 23 Binder - https://developer.android.com/reference/android/os/Binder.html

Page 20: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

20

1.5. Protocolul de comunicare între client și server

Comunicarea între aplicația client și server se desfășoară prin intermediul unui canal

TCP deschis atât timp cât utilizatorul este autentificat. 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 mesaj î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 tipurilor 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/ByteBuffer.html

Page 21: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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++:

Clientul 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 regulă, mai instabile și

foarte diversificate ca și parametri de securitate ceea ce ar fi putut face protocolul UDP

inutilizabil în unele cazuri (probleme cu pachetele 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

momentul 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 trebuiesc 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

Page 22: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 prin

intermediul interfețelor de tipul SLObjectItf. Obiectele OpenSL ES trebuiesc distruse

(Secțiunea de cod 9) în ordinea inversă 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 de 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

Page 23: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 coada 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

Page 24: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 biți. Biții de pe pozițiile marcate 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

Page 25: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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.

Page 26: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 27: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 valori 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.

Page 28: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

28

0

200000

400000

600000

800000

1000000

Java C/C++ (JNI)

Normal

Graficul 1: Număr de codari pe secundă

0

200000

400000

600000

800000

1000000

1200000

1400000

1600000

Java C/C++ (JNI)

Normal

Optimizat cu

valori

precalculate

Graficul 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 cp 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 codecul 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 permite 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.

Page 29: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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/decodare a codecului pot fi optimizate de compilatorul C/C++,

nefiind astfel nevoie de apeluri costisitoare prin JNI).

Page 30: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 comunicare 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

Page 31: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 clienț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. Serverul 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ăruia ș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 este 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):

Page 32: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 niciunul 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 acceptabil î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

Page 33: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 introdus î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 cel 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 un 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

Page 34: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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

Page 35: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

35

2.2.1.4. Recepționarea datelor în mod asincron

Recepț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 pachetelor 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

coada 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

Page 36: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 socket 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 mecanismului 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 platforme ș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++ pusp 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.

Page 37: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 drastică 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

Page 38: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 apelat cel de-al doilea callback, având ca parametru

rezultatele 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

Page 39: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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.

0

200

400

600

800

1000

1200

1400

10 50 100 1000

Clasic

Asincron

Graficul 3: Număr de interogări lansate și executate pe secundă

Conform rezultatelor (Graficul 3) obținute utilizarea sistemului de lansare asincronă

a interogărilor scade timpul de execuție semnificativ 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 genera 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 server HTTP pentru fișiere statice

– caută pe disc poza corespunzătoare identificatorului numeric iar dacă nu o gasește trimite

utilizatorului o poză de profil implicită.

43 Node.js - https://nodejs.org/en/about/

Page 40: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

40

Node.js pune la dispoziția dezvoltatorilor tot felul de extensii care simplifică mult

procesul de dezvoltare al aplicațiilor. Una 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/

Page 41: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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 implementare 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 cantitatea 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.

Page 42: Dezvoltarea unei aplicații mobile pentru comunicare vocală ...alaiba/pub/absolvire/2016 vara/ComunicareVocala.pdf · reformularea în cuvinte proprii a textelor scrise de către

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/guides/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-pages - 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++ Developer Guide -

https://dev.mysql.com/doc/connector-cpp/en/

[8] cppreference.com, C++ Reference - http://en.cppreference.com/w/cpp

[9] Node.js Foundation, Node.js Docs - https://nodejs.org/en/docs/

[10] Node.js Foundation, Express.js API reference - http://expressjs.com/en/4x/api.html


Recommended