Interfaţarea accesoriilor în Android™

by donpedro

Interfaţarea accesoriilor simplifică dezvoltarea de aplicaţii embedded Android, explică David Flowers de la Microchip Technology Inc.

De vreme ce tabletele şi telefoanele mobile şi-au început viaţa ca platforme embedded, ele lucrează acum cu sisteme de operare similare cu cele ale unui PC standard. Ele au devenit acum dispozitive multi-threaded şi chiar multi-miez, capabile de a rula simultan aplicaţii multiple, de a beneficia de conectivitate de mai multe tipuri şi oferind interfaţă cu utilizatorul. Pentru client, acest lucru creează mari aşteptări, în vreme ce pentru dezvoltatorul aplicaţiei se creează atât oportunităţi cât şi provocări.
Oportunităţile includ faptul că dispozitivele mobile se interfaţează cu alte dispozi­tive electronice, precum echipamente de fitness şi dispozitive medicale, iar provocările rezidă din faptul că, dezvoltarea de noi aplicaţii mobile pot necesita un set foarte diferit de abilităţi, în special pentru dezvoltatorii mai familiarizaţi cu crearea de aplicaţii pe procesoare mai mici.

Înţelegerea threading-ului

(Multi-threading reprezintă posibilitatea unui program de a executa mai multe secvenţe de program în acelaşi timp. Aceste secvenţe de program se numesc thread-uri, sau fire de execuţie. Nota trad.)
Cunoaşterea threading-ului este importantă pentru toţi dezvoltatorii de aplicaţii mobile deoarece este un punct central în abilitatea dispozitivelor mobile de a rula simultan mai multe operaţii. Threading este o diviziune a executării unui program, în cadrul unui proces, ceea ce creează două seturi de programe ce rulează simultan. În vreme ce unul dintre programe aşteaptă apariţia unui eveniment înainte de a continua, celălalt trebuie să fie capabil să ruleze. Fără un threading corespunzător, interfaţa cu utilizatorul a aplicaţiei se va bloca şi nu va mai răspunde la comenzi, la sarcini precum conectarea la internet, sau prin Bluetooth® sau USB, pentru un interval de timp nespecificat.
Threading-ul introduce de asemenea problema concurenţei în dezvoltarea de programe. Atunci când două sau mai multe thread-uri rulează simultan, este posibil să apară probleme de acces de date foarte complexe, atunci când datele trebuie să treacă între două thread-uri, sau când aceleaşi date trebuie să fie citite sau modificate de două thread-uri. Deoarece fiecare durată de execuţie a unui thread este necunoscută, este posibil ca o variabilă să fie modificată exact atunci când este citită de al doilea. Java furnizează răspunsul la aceasta prin utilizarea unui cuvânt cheie de sincronizare: acesta permite dezvoltatorilor să creeze secţiuni fixate pe un obiect partajat, astfel încât, dacă un thread este în interiorul unei secţiuni sincronizate, niciun alt thread nu poate intra în secţiune până când primul thread îl lasă.

private Integer a = 0, b = 0;

public synchronized void updateVariables() {
a += 1;
b -= 1;
}

public synchronized Integer getSum() {
return a + b;
}

Figura 1: Funcţii sincronizate

În Figura 1, funcţiile sincronizate sunt utilizate pentru a asigura faptul că variabilele “a” şi “b” sunt modificate împreună, rezultând aceeaşi sumă. Fără sincro­nizare, ar fi fost posibil ca un thread să apeleze în program funcţia updateVariables() pentru “b”, imediat după ce “a” a fost incrementat, dar înainte ca “b” să fie decrementat, moment în care un al doilea thread, să apeleze rutina getSum(), rezultând o sumă diferită în acel moment de timp.
Figura 2 prezintă modalitatea în care o secţiune sincronizată poate fi utilizată în locul unei funcţii sincronizate pentru a atinge acelaşi obiectiv.

private Integer a = 0, b = 0;

public void updateVariables() {
synchronized(a) {
a += 1;
b -= 1;
}
}

public Integer getSum() {
synchronized(a) {
return a + b;
}
}

Figura 2: Cuvinte cheie sincronizate

Utilizarea unei secţiuni sincronizate permite ca alte variabile şi funcţii din obiectul iniţial să continue a fi utilizate, de vreme ce blocarea are loc doar asupra variabilei “a” în loc de întregul obiect.
Deoarece declaraţiile de sincronizare sunt un pic mai complexe şi mai susceptibile la erori, trebuie avută grijă în utilizarea metodei potrivite pentru fiecare funcţie. Java permite de asemenea ca datele sau evenimentele să fie transferate în siguranţă între thread-uri utilizând sisteme de manipulare (handler) şi mesaje.
Un handler este similar unei cutii poştale: mesajele pot fi plasate în handler, care apoi prezintă primul mesaj thread-ului, imediat ce acesta nu este ocupat. Metoda poate fi utilizată pentru a transfera între thread-uri informaţiile despre evenimente sau date.

public class PushbuttonMessage {
private boolean pressed = false;
public PushbuttonMessage (boolean state) {
pressed = state;
}

public boolean isPressed() {
return pressed;
}

Figura 3: Crearea unei clase pentru a fi folosite ca mesaj

private final static int BUTTON_EVENT = 1;

PushbuttonMessage pbMsg = new PushbuttonMessage(false);

/* Get a new message with the “what” of BUTTON_EVENT, and the button message
that was just created */
Message msg = handler.obtainMessage(BUTTON_EVENT, pbMsg);
msg.sendToTarget();

Figura 4: Crearea unui mesaj şi trimiterea lui către sistemul de manipulare (handler)

private final static int BUTTON_EVENT = 1;

private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case BUTTON_EVENT:
/* Acum, pentru că ştim că avem un mesaj buton, ştim că obiectul asociat cu mesajul este un PushbuttonMessage. Crearea unui PushbuttonMessage şi prinderea obiectului în el pentru a putea accesa membri săi */
PushbuttonMessage pbMessage = (PushbuttonMessage) msg.obj;
if(pbMessage.isPressed() == true) {
//do something here
}

Figura 5: Implementarea unui handler pentru recepţionarea şi decodarea unui mesaj

Schimbări ale ciclului de funcţionare

Activităţile sau aplicaţiile Android trec prin tranziţii la nivelul ciclului de funcţionare, ce apar atunci când schimbările de pe telefon sau tabletă afectează aplicația. La dezvoltarea de aplicaţii specifice pentru sistemul de operare Android, este important de înţeles ciclul de funcţionare al activităţilor, deoarece chiar şi interacţiuni simple cu utilizatorul, precum rotirea ecranului, atingerea ecranului, sau primirea unui apel pot cauza schimbări ale ciclului de funcţionare dintr-o aplicaţie. Multe dintre resursele sistemului pe care o activitate le poate solicita trebuie să fie eliberate de sarcini în anumite stări ale ciclului de funcţionare. De exemplu, receptoarele pentru transmisii sunt utilizate pentru detectarea de evenimente pe magistrala USB, precum momentul când dispozitivul este detaşat. Receptorul trebuie să devină neînregistrat atunci când aplicaţia se află în pauză, iar apoi re-înregistrat atunci când aplicaţia reporneşte.

Ciclul de funcţionare al activităţii în Android

Sistemul de operare Android furnizează o cale de a suprascrie comportamentul implicit al fiecăruia dintre aceste evenimente, astfel încât dezvoltatorii pot adăuga orice funcţionalitate necesară pentru aceste tranziţii în ciclul de funcţionare.
Pentru suprascrierea unei funcţii din ciclul de funcţionare trebuie utilizat numele stării ca funcţie cu cuvântul cheie @Override înaintea sa.

@Override
public void onResume() {
super.onResume();

//your stuff here
}

Figura 6: Suprascrierea funcţiei onResume()

Atunci când se suprascrie o funcţie a ciclului de func­ţio­nare, întotdeauna se utilizează super cuvântul cheie pentru a apela funcţionalitatea parentală ce a fost suprascrisă. Acest lucru asigură că ceilalţi paşi ce apar în mod normal în ciclul de funcţionare, vor apărea în continuare. Apariţia unei probleme în această situaţie se poate concretiza prin căderea aplicaţiei. Este de asemenea important de observat că, uneori, contează unde funcţionalitatea paren­tală este apelată în funcţie. Pentru schimbări în ciclul de funcţionare pe partea de creare a ciclului: onCreate(), onStart(), onResume(), super funcţia este tipic apelată la startul funcţiei. Pentru schimbări ale ciclului de funcţionare pe partea de finalizare a ciclului: onPause(), onStop(), OnDestroy(), este uzual important de făcut apelul la sfârşitul funcţiei. O metodă de a lucra cu această problemă de a gestiona diferite schimbări ale ciclului de funcţionare, este de a mişca unele dintre aceste obiecte care trebuie să reziste schimbărilor într-un serviciu. Utilizarea unui serviciu pentru obiecte de conectivitate de date poate de asemenea permite ca mai multe activităţi să partajeze aceeaşi conexiune de date.

Comunicaţie wireless

Cele mai importante interfeţe de comunicare sunt: USB, Bluetooth şi Wi-Fi®. Totuşi, aceste metode sunt dependente de versiunea de sistem de operare, precum şi de caracteristicile hardware disponibile pe dispozitiv.
Wi-Fi este probabil una dintre cele mai simple şi mai bine documentate interfeţe disponibile pentru dezvoltarea de aplicaţii. Dacă accesoriul ţintă include un server HTTP, browser-ul pe telefon sau tabletă poate fi utilizat pentru a elimina necesitatea unei aplicaţii particularizate. Există de asemenea diferite aplicaţii telnet/ftp ce pot elimina dezvoltarea particularizată. Dacă este însă cerută o astfel de aplicaţie, Java oferă API-uri de reţea, existând un amplu material de documentare în acest sens. Există însă un element specific sistemului de operare Android ce trebuie adăugat aplicaţiei înainte de a fi capa­bilă de a utiliza API-ul.

În fişierul AndroidManafest.xml, activitatea care accesează API de reţea trebuie să primească permisiunea de a face acest lucru prin adăugarea următoarei linii:

< uses-permission android:name = ”android.permission.INTERNET” />

Figura 7: Adăugarea permisiunii unei aplicaţii de a utiliza Internetul

O limitare majoră la utilizarea comunicaţiei Wi-Fi pe un dispozitiv Android pentru interfaţare este aceea că Android nu suportă reţele ad-hoc, astfel încât este nevoie de o infrastructură de reţea înainte ca accesoriul Wi-Fi să opereze. Acest lucru poate fi realist pentru unele aplicaţii, precum termostat într-o casă care are în permanenţă conectivitate Wi-Fi prin router, dar nerealist pentru aproape toate accesoriile mobile. Diferite versiuni de sisteme de operare Android oferă suport pentru diferite dispozitive Bluetooth. Versiunile Android v2.x asigură suport pentru SPP (Serial Port Profile), dar nu toate dispozitivele cu aceste versiuni de SO sunt capabile să utilizeze carac­teristica. Profilul SPP este util pentru crearea de aplicaţii particulare care nu au un format al datelor predefinit. Pentru accesorii mai specializate, v3.x a introdus suport pentru căşti şi A2DP (Advanced Audio Distribution Profile), în vreme ce sistemul de operare Android versiunile 4.x au introdus suport pentru dispozitive de sănătate HDP (Health Device Profile).

Conectivitate USB

Una dintre cele mai recente metode pentru descărcarea datelor de pe un dispozitiv Android este USB-ul. Înaintea versiunii 2.3.4 de sistem de operare Android, portul USB era utilizat exclusiv de producătorul dispozitivului, astfel încât nu era disponibil pentru dezvoltatorii de aplicaţii. Acest lucru s-a schimbat cu v2.3.4 şi actualizările către v3.1, permiţând dezvoltatorilor de accesorii Android să utilizeze portul USB.
Versiunea 3.1 a Android a introdus apoi USB Host API, permiţând dezvoltatorilor să utilizeze periferice standard USB conectate la dispozitive potrivite Android. Sistemul de operare are de asemenea integrat suport pentru unele clase de dispozitive USB, precum interfeţe cu utilizatorul HID (Human Interface Device) şi dispozitive de stocare MSD (Mass Storage Devices). Aceste drivere integrate permit perife­ricelor USB să fie utilizate fără probleme, exact aşa cum sunt utilizate pe un computer standard. Pentru periferice fără suport integrat, USB Host API permite dezvoltatorilor de aplicaţii să se conecteze şi să comunice direct cu punctele finale USB printr-un set API simplu de nivel scăzut.

UsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);

UsbInterface intf = device.getInterface(0);

UsbDeviceConnection connection = manager.openDevice(device);

connection.claimInterface(intf, true);

UsbEndpoint endpointOUT = intf.getEndpoint(0);

connection.bulkTransfer(endpointOUT, buffer, buffer.length, timeout_ms);

Figura 8: Conectare prin USB Host API şi expedierea unui pachet

Pentru a obţine permisiunea pentru USB Host API, aplicaţia trebuie să declare utilizarea bibliotecii în fişierul AndroidManafest.xml:

< uses-library android_name=“android.hardware.usb.host” />

Figura 9: Activarea unei aplicaţii prin permiterea accesului la USB Host API

Pregătirea unui filtru de dispozitive permite unei aplicaţii să se auto-lanseze atunci când un periferic specific este introdus în portul USB.
În fişierul AndroidManafest.xml trebuie creat un filtru şi asociat cu un eveniment USB_DEVICE_ATTACHED, iar acesta trebuie asociat cu un fişier filtru precum “xml/device_filter.xml”.

< intent-filter>
< action android_name=”android.hardware.usb.action.USB_DEVICE_ATTACHED” />
< /intent-filter>
< meta-data android_name=”android.hardware.usb.action.USB_DEVICE_ATTACHED” android_resource=”@xml/device_filter” />

Figura 10: Auto-lansarea unei aplicaţii atunci când un dispozitiv este ataşat în modul USB Host

Fişierul device_filter.xml conţine informaţii despre dispozitivul ce poate cauza lansarea aplicaţiei. Acestea pot fi identificatori ai vânzătorului sau de produs VID (Vendor ID) şi PID (Product ID), sau informaţii de clasă, subclasă şi set de protocoale.

< ?xml version=”1.0” encoding=”utf-8”?>
< resources>
< usb-device vendor-id=”1240” product-id=”516”/>
< usb-device class=”256” subclass=”256” protocol=”5”/>
< /resources>

Figura 11: Stabilirea unui filtru pentru dispozitiv pentru lansarea unei aplicaţii în modul USB Host

Este de asemenea posibil ca dezvoltatorii de aplicaţii să fie mai puţin specifici prin neincluderea fiecărui atribut în informaţii. De exemplu, în absenţa unui atribut de identificare produs, potrivirea unui identificator de vânzător poate lansa aplicaţia.

OpenAccessory USB

Pentru a activa capabilitatea USB în dispozitivele Android fără suport hardware pentru USB Host, Google a adăugat framework-ul OpenAccessory în driverele USB standard în dispozitivele Android. Acesta permite dezvoltatorilor de accesorii să utilizeze funcţionalitatea standard de port USB pentru trafic particularizat USB. Protocolul OpenAccessory obţine acest comportament prin transferuri de control particularizate la nivel de dispozitiv, după cum au fost dezvoltate de producătorul dispozitivului. Aceste comenzi schimbă driverele USB într-un mod accesoriu şi fac ca perifericul USB să se detaşeze din modul de periferic USB şi să fie reataşat în modul de accesoriu, cu un identificator de producător de la Google (vendor ID) şi una sau două identificatoare de produs specifice. În acest mod, există o interfaţă de clasă producător ce poate fi accesată de o aplicaţie.
Interfaţa pe care o prezintă OpenAccessory aplicaţiei este ca un format FileStream. Datele sunt scrise şi citite din fluxul de informaţii, similar modului în care un fişier este citit şi scris. Acest mod diferă faţă de majoritatea implementărilor de firmware pentru periferice USB, în care interfaţa este bazată pe dimensiuni de pachete USB. Problema rezultată din această diferenţă trebuie înţeleasă de dezvoltatorul aplicaţiei, precum şi de dezvoltatorul firmware-ului pentru accesoriu.

Pachete de date şi fragmentare

Driverul USB al dispozitivului Android recepţionează un fişier flux şi de aceea nu recunoaşte sau înţelege rupturile logice potenţiale ale datelor pentru comenzi specifice. Datele de la două apeluri separate ale funcţiei de scriere a aplicaţiei pot aduce pachetele împreună în acelaşi pachet USB. Firmware-ul trebuie să fie avertizat că pachetul recepţionat prin USB poate conţine informaţiile de la două apeluri separate ale funcţiei de scriere din cadrul aplicaţiei.
O singură apelare de la aplicaţie a funcţiei de scriere poate de asemenea să fie fragmentată pe mai multe pachete USB. Driverul USB de pe dispozitivul Android va sparge datele în pachete şi le va trimite accesoriului, iar acesta trebuie să fie capabil să reasambleze datele în formatul corect.
Crearea pachetelor şi fragmentarea pot apărea împreună. De exemplu, framework-ul OpenAccessory utilizează curent pachete de 64 de byte. Dacă aplicaţia apelează funcţia de scriere de două ori, back to back, primul apel trimite 20 byte de date, iar al doilea trimite 64 byte de date. De aceea, este posibil ca cele două secţiuni de date să fie prinse împreună într-un bloc de 84-byte, în funcţie de modul în care driverul USB preia datele din flux şi le trimite prin magistrală.
Driverul USB necesită apoi să spargă fluxul de date în pachete de dimensiune USB, trimiţând primii 64 de byte de date, urmaţi de un pachet de 20 de byte. Primul pachet conţine cei 20 byte de date de la prima scriere şi 44 byte de la a doua. Al doilea pachet conţine cei 20 byte rămaşi de la a doua scriere.
Ultima provocare o reprezintă înţelegerea modului în care sunt formate transferurile de volum USB. În acord cu specificaţiile USB, transferurile de volum prin USB sunt complete când:

1) Este trimisă cantitatea exactă de date aşteptate
2) Şi când este trimis un pachet mai mic decât dimensiunea finală, sau un pachet de dimensiune zero

Pentru a completa transferul unui bloc de date, care este un multiplu exact al dimen­siunii finale, curent 64 byte, dezvoltatorii de accesorii trebuie să încheie cu un pachet de lungime zero. Eroarea de trimitere a pachetelor de lungime zero, când este necesar, poate rezulta din datele rămase în driverul USB Android fără a fi transferate către fişierul flux OpenAccessory, şi de aceea fără a fi transferate aplicaţiei.
Framework-ul OpenAccessory necesită de asemenea permisiunea în fişierul AndroidManifest.xml:

< uses-library android_name=”com.android.usb.accessory” />

Figura 12: Permisiunea unei aplicaţii de a accesa framework-ul OpenAccessory

Dispozitivul poate auto-lansa o aplicaţie pe baza unui şir de caractere ce trece în timpul paşilor necesari intrării în modul accesoriu. Acest lucru este realizat prin fişiere xml similare cu USB Host API:

< intent-filter>
< action android_name=”android.hardware.usb.action.USB_ACCESSORY_ATTACHED” />
< /intent-filter>
< meta-data android_name=”android.hardware.usb.action.USB_ACCESSORY_ATTACHED” android_resource=”@xml/accessory_filter” />

Figura 13: Lansarea unei aplicaţii în mod OpenAccessory

< ?xml version=”1.0” encoding=”utf-8”?>
< resources>
< usb-accessory manufacturer=”Microchip Technology Inc.” model=”Basic Accessory Demo” version=”1.0” />
< /resources>

Figura 14: Utilizarea unui filtru pentru a determina ce dispozitive vor lansa aplicaţia

Cel mai semnificativ neajuns al framework-ului Open Accessory este acela că este un add-on opţional al bibliotecii Android OS. De aceea, unii producători au decis să nu îl includă, astfel încât nu este posibil să se pornească de la ideea că fiecare dispozitiv cu versiune de sistem de operare corespunzătoare va suporta această funcţionalitate. Suportul poate fi de asemenea inclus de asemenea într-o versiune a produsului, dar scos apoi în versiunile subsecvente.
Lansarea Android 4.1 Jelly Bean a introdus versiunea 2 a protocolului Android Open Accessory (AOA) care oferă dezvoltatorilor de accesorii două noi carac­teristici: adăugarea de suport pentru ieşire audio digitală şi control HID (Human Interface Device) în modul accesoriu.

Audio digital

Suportul pentru ieşiri audio digitale permite crearea simplă a unor sisteme de andocare audio cu dispozi­tive Android. Deşi crearea de docuri audio era posibilă cu AOA versiunea 1, ea necesita ca proiectantul să utilizeze un protocol particularizat şi o aplicaţie particularizată. Orice aplicaţie standard avea ieşire audio prin căşti sau speakere. Cu AOA versiunea 2 toate circuitele audio de pe dispozitivul Android au trasee către portul USB, permiţând ca audio să lucreze cu orice aplicaţie sau caracteristică a dispozitivului. AOA versiunea 2 permite de asemenea interfeţei audio să fie accesate cu sau fără lansarea aplicaţiei la andocare. Netrimiterea unui şir de caractere cu privire la producător sau model către dispozitivul Android, înainte ca acesta să intre în modul accesoriu, este suficientă pentru andocare fără lansare.

Controlul interfeţei

Controlul interfeţei cu utilizatorul HID (Human Interface Device), din modul de accesoriu, era anterior disponibil numai în modul USB Host. Cu AOA versiunea 2, accesoriile pot trimite rapoarte HID pentru a fi asociate dispozitivului Android şi către sistemul de operare pentru a controla intrările de la utilizator. Acest lucru este util pentru verificarea controlului audio la andocare, dar şi pentru crearea de dispozitive de control precum mouse-uri, tastaturi şi joystick-uri.

Concluzie

Înţelegerea capabilităţilor şi limitărilor interfeţelor cu accesoriul este crucială pentru transformarea unui design hardware într-un accesoriu Android eficient. Trebuie stăpânite abilităţi precum threading, comunicaţie wireless, utilizarea Android ca USB host şi audio digital. Resursele de dezvoltare pentru toate aceste provocări, inclusiv firmware gratuit şi exemple cu privire la accesorii Android, pot fi găsite pe website-urile Microchip: www.microchip.com/android, /USB, /wifi şi /bluetooth.

Microchip Technology
www.microchip.com

S-ar putea să vă placă și