This is an old revision of the document!


Laborator 08. Transmiterea de Notificări folosind Serviciul Google Cloud Messaging

Google Cloud Messaging for Android (GCM) este un serviciu gratuit prin intermediul căruia programatorii pot transmite informații între un server de aplicații și un dispozitiv mobil (pe care rulează sistemul de operare Android), implementând toate aspectele legate de gestiunea mesajelor, asigurând livrarea acestora. Nu există nici un fel de limite în privința resurselor implicate pentru transmiterea acestor informații.

Necesitatea unui astfel de serviciu este impusă de optimizările specifice aplicațiilor pentru dispozitive mobile:

  • pentru serverul de aplicații: detecția disponibilității dispozitivului mobil și a momentului în care se poate realiza transmisia de date (în funcție de tipul de conexiune), gestiunea mesajelor (implementarea unor cozi de așteptare), implementarea mesajelor cu mai mulți destinatari (reutilizarea unei conexiuni), transmiterea mai multor mesaje către un singur dispozitiv mobil, aspecte legate de compresie și detecția erorilor;
  • pentru dispozitivul mobil: limitări în privința consumului de energie și al transferului de date (în cazul unor anumite tipuri de conexiuni costurile sau viteza implicate fiind semnificative).

Serviciile precum Google Cloud Messaging for Android implementează cele mai bune practici în domeniu pentru tratarea acestor aspecte, astfel încât programatorul se poate concentra exclusiv asupra funcționalității aplicației.

Comunicația dintre cele două entități se poate realiza în ambele sensuri:

  • server de aplicații → dispozitiv mobil prin intermediul Google Cloud Messaging, nefiind necesar ca aplicația Android să ruleze la momentul în care este transmis mesajul, ea fiind pornită prin intermediul unei intenții (cu condiția ca aplicația să poată primi mesajele cu difuzare și să aibă permisiunile necesare specificate);
  • dispozitiv mobil → server de aplicații prin intermediul Cloud Connection Server.

Google Cloud Messaging for Android nu pune la dispoziție un API pentru procesarea mesajelor, acestea fiind trimise exact în forma în care au fost primite.

Un mesaj transmis prin intermediul Google Cloud Messaging poate:

  • indica aplicației Android faptul că trebuie să se sincronizeze cu serverul de aplicații (util mai ales pentru aplicațiile de poștă electronică);
  • conține informații utile (de până la 4kb), care sunt interpretate de aplicația Android (folosite în special pentru aplicațiile de mesagerie instantanee).

Pentru dispozitivele mobile cu o versiune a sistemului de operare Android mai mică decât 3.0 este necesară crearea unui cont de utilizator Google. Această cerință nu este obligatorie pentru versiuni mai mari decât 4.0.4.

Google Cloud Messaging for Android nu poate fi utilizat pe platforme care au instalată o versiune a sistemului de operare Android anterioară 2.2.

Arhitectura Google Cloud Messaging for Android

Un sistem informatic folosind infrastructura de comunicații Google Cloud Messaging for Android implică dezvoltarea următoarelor componente:

  • un server de aplicații responsabil cu transmiterea de informații către dispozitivul mobil prin intermediul serviciului Google Cloud Messaging for Android; de asemenea, serverul va putea procesa anumite mesaje transmise de către client (de exemplu, identificatorul prin care dispozitivul mobil este înregistrat în mod unic în cadrul serverului Google Cloud Messaging);
  • o aplicație Android rulând pe un dispozitiv mobil cu un sistem de operare Android având o versiune mai mare de 2.2, pe care este instalat Google Play Services.

De regulă, procesul de proiectare și dezvoltare a sistemului informatic implică următorul flux de lucru:

1. se obține un număr de proiect prin intermediul Google Developers Console, prin care un dezvoltator este identificat în mod unic; de asemenea, se activează serviciul Google Cloud Messaging;
2. se implementează aplicația Android care va rula pe client:

  • se realizează înregistrarea dispozitivului mobil (printr-un apel al metodei register a clasei GoogleCloudMessaging, care primește ca parametru numărul de proiect) în cadrul serverului Google Cloud Messaging (etapa 1), generându-se un identificator care va fi utilizat pentru a desemna mesajele care trebuie transmise către acesta (etapa 2); valoarea sa este generată astfel încât să identifice în mod unic:
    • numărul de proiect al dezvoltatorului de la al cărui server de aplicații vor fi transmise mesajele;
    • dispozitivul mobil către care se transmite mesajul (prin intermediul contului Google asociat acestuia);
    • aplicația care va procesa mesajul respectiv (pe baza informațiilor din AndroidManifest.xml - denumirea pachetului care identifică în mod unic o aplicație de pe un dispozitiv mobil și versiunea).
Generarea unui identificator de înregistrare se realizează o singură dată, urmând ca această valoare să fie stocată pentru a fi utilizată ulteror. În situația în care nu se mai dorește ca mesajele să fie primite, se poate realiza și procesul invers, de anulare a înregistrării realizate anterior.
  • se transmite identificatorul de înregistrare către serverul de aplicații (etapa 3) astfel încât acesta să îl poată utiliza pentru a indica Google Cloud Messaging locația către care va livra mesajele;
  • se tratează mecanismul de primire a unui mesaj (etapa 6); acesta va fi livrat aplicației corespunzătoare prin intermediul unei intenții de difuzare (de tip com.google.android.c2dm.intent.RECEIVE), după ce perechile (atribut, valoare) au fost extrase din conținutul mesajului și atașate acesteia; nu este necesar ca aplicația Android să ruleze la momentul primirii mesajului, aceasta fiind lansată în execuție în mod automat, dacă nu se găsește în această stare.
În situația în care prelucrarea unui mesaj implică un volum de procesare semnificativ, se recomandă ca acesta să fie realizat prin intermediul unui serviciu, după ce s-a obținut un mutex (de tip PowerManager.WakeLock) care să prevină trecerea dispozitivului mobil într-o stare latentă.

3. se obține o cheie publică pentru serverul de aplicații ce va fi utilizată în procesul de autentificare pentru a obține drepturi de accesare a serverului Google Cloud Messaging prin intermediul căruia vor fi transmise mesajele;
4. se implementează serverul de aplicații:

  • se vor procesa cererile venite din partea clientului de transmitere a identificatorului de înregistrare prin stocarea lor într-o bază de date locală (etapa 4);
  • se va transmite un mesaj către serverul Google Cloud Messaging (etapa 5) care va include:
    • cheia publică a serverului de aplicații, utilizată în procesul de autentificare, inclusă în antetul mesajului;
    • identificatorul de înregistrare a dispozitivului mobil, astfel încât serverul Google Cloud Messaging să cunoască locația către care se transmite mesajul.
    • datele propriu-zise (dacă este cazul).
Un mesaj transmis de pe serverul de aplicații va fi stocat într-o coadă de așteptare pe serverul Google Cloud Messaging și livrat în momentul în care dispozitivul mobil este disponibil.

Configurare

1. Prin intermediul Consolei Google API, se obțin numărul de proiect și cheia publică ce vor fi utilizate de dispozitivul mobil, respectiv de serverul de aplicații pentru a se autentifica în cadrul Google Cloud Messaging for Android.

Trebuie să fiți autentificați folosind numele de utilizator și parola contului Google, altfel va trebui să vă creați un astfel de cont.
  • În situația în care nu a fost creat un proiect Google API anterior, se va afișa o fereastră prin care se poate realiza acest lucru, accesându-se opțiunea Create Project…:

  • Se identifică numărul de proiect care va fi utilizat de dispozitivul mobil pentru a se înregistra la serverul Google Cloud Messaging for Android (SENDER_ID), acesta putând fi preluat din:
    • bara de adrese a aplicației de navigare
    • secțiunea Overview a consolei Google API
  • Se activează API-ul Cloud Messaging for Android din secțiunea Mobile APIs, ce poate fi accesată din APIs & authAPIs a consolei Google API.

  • Ulterior, vor putea fi vizualizate statistici cu privire la gradul de utilizare al acestui serviciu (număr de cereri realizate, distribuția codurilor de răspuns), precum și informații legate de monetizare.

  • În secțiunea APIs & authCredentials se creează o cheie publică ce va fi utilizată pentru a transmite notificări de la serverul de aplicații la serverul Google Cloud Messaging for Android.
    • Se selectează butonul Create New Key din secțiunea Public API access.
    • Se specifică proprietățile cheii publice ce se dorește a fi generată în funcție de aplicația prin intermediul căreia se vor transmite notificări către dispozitivul Android prin intermediul Google Cloud Messaging.
      • tipul cheii publice (variante disponibile: Server Key, Browser Key, Android Key, iOS Key) - se va selecta cel mai general tip, Server Key.
      • adresele Internet de la care pot fi acceptate solicitări (câte o adresă Internet pe un rând): acest câmp poate fi lăsat vid, dacă vor fi acceptate cereri de la mai multe aplicații web, disponibile la diferite locații, sau se poate folosi adresa IP 0.0.0.0/0, în scop de testare.
    • Se identifică valoarea cheii publice care va fi utilizată

Configurare client

2. Se instalează SDK-ul Google Play Services, necesară autentificării / deautentificării la Google Cloud Messaging for Android din cadrul aplicației instalată pe dispozitivul mobil.

  • În Eclipse Luna, se accesează Android SDK Manager
    • din bara de unelte
    • din meniul principal, WindowAndroid SDK Manager
  • Se selectează următoarele pachete:
    • din secțiunea Android 4.4.2 (API 19), pachetele Google APIs
    • din sețiunea Extras, pachetul Google Play Services
  • Biblioteca pentru accesarea Google Cloud Messaging va fi instalată la <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib.

3. Se configurează emulatorul astfel încât să poată fi rulată aplicația Android ce folosește Google Cloud Messaging.

Pentru aplicațiile ce rulează pe dispozitivul fizic (având o versiune a sistemului de operare Android ulterioară 2.2) nu este necesară nici o configurare suplimentară (cu excepția asocierii unui cont Google, prin specificarea parametrilor acestuia - nume de utilizator și parolă).
  • AVD
    • se creează un dispozitiv virtual pentru care va specifica la secțiunea Target valoarea Google APIs (Google Inc.)
    • se asociază datele unui cont Google (nume de utilizator, parola) din SettingsAccounts, selectându-se opțiunea Add account
  • Genymotion - vor trebui instalate manual ARM Transalation și Google Apps for Android întrucât suportul pentru acestea nu mai este asigurat începând cu versiunea 2.0
    • în SettingsADBADB Connection Settings se indică locația unde se găsește instalat SDK-ul de Android
    • se instalează ARM Translation 1.1 printr-o operație de tip drag-and-drop în ecranul principal al emulatorului
      • se afișează o fereastră de dialog ce va conține mesajul File transfer in progress…
        lab09-genymotionfiletransfer.jpg
      • odată realizat transferul, se afișează o fereastră de dialog prin care utilizatorul este atenționat de faptul că arhiva ar putea corupe dispozitivul virtual, solicitându-i-se acordul pentru a continua instalarea
        lab09-genymotioninstall.jpg
      • odată realizată instalarea, se afișează o fereastră de dialog ce conține rezultatul operației
        lab09-genymotionresult.jpg
    • se repornește emulatorul prin comanda
      adb -s 192.168.56.101:5555 reboot
    • se instalează Google Apps for Android corespunzătoare versiunii sistemului de operare existentă pe dispozitivul virtual, printr-o operație de tip drag-and-drop în ecranul principal al emulatorului (sunt parcurse aceleași etape ca anterior)
    • se repornește emulatorul prin comanda
      adb -s 192.168.56.101:5555 reboot
    • se asociază datele unui cont Google (nume de utilizator, parola) din SettingsAccounts, selectându-se opțiunea Add account
      lab09-genymotiongoogleaccount.jpg
    • în Play Store se actualizează toate aplicațiile care necesită acest lucru
      lab09-genymotionsoftwareupdate.jpg

4. În situația în care se va folosi un server CCS (XMPP), este necesară o înregistrare suplimentară pentru a se obține acceptul Google de a utiliza serviciul de transmitere a mesajelor de pe dispozitivul mobil, prin furnizarea informațiilor solicitate la Upstream Google Cloud Messaging and User Notifications Sign-up.

Dacă nu se urmează acest pas, va fi transmis un mesaj prin care se refuză conexiunea la server, de tipul Project not whitelisted.
Procesul de aprobare a accesului la acest serviciu poate dura o perioadă de timp mai îndelungată.

Configurare server

Pot exista mai multe variante de implementare a unui server de aplicații, în acest caz fiind aleasă o soluție care implică un efort redus de configurare și de implementare propriu-zisă.
Ca atare, se va proiecta un server de aplicații care va trimite mesaje către serverul Google Cloud Messaging, respectiv va primi mesaje de la un client Android folosind protocolul HTTP. Pentru dezvoltarea acestei funcționalități, soluția aleasă se bazează pe pagini Internet generate cu Java Servlets și utilizând un motor pentru gestiunea bazelor de date MySQL, serverul web folosit fiind Apache Tomcat.

5. Se descarcă cea mai recentă versiune stabilă a serverului web Apache Tomcat (zip|tar.gz) și se dezarhivează.

6. Se descarcă cea mai recentă versiune stabilă a mediului de dezvoltare Eclipse IDE for Java EE Developers (Windows: 32-bit|64-bit, Linux: 32-bit|64-bit) și se integrează cu serverul web Apache Tomcat.

  • WindowPreferencesServerRuntime Environments și se accesează butonul Add…
    lab09-eclipseeeserverruntimeenvironments.jpg
  • se specifică tipul serverului web (Apache Tomcat v7.0)
    lab09-eclipseeenewserverruntimeenvironment.jpg
  • se precizează locația la care se găsește serverul web (directorul în care a fost dezarhivat, fără alte configurări) și versiunea de Java Runtime Environment
    lab09-eclipseeetomcatserver.jpg
Se oferă și posibilitatea descărcării și instalării unei instanțe a serverului web Apache Tomcat, însă de regulă aceasta nu referă cea mai recentă versiune.
  • WindowShow ViewOther…ServersServer și se accesează legătura No servers are available. Click this link to create a new server… sau click pe butonul drept al mouse-ului în spațiul de lucru ServerNewServer
    lab09-eclipseeeshowview.jpg
    lab09-eclipseeecreateanewserver.jpg
  • se selectează serverul web definit anterior și se accesează butonul Finish
    lab09-eclipseeedefineanewserver.jpg
  • se asociază proiectului un server web pe care va rula: click pe butonul drept al mouse-ului pe denumirea proiectului → PropertiesServer și se selectează serverul web definit anterior
    lab09-eclipseeeprojectproperties.jpg
  • se realizează o referință către biblioteca serverului web, aceasta fiind trecută în classpath: click pe butonul drept al mouse-ului pe denumirea proiectului → Build PathAdd Library
    • se alege Server Runtime
      lab09-eclipseeeaddlibrary.jpg
    • se indică serverul web definit anterior și se accesează butonul Finish
      lab09-eclipseeeserverlibrary.jpg

7. Se descarcă și se instalează cea mai recentă versiune stabilă a motorului pentru baze de date MySQL.

  • Windows: se poate folosi MySQL Community Server.
  • Linux: se poate utiliza comanda
    sudo apt-get install mysql-server
Configurarea propriu-zisă nu presupune decât specificarea unei parole de administrare.

Pentru gestiunea propriu-zisă a bazelor de date, poate fi utiliza MySQL Workbench Windows|Ubuntu|Fedora Core|Red Hat.
De asemenea, se poate accesa consola MySQL folosind comanda

mysql -u root -p

Va trebui indicată parola, atunci când aceasta este solicitată.

MySQL pe Debian Wheezy

  • in cazul in care nu exista, se instaleaza pachetul libaio1
    sudo apt-get install libaio1
  • se descarca versiunea corespunzatoare arhitecturii pe care se doreste sa se instaleze serverul MySQL pentru gestiunea bazei de date (32-bit|64-bit).
  • se instaleaza pachetul in /opt/mysql/server-5.6
    sudo dpkg -i mysql-5.6.17-debian6.0-<arhitectura>.deb
  • se copiaza fisierul de configurare my-default.cnf in /etc, astfel incat acesta sa fie utilizat atunci cand se vor rula script-urile MySQL
    sudo cp /opt/mysql/server-5.6/support-files/my-default.cnf /etc/my.cnf
  • se editeaza fisierul de configurare /etc/my.cnf
    gedit /etc/my.cnf
    
    basedir = /opt/mysql/server-5.6
    #datadir = .....
    port = 3306
    #server_id = .....
    #socket = .....
  • se specifica calea catre binare
    sudo gedit /etc/profile
    
    PATH=/opt/mysql/server-5.6/bin:$PATH
Incarcarea acestor configurari va fi realizata in momentul in care se realizeaza autentificarea unui utilizator in cadrul sistemului de operare sau se ruleaza in terminal . /etc/profile.
  • se creeaza grupul si utilizatorul mysql, care va primi drepturi asupra directorului /opt/mysql
    sudo groupadd mysql
    sudo useradd -r -g mysql mysql
    cd /opt/mysql
    sudo chown -R mysql.mysql /opt/mysql
    sudo chgrp -R mysql /opt/mysql
  • se ruleaza script-urile pentru configurarea propriu-zisa a serverului MySQL pentru gestiunea bazei de date
    cd /opt/mysql/server-5.6/scripts
    sudo ./mysql_install_db --user=mysql
    mysqld_safe --user=mysql
    mysql_secure_installation
  • se realizeaza configurarile necesare pentru ca serverul pentru gestiunea bazei de date sa porneasca / sa se opreasca odata cu sistemul de operare
    sudo cp /opt/mysql/server-5.6/support-files/mysql.server /etc/init.d/mysql.server
    cd /etc/init.d
    sudo chmod 755 mysql.server
    sudo inssev mysql.server

Proiectarea și dezvoltarea clientului

Proiectul Eclipse

Există posibilitatea ca rularea proiectului Eclipse să necesite mai multă memorie ce trebuie alocată mașinii virtuale Java, aceasta realizându-se prin intermediul valorilor furnizate în cadrul fișierului de configurare eclipse.ini:
--launcher.XXMaxPermSize
512M
--launcher.XXMaxPermSize
512m
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.6
-Xms512m
-Xmx1024m

Se realizează o referință către biblioteca pentru accesarea Google Cloud Messaging care este localizată în <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib

  • se accesează FileImportAndroidExisting Android Code Into Workspace
    lab09-eclipseimportandroidcode.jpg
  • se indică locația unde se găsește instalată biblioteca pentru accesarea Google Cloud Messaging, creându-se o copie a acestuia în spațiul de lucru (se bifează opțiunea Copy projects into workspace)
    lab09-eclipseimportandroidproject.jpg
  • în fereastra corespunzătoare proiectului (click dreapta pe denumirea proiectului în Package ExplorerProperties), se accesează opțiunea Android și se specifică referința către biblioteca respectivă în secțiunea Library (accesând butonul Add)
    lab09-eclipseprojectproperties.jpg

Structura Proiectului

Aplicația Android care va rula pe dispozitivul mobil va consta din:

  1. o activitate (GoogleCloudMessagingActivity) care:
    1. verifică dacă pe dispozitivul mobil este instalat Google Play Services și dacă această aplicație este actualizată;
    2. permite înregistrarea dispozitivului mobil la serverul Google Cloud Messaging dacă această operație nu a fost realizată în prealabil (o astfel de informație ar trebui stocată în SharedPreferences astfel încât operația să fie realizată o singură dată);
    3. transmite identificatorul de înregistrare obținut către serverul de aplicații (împreună cu alte informații prin care să poată fi recunoscut dispozitivul mobil - de exemplu, un nume de utilizator sau o adresă de posță electronică)
  2. un ascultător pentru mesajele cu difuzare (GoogleCloudMessagingBroadcastReceiver) către care este livrat un mesaj transmis de serverul Google Cloud Messaging, prin intermediul unei intenții
  3. (opțional) un serviciu (GoogleCloudMessagingIntentService) lansat în execuție în momentul în care este livrat mesajul (de către ascultător) în cadrul căruia acesta este procesat propriu-zis; utilizarea unui serviciu este necesară doar în situația în care prelucrarea mesajului se realizează într-un interval de timp mai mare, fiind necesar să se asigure faptul că dispozitivul mobil nu devine inactiv între timp.

lab09-structuraproiectului.jpg

Activitatea GoogleCloudMessagingActivity

Pe lângă implementarea interfeței grafice prin care se asigură interacțiunea cu utilizatorul, activitatea GoogleCloudMessagingActivity este responsabilă pentru:

1. verificarea compatibilității dispozitivului mobil pe care rulează aplicația cu Google Play Services, SDK pe care se bazează Google Cloud Messaging; această operație trebuie să se realizeze la crearea activității (metoda onCreate()) dar și în cazul revenirii la aceasta prin alte mijloace (metoda onResume())

private boolean checkPlayServices() {
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
    if (resultCode != ConnectionResult.SUCCESS) {
        if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
            GooglePlayServicesUtil.getErrorDialog(resultCode, this, Configuration.PLAY_SERVICES_RESOLUTION_REQUEST).show();
        } else {
            Log.i(TAG, "This mobile device does not support Google Play Services.");
	    finish();
        }
        return false;
    }
    return true;
}

În situația în care dispozitivul mobil este compatibil cu Google Play Services (rulează un sistem de operare Android cu o versiune mai mare decât 2.2), se lasă utilizatorului posibilitatea de a-l instala (sau actualiza, după caz) prin afișarea unei ferestre de dialog, altfel aplicația este terminată.

2. înregistrarea dispozitivului mobil la Google Cloud Messaging, în situația în care această operație nu s-a realizat în prealabil (nu există o astfel de valoare stocată ca preferință a utilizatorului):

private void registerInBackground() {
    new AsyncTask<Object, Integer, String>() {
        @Override
	protected String doInBackground(Object... params) {
	    try {
	        if (googleCloudMessaging == null) {
		    googleCloudMessaging = GoogleCloudMessaging.getInstance(context);
		}
		registrationId = googleCloudMessaging.register(Configuration.PROJECT_NUMBER);
		// store the registration ID into the SharedPreferences
		// the ID is not yet registered with the application server
		setSharedPreferences(context, registrationId, Configuration.FAILURE);
            } catch (IOException exception) {
	        registrationId = "an exception has occurred: " + exception.getMessage();
	        exception.printStackTrace();
            }
	    return registrationId;
	}
 
	@Override
	protected void onPostExecute(String registrationId) {
	    registrationIdTextViewValue.append(registrationId + "\n");
	    Log.i(TAG, "registration ID supplied by the Google Cloud Messaging Server is: "+registrationId);
	}
    }.execute(null, null, null);
}
Întrucât procesul de înregistrare la Google Cloud Messaging implică comunicație prin rețea, este necesar ca acesta să se realizeze asincron, pe un fir de execuție separat, motiv pentru care procesarea a fost realizată în cadrul metodei doInBackground a unui obiect de tip AsyncTask.

Testarea existenței unei valori corespunzătoare identificatorului de înregistrare în preferințele utilizatorului se face foarte ușor, acesta având o structură de tip asociere între o cheie și o valoare.

private String getRegistrationId(Context context) {
    final SharedPreferences sharedPreferences = getSharedPreferences(context);
    String registrationId = sharedPreferences.getString(Configuration.REGISTRATION_ID_PROPERTY, "");
    if (registrationId.isEmpty()) {
        Log.i(TAG, "Registration ID not found into the SharedPreferences.");
        return "";
    }
    // check whether the application was updated 
    // if it is the case, the registration ID previously supplied must be cleared
    // as it is not guaranteed to work with the current version
    int registeredVersion = sharedPreferences.getInt(Configuration.APPLICATION_VERSION_PROPERTY, Integer.MIN_VALUE);
    int currentVersion = getApplicationVersion(context);
    if (registeredVersion != currentVersion) {
        Log.i(TAG, "The application version was changed.");
        return "";
    }
    return registrationId;
}

De remarcat este faptul că în preferințele utilizatorului va fi stocată și versiunea aplicației Android pentru care a fost generat identificatorul de înregistrare, iar în situația în care aceasta este actualizată, se va realiza o nouă înregistrare la serverul Google Cloud Messaging, de vreme ce o astfel de valoare identifică în mod unic o aplicație caracterizată prin denumirea pachetului și versiunea acesteia.

3. transmiterea identificatorului de înregistrare către serverul de aplicații printr-o cerere HTTP de tip POST care va conține și alte informații prin care să poată fi referit utilizatorul respectiv (de exemplu, numele de utilizator sau adresa de poștă electronică):

public static class RegisterButtonListener implements OnClickListener {
    @Override
    public void onClick(View view) {
        new AsyncTask<Object, Integer, Integer>() {
            @Override
            protected Integer doInBackground(Object... params) {
                try {
                    HttpClient httpClient = new DefaultHttpClient();        
 
                    HttpPost httpPost = new HttpPost(Configuration.GOOGLECLOUDMESSAGING_WEBSERVER_URL);
 
                    // send the username, email and registration ID as fields of the HTTP body
                    // to the application server
                    List<NameValuePair> requestParameters = new ArrayList<NameValuePair>();        
                    requestParameters.add(new BasicNameValuePair(Configuration.USER_NAME, usernameEditText.getText().toString()));
                    requestParameters.add(new BasicNameValuePair(Configuration.EMAIL, emailEditText.getText().toString()));
                    requestParameters.add(new BasicNameValuePair(Configuration.REGISTRATION_ID, registrationIdTextViewValue.getText().toString()));
                    UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(requestParameters, HTTP.UTF_8);
                    httpPost.setEntity(urlEncodedFormEntity);
 
                    HttpResponse httpPostResponse = httpClient.execute(httpPost); 
 
                    // transmission of information to the application server was successful
                    if (httpPostResponse.getStatusLine().getStatusCode() == 200)
                        return Configuration.SUCCESS;
                } catch (Exception exception) {
                    Log.e(TAG, exception.getMessage());
                    exception.printStackTrace();
                }
                // transmission of information to the application server was not successful
		return Configuration.FAILURE;
            }
 
            @Override
            protected void onPostExecute(Integer result) {
                // ...
            }
        }.execute(null, null, null);					
    }			
}

În cadrul serverului de aplicații va trebui procesată o astfel de cerere prin stocarea informațiilor transmise în cadrul unei baze de date și transmiterea unui răspuns corespunzător prin care se indică rezultatul operației, pentru a fi marcat și la nivelul clientului (în preferințele utilizatorului).

Transmiterea identificatorului de înregistrare către serverul de aplicații se realizează o singură dată, astfel încât să nu se înregistreze duplicate în baza de date.

Ascultătorul GoogleCloudMessagingBroadcastReceiver

Implementarea unui ascultător pentru mesaje cu difuzare poate fi realizată în două variante:

  1. în cazul în care procesarea mesajului implică operații triviale (ce pot fi realizate într-un interval de timp rezonabil), se poate defini un ascultător BroadcastReceiver, prelucrarea propriu-zisă realizându-se în cadrul metodei onReceive (ce primește ca parametri contextul și intenția care a apelat-o, cu informațiile din corpul mesajului atașate într-un obiect de tip Bundle), apelată în mod automat în momentul în care mesajul este livrat.
  2. în situația în care procesarea mesajului presupune mai multe operații realizate într-un interval de timp semnificativ, prelucrarea se va realiza în cadrul unui serviciu dedicat (de tip IntentService), ce va rula în fundal, în timp ce se obține accesul la un tip de mutex ce împiedică dispozitivul mobil să intre într-o stare de inactivitate care ar suspenda tratarea evenimentului de livrare a unui mesaj, dacă aceasta nu este încă completată:
    public class GoogleCloudMessagingBroadcastReceiver extends WakefulBroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // create a component associated to the service that will handle the intent
            ComponentName component = new ComponentName(context.getPackageName(), GoogleCloudMessagingIntentService.class.getName());
            // start the service
            // maintain the mobile device awake
            startWakefulService(context, (intent.setComponent(component)));
            setResultCode(Activity.RESULT_OK);
        }
    }

    Așa cum se poate observa, a fost utilizat un ascultător de tip WakefulBroadcastReceiver, delegarea procesării mesajului către serviciul dedicat făcându-se prin metoda startWakefulService ce asigură și obținerea accesului la mutexul care împiedică intrarea dispozitivului mobil într-o stare de inactivitate.

Serviciul GoogleCloudMessagingIntentService

În cadrul serviciului pentru tratarea evenimentului (de tip IntentService) are loc procesarea propriu-zisă a mesajului, aceasta fiind realizată în cadrul metodei onHandleIntent() care primește ca parametru aceeași intenție care a fost transmisă ascultătorului, și pentru care a fost obținut mutextul care împiedică intrarea dispozitivului mobil într-o stare de inactivitate.

Prelucrarea unui mesaj se face în funcție de tipul acestuia, furnizat de metoda getMessageType a obiectului de tip GoogleCloudMessaging, pe baza informațiilor din intenție. Odată cunoscut tipul mesajului și datele asociate prin Bundle, utilizatorul poate realiza orice acțiune, de la transmiterea unei notificări la modificarea conținutului unor controale grafice din cadrul activității.

Ulterior analizei mesajului, trebuie să se asigure și eliberarea mutexului ce împiedică intrarea dispozitivului mobil într-o stare de inactivitate, prin apelul metodei completeWakefulIntent() căreia i se transmite ca parametru aceeași intenție pentru care a fost obținută această resursă.
public class GoogleCloudMessagingIntentService extends IntentService {
 
    private final static String TAG = "GoogleCloudMessagingIntentService";
 
    public GoogleCloudMessagingIntentService() {
        super(TAG);
    }
 
    @Override
    protected void onHandleIntent(Intent intent) {
        // the intent is supplied via the BroadcastReceiver
 
        GoogleCloudMessaging googleCloudMessaging = GoogleCloudMessaging.getInstance(this);
 
        Bundle extras = intent.getExtras();
        if (!extras.isEmpty()) {
            String messageType = googleCloudMessaging.getMessageType(intent);
            // filter the message according to its type
            // send a notification containing the data attached to the message (as extras)
            if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
                sendNotification("Send error: " + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
                sendNotification("Deleted messages on server: " + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
                sendNotification("Received: " + extras.toString());
            }
        }
        // release the wake lock provided by the WakefulBroadcastReceiver.
        GoogleCloudMessagingBroadcastReceiver.completeWakefulIntent(intent);
    }
 
    // create a notification containing the message and post it
    private void sendNotification(String message) {
        // ...
    }
}

Permisiuni AndroidManifest.xml

  • în fișierul AndroidManifest.xml se indică versiunea Google Cloud Messaging în cadrul elementului <application>:
    <meta-data android:name="com.google.android.gms.version"
               android:value="@integer/google_play_services_version" />
  • în fișierul AndroidManifest.xml se precizează permisiunile necesare pentru ca aplicația să poată utiliza funcționalitățile oferite de Google Cloud Messaging
    <manifest package="ro.pub.systems.pdsd.lab09.GoogleCloudMessagingClient" ...>
     
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
     
        <permission android:name="ro.pub.systems.pdsd.lab09.GoogleCloudMessagingClient.permission.C2D_MESSAGE"
            android:protectionLevel="signature" />
        <uses-permission android:name="ro.pub.systems.pdsd.lab09.GoogleCloudMessagingClient.permission.C2D_MESSAGE" />
     
        <application ...>
            <receiver
                android:name=".GoogleCloudMessagingBroadcastReceiver"
                android:permission="com.google.android.c2dm.permission.SEND" >
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                    <category android:name="ro.pub.systems.pdsd.lab09.GoogleCloudMessagingClient" />
                </intent-filter>
            </receiver>
            <service android:name=".GoogleCloudMessagingIntentService" />
        </application>
     
    </manifest>
Pentru a se asigura faptul că aplicația Android va rula corespunzător, ținând cont de faptul că Google Cloud Messaging nu poate fi folosit pe dispozitive mobile rulând un sistem de operare Android anterior versiunii 2.3, proprietatea android:minSdkVersion va avea o valoare egală sau mai mare cu 8.
  • permisiunea android.permission.INTERNET este necesară pentru ca aplicația să poate transmite identificatorul său serverului de aplicații, prin intermediul căruia se realizează înregistrarea sa
  • permisiunea android.permission.GET_ACCOUNTS este necesară întrucât Google Cloud Messaging solicită (pentru sisteme de operare Android anterioare versiunii 4.0.4) un cont Google
  • permisiunea android.permission.WAKE_LOCK (opțională) este necesară pentru a se preveni întreruperea activității în momentul în care este primit un mesaj
  • permisiunea android.permission.RECEIVE este necesară pentru ca aplicația să se poată înregistra (în cadrul serverului de aplicații) și pentru a putea primi mesaje
  • permisiunea de tip permission.C2D_MESSAGE (prefixată de denumirea pachetului care identifică în mod unic aplicația) pentru a preveni altă aplicație Android să se înregistreze (în cadrul serverului de aplicații) și să primească mesajele care îi sunt destinate
Este necesar ca acest tip de permisiune să utilizeze această sintaxă, în caz contrar mesajele nefiind transmise aplicației Android.
  • un ascultător (de tipul com.google.android.c2dm.intent.RECEIVE) având permisiunea com.google.android.c2dm.SEND astfel încât doar motorul Google Cloud Messaging să îi poată transmite mesaje
Proprietatea category a ascultătorului trebuie să aibă ca valoare denumirea pachetului aplicației Android.
În cazul în care este asociat și un serviciu (șablon de proiectare frecvent utilizat, însă nu obligatoriu), ascultătorul va fi un obiect de tipul WakefulBroadcastReceiver, creând un mutex parțial pentru a se asigura faptul că dispozitivul mobil rulează.
  • un serviciu (de obicei, un obiect de tip IntentService) - opțional - căruia i se transmite mesajul Google Cloud Messaging (de către obiectul de tip WakefulBroadcastReceiver) având sarcina procesării sale și asigurând faptul că dispozitivul nu își întrerupe activitatea în timpul acestui proces
În situația în care nu se dorește să se utilizeze un serviciu, procesarea mesajului poate fi realizată în ascultător (în acest caz, un obiect de tipul BroadcastReceiver).

Reguli Proguard

Se precizează regulile pentru obfuscatorul Proguard (în fișierul proguard-project.txt din rădăcina proiectului), astfel încât acesta să nu elimine clasele necesare:

-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}
 
-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
    public static final *** NULL;
}
 
-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
    @com.google.android.gms.common.annotation.KeepName *;
}
 
-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

Proiectarea și dezvoltarea serverului

Arhitectura Google Cloud Messaging implică existența a două servere:

1. un server de conexiune furnizat de serviciul Google Cloud Messaging, prin intermediul căruia pot fi livrate mesaje către un client Android, transmise anterior de la un server de aplicații; la rândul său, acesta este disponibil în două variante:

În alegerea uneia dintre cele două variante ale serverului de conexiune, trebuie să se țină cont de funcționalitățile puse la dispoziție de acestea:

FUNCȚIONALITATE HTTP CCS/XMPP
sens conexiune server GCM → client Android (unidirecțional) server GCM ↔ client Android (bidirecțional)
tip comunicație sincron
expeditorul se conectează la destinatar printr-o conexine HTTP nepersistentă, transmite o cerere POST și se blochează în așteptarea unui răspuns
asincron
expeditorul se conectează la destinatar printr-o conexiune XMPP persistentă, putând trimite/primi mesaje la viteză maximă (rezultatul fiind transmis sub forma unor notificări codificate utilizând același protocol)
corp mesaj text sau JSON transmise prin intermediul unei cereri HTTP POST JSON încapsulat într-un mesaj XMPP
destinatar multiplu NU (pentru mesaje text)
DA (pentru mesaje JSON)
NU
înregistrare anterioară NU DA
Cele două soluții pentru serverul de conexiune la serviciul Google Cloud Messaging pot fi utilizate concomitent, în funcție de specificațiile aplicației respective.

2. un server de aplicații, implementat de programator, pentru a transmite mesaje către serverul de conexiune Google Cloud Messaging ce vor fi livrate ulterior clientului Android (atunci când acesta devine disponibil); acesta trebuie să îndeplinească mai multe condiții:

  • să poată comunica cu clientul Android (să fie disponibil la o adresă publică în Internet sau să facă parte din cadrul aceleiași rețele, în caz contrar);
  • să construiască mesaje către serverul de conexiune Google Cloud Messaging într-un format acceptat de acesta;
  • să poată stoca o cheie publică prin intermediul căreia să se autentifice în cadrul serverului de conexiune Google Cloud Messaging precum și identificatorii de identificare a clienților Android pentru a specifica locația la care trebuie livrate mesajele;
  • să poată primi mesaje și să își adapteze rata de transmisie în funcție de informațiile furnizate cu privire la rezultatul acestora;
  • să genereze identificatori unici pentru mesajele pe care le trimite.
În continuare, va fi prezentată structura unui mesaj folosind un server de conexiune HTTP.

Structura unui mesaj

Mesajele interschimbate între serverul de aplicații și serverul de conexiune Google Cloud Messaging de tip HTTP pot fi:

  • o cerere de tip POST transmisă către https://android.googleapis.com/gcm/send și formată din:
    • un antet care specifică informațiile necesare autentificării în cadrul serviciului Google Cloud Messaging, precum și formatul mesajului;
    • un corp (în format text sau JSON) în care informațiile propriu-zise sunt conținute în perechi de tip (atribut, valoare).
  • un răspuns care conține rezultatul operației precum și alte informații suplimentare.

Cerere: server de aplicații → server de conexiune GCM

Un mesaj transmis de serverul de aplicații către serverul de conexiune Google Cloud Messaging de tip HTTP este o cerere de tip POST ce conține informațiile ce trebuie livrate către unul sau mai multe dispozitive mobile (în momentul în care acestea vor deveni disponibile).

// the address of the Google Cloud Messaging HTTP connection server 
// to which the application server sends a POST request in order to be further transmitted to the mobile device
 
final public static String REQUEST_SERVER_URL = "https://android.googleapis.com/gcm/send";
 
// ...
 
HttpPost httpRequest = new HttpPost(REQUEST_SERVER_URL);

Un astfel de mesaj va trebui să conțină:

  • în antet:
    • informațiile de autentificare în cadrul serverului Google Cloud Messaging - cheia publică obținută prin intermediul Google Developer's Console, certificându-se faptul că există drepturile necesare pentru realizarea unei conexiuni de acest tip;
    • informații referitoare la tipul de mesaj (text sau JSON);
  • în corp: datele propriu-zise.
Antet

În cadrul antetului cererii de tip POST trebuie să fie incluse câmpurile:

  • Authorization - conține informațiile necesare procesului de autentificare în cadrul serverului Google Cloud Messaging, având forma key=, urmată de cheia publică furnizată prin intermediul Google Developer's Console.
  • Content-Type - indică tipul pe care îl are corpul mesajului, acesta putând fi text (application/x-www-form-urlencoded;charset=UTF-8) sau JSON (application/json).
În situația în care nu este inclus câmpul Content-Type, se așteaptă ca mesajul să aibă un corp de tip text.
/**
 * HTTP headers
 * 
 * Authorization: key=API key provided from Google Developer Console
 * Content-Type: the format of the HTTP body (may be omitted for plain text)
 *   - application/x-www-form-urlencoded;charset=UTF-8 for plain text
 *   - application/json for JSON
 *
 */
httpRequest.setHeader("Authorization", "key=" + Constants.API_KEY);
httpRequest.setHeader("Content-Type", "application/json");
Corp

Informațiile conținute de corpul unei cereri de tip POST au forma unor perechi (cheie, valoare), fiind definită o serie de parametrii standard, în funcție de formatul în care este transmis mesajul.

PARAMETRU DESCRIERE text JSON obligativitate
registration_id un identificator de înregistrare descriind dispozitivul mobil către care se dorește să se livreze mesajul respectiv
registration_ids o listă conținând 1-1000 identificatori de înregistrare indicând dispozitivele mobile către care se dorește să se livreze mesajul respectiv
notification_key șir de caractere asociat mai multor dispozitive mobile (1-10) ce aparțin aceluiași utilizator
collapse_key șir de caractere (aleator, definit de utilizator) folosit pentru a identifica mesajele de același tip, astfel încât, pe serverul Google Cloud Messaging să se stocheze doar cea mai recentă valoare asociată, în scopul optimizării traficului de mesaje către dispozitivul mobil (dacă acesta nu este disponibil la un moment dat și au fost transmise mai multe mesaje, va fi stocat doar cel mai recent); se permite stocarea a maximum 4 chei de acest tip pe serverul Google Cloud Messaging
data obiect JSON format din perechi (atribut, valoare) reprezentând conținutul propriu-zis al mesajului, acestea fiind incluse în cadrul intenției sub forma unui obiect Bundle (ca extras), având aceeași structură;
atributul poate avea orice valoare, cu excepția unor cuvinte rezervate (from, google.*), evitându-se pe cât posibil folosirea denumirilor de parametri, întrucât aceștia vor fi suprascriși;
pentru valoare poate fi utilizat orice tip de obiect JSON, însă la nivelul serverului Google Cloud Messaging se face conversia la șiruri de caractere;

dimensiunea obiectului este limitată la 4kb;
nu există limitări în privința numărului de perechi (atribut, valoare);
data.<atribut>
delay_while_idle indică faptul că mesajul nu trebuie transmis imediat dacă dispozitivul se află într-o stare de inactivitate, urmând a fi livrat în momentul în care acesta devine activ; valoarea implicită este false
time_to_live durata de stocare a mesajului pe serverul Google Cloud Messaging (exprimată în secunde) în situația în care dispozitivul mobil nu este disponibil; valoarea implicită este de 4 săptămâni
restricted_package_name șir de caractere referind denumirea pachetului corespunzător unei aplicații Android, astfel încât mesajul să fie livrat doar acelor identificatori de înregistrare care au fost generați pentru acesta
dry_run utilizat în scop de testare, pentru a verifica funcționalitatea cererii de tip POST fără a transmite propriu-zis un mesaj către dispozitivul mobil; valoarea implicită este false
Locația (dispozitivul mobil) către care va fi livrat un mesaj trebuie să fie specificată fie prin parametrul registration_id / registration_ids, fie prin parametrul notification_key.
Pentru mesajele de tip text, se va defini câte un parametru data.<atribut> pentru fiecare informație, asociindu-i-se valoarea respectivă. În această situație, se mențin restricțiile din cazul mesajelor de tip JSON.
/**
 * HTTP body
 * 
 * -> registration_id/registration_ids indicates the recipient/recipients
 * (for each mobile device to which the message is to be transmitted the message must specify
 * the id provided by the GCM server during the registration process)
 * 
 * -> data contains the payload to be transmitted; the developer may define its own keys
 * 
 * -> other fields: collapse_key, delay_while_idle, time_to_live, restricted_package_name, dry_run
 * 
 * The complete list of the message parameters can be found out at: 
 * http://developer.android.com/google/gcm/server.html#params
 */
 
JSONObject requestBody = new JSONObject();
JSONArray registrationIds = new JSONArray();
registrationIds.add(registrationId);
requestBody.put("registration_ids", registrationIds);
JSONObject payload = new JSONObject();
payload.put("message", message);
requestBody.put("data", payload);

O astfel de cerere va avea forma:

Authorization:key=...
Content-Type:application/json
{
 "registration_ids": ["..."],
 "data": {
     "message": "..."
 }
}

Ulterior construirii cererii, aceasta va trebui transmisă, operația fiind realizată sincron, producând blocarea metodei în așteptarea răspunsului de la server:

HttpClient client = HttpClientBuilder.create().build();
 
// ...
 
httpRequest.setEntity(new StringEntity(JSONValue.toJSONString(requestBody)));
 
// send the request
HttpResponse httpResponse = client.execute(httpRequest);
În situația în care comunicația dintre serverul de aplicații și serverul Google Cloud Messaging nu poate fi realizată, ar trebui investigată posibilitatea blocării porturilor pe care se realizează transmisia de mesaje (de obicei 5228, dar și 5529, respectiv 5530).

Răspuns: server de conexiune GCM → server de aplicații

Răspunsul transmis de serverul de conexiune la Google Cloud Messaging către serverul de aplicații conține rezultatul operației solicitate, acesta putând fi investigat prin intermediul codului de stare:

int responseCode = httpResponse.getStatusLine().getStatusCode();

Aceasta poate avea valorile:

  • 200 - dacă mesajul a fost procesat cu succes;
  • o valoare diferită de 200 - în situația în care mesajul a fost respins de serverul Google Cloud Messaging.
Mesaj procesat cu succes

În situația în care mesajul a fost procesat cu succes, codul de stare returnat este 200, iar corpul răspunsului conține informații suplimentare, reprezentate sub forma unor perechi de tip (atribut, valoare) în care există un set de parametri predefiniți.

text

Există două variante de răspunsuri:

  1. mesaj care a fost procesat cu succes
    1: id=identificatorul mesajului care a fost transmis
    2(opțional): registration_id=identificator de înregistrare canonic al dispozitivului mobil stocat pe serverul Google Cloud Messaging
  2. mesaj care nu a fost procesat cu succes
    1: Error=cod de eroare furnizat de serverul Google Cloud Messaging

Algoritmul de procesare a unui răspuns este următorul:

if (firstLine.startsWith("id")) {
    if (secondLine.equals("registration_id")) {
        old_registration_id = get registration_id from the request field
        replace old_registration_id with new registration_id in the request / application server database
    }
} else {
    switch(Error) {
        case "NotRegistered":
            // the application was uninstalled from the device
            // or it does not have a broadcast receiver in order to process com.google.android.c2dm.intent.RECEIVE intents
            remove the registration ID from the application server database
            break;
        default:
            // non-recoverable error regarding the registration ID
            remove the registration ID from the application server database
            break;
    }
}

JSON

PARAMETRU DESCRIERE
multicast_id identificator unic ce reprezintă un mesaj transmis către mai multe dispozitive mobile
success numărul de mesaje care au fost procesate cu succes
failure numărul de mesaje care nu au fost procesate cu succes
canonical_ids număr de rezultate care conțin un identificator de înregistrare canonic
results listă de obiecte conținând starea mesajelor procesate, în ordinea în care au fost incluse în cadrul solicitării (pe baza identificatorului de înregistrare):
message_id: șir de caractere reprezentând mesajul respectiv, în situația în care a fost procesat cu succes
registration_id: în situația în care este inclus, specifică faptul că mesajul a fost procesat, dar serverul Google Cloud Messaging deține un alt identificator de înregistrare canonic pentru dispozitivul mobil, astfel încât solicitările ulterioare ar trebui să îl utilizeze pentru a nu fi respinse; nu este inclus dacă s-a produs o eroare
error: șir de caractere indicând eroarea ce s-a produs în timpul procesării mesajului; valoarea Unavailable indică faptul că serverul Google Cloud Messaging nu era disponibil la momentul transmiterii solicitării, astfel încât aceasta trebuie retransmisă

Algoritmul de procesare a unui răspuns este următorul:

if (failure != 0 && canonical_ids != 0) {
    for-each (result ∈ results) {
        if (result.registration_id) {
            // the old registration ID is not included within the response
            old_registration_id = get registration_id from the request field registration_ids, having the same index
            
            replace old_registration_id with new result.registration_id in the request
        } else {
            switch(error) {
                case "Unavailable":
                    retry sending the request
                    break;
                case "NotRegistered":
                    // the application was uninstalled from the device
                    // or it does not have a broadcast receiver in order to process com.google.android.c2dm.intent.RECEIVE intents
                    remove the registration ID from the application server database
                    break;
                default:
                    // non-recoverable error regarding the registration ID
                    remove the registration ID from the application server database
                    break;
            }
        }
    }
}
Mesaj respins de serverul Google Cloud Messaging

În situația în care mesajul a fost respins de serverul Google Cloud Messaging, pot fi returnate mai multe coduri de stare:

COD DE STARE DESCRIERE
400 (doar pentru mesaje JSON) eroare ce indică faptul că mesajul nu a putut fi parsat ca JSON sau conține câmpuri invalide (fie atribute - denumire, fie valoare), detalii suplimentare fiind incluse în corpul mesajului pentru a remedia problema înainte de retransmisie
401 eroare privind procesul de autentificare a serverului de aplicații în cadrul serverului Google Cloud Messaging
5xx eroare în cadrul serverului Google Cloud Messaging (în timpul procesării mesajului) sau indisponibilitate temporară a acestuia; mesajul trebuie transmis potrivit informațiilor furnizate de antetul Retry-After

Cele mai frecvente tipuri de erori sunt:

  • MissingRegistration - cauzată de faptul că nu se include câmpul registration_id (text) / registration_ids (JSON) în corpul mesajului;
  • InvalidRegistration - valoarea identificatorului de înregistrare trimisă prin mesaj este diferită de valoarea primită de telefon prin intenția com.google.android.c2dm.intent.REGISTRATION;
  • MismatchSenderId - utilizatorul care a transmis mesajul nu are permisiunea de a o face (în cadrul procesului de înregistrare a dispozitivului mobil se precizează care sunt utilizatorii care pot transmite mesaje);
  • NotRegistered - identificatorul de înregistrare nu mai este valid, în următoarele cazuri:
    • aplicația Android anulează înregistrarea la serverul Google Cloud Messaging folosind o intenție de tip com.google.android.c2dm.intent.UNREGISTER;
    • aplicația Android a fost actualizată astfel încât să nu mai prelucreze intenții de tip com.google.android.c2dm.intent.RECEIVE.
    • aplicația Android a fost dezinstalată de pe dispozitivul mobil (în acest caz, nu se garantează că procesul de anulare a înregistrării la Google Cloud Messaging va fi realizată automat);
    • identificatorul de înregistrare a expirat;
  • MessageTooBig - dimensiunea mesajului propriu-zis (atât a valorilor, cât și a atributelor) depășește 4096 octeți
  • InvalidDataKey - mesajul propriu-zis include parametrii care sunt rezervați (from, google.*) spre a fi utilizați de intenția com.google.android.c2dm.intent.RECEIVE;
  • InvalidTtl - valoarea parametrului time_to_live nu este o valoare întreagă (exprimând o durată de timp, în secunde) cuprinsă între 0 și 2419200 (4 săptămâni);
  • cod de stare 401 - utilizatorul care a transmis un mesaj către serverul Google Cloud Messaging nu a putut fi autentificat datorită uneia dintre următoarele cauze:
    • lipsește câmpul Authorization din antetul mesajului sau a fost folosită o sintaxă incorectă;
    • este folosit un număr de proiect invalid drept cheie publică;
    • cheia publică este validă, însă serviciul Google Cloud Messaging este dezactivat pentru contul care a transmis mesajul respectiv;
    • mesajul a fost transmis de pe un server de aplicații de la o adresă Internet care nu a fost precizată la momentul în care a fost generată cheia publică.
  • InternalServerError / cod de stare 500 - s-a produs o eroare pe serverul Google Cloud Messaging la procesarea mesajului, acesta trebuind retransmis; în cazul în care problema persistă, aceasta trebuie raportată la https://groups.google.com/forum/?fromgroups#!forum/android-gcm;
  • Unavailable / cod de stare 501-599 - mesajul nu a putut fi procesat de serverul Google Cloud Messaging în timp util și trebuie retransmis ținând cont de următoarele recomandări:
    • dacă este inclus antetul Retry-After, trebuie respectată valoarea indicată;
    • trebuie implementat un mecanism de adaptare a transmisiei (eng. exponential back-off), astfel încât perioada după care se retransmite un mesaj să fie modificată în mod exponențial după o încercare eșuată; în cazul în care se transmit mai multe mesaje, acestea trebuie tratate independent; utilizatorilor care transmit prea multe mesaje către serverul Google Cloud Messaging li se refuză accesul ulterior;
  • InvalidPackageName - mesajul a fost transmis folosind un identificator de înregistrare

Tratarea cererilor de înregistrare

Un server de aplicații trebuie să trateze și mesajele provenite de la clientul Android (cereri de înregistrare), stocând informațiile referitoare la identificatorii de înregistrare în baza de date locală, pentru a fi utilizate ulterior, când se dorește precizarea unei locații către care să fie livrat un mesaj.

Comunicația dintre clientul Android și serverul de aplicații poate folosi un protocol de comunicație definit de utilizator, folosind propriile reguli pentru definirea mesajelor schimbate între aceste entități.

@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Enumeration<String> parameters = request.getParameterNames();
    boolean found = false;
    while(parameters.hasMoreElements()) {
        String parameter = (String)parameters.nextElement();
        if (parameter.equals(Constants.REGISTRATION_ID)) {
            found = true;
            registrationId = request.getParameter(parameter);
        }
        if (parameter.equals(Constants.USER_NAME)) {
            userName = request.getParameter(parameter);
        }
        if (parameter.equals(Constants.EMAIL)) {
            email = request.getParameter(parameter);
        }
    }
    if (found) {
        ArrayList<String> values = new ArrayList<String>();
        values.add(registrationId);
        values.add(userName);
        values.add(email);
        values.add(general.Constants.SYSTEM_FUNCTION[0]);
        try {
       	    DataBaseConnection.insertValuesIntoTable(Constants.TABLE_NAME, null, values, true);
        } catch (Exception exception) {
            System.out.println("exceptie: "+exception.getMessage());
            if (Constants.DEBUG)
                exception.printStackTrace();
        }
    }
    response.setContentType("text/html");
    try {
        PrintWriter printWriter = new PrintWriter(response.getWriter());
        ArrayList<ArrayList<Object>> records = DataBaseConnection.getTableContent(Constants.TABLE_NAME, null, null, null, null);
        GoogleCloudMessagingGraphicUserInterface.displayGoogleCloudMessagingGraphicUserInterface(records, printWriter);
    } catch (Exception exception) {
        System.out.println("exceptie: "+exception.getMessage());
        if (Constants.DEBUG)
            exception.printStackTrace();
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    }
    response.setStatus(HttpServletResponse.SC_OK);
}

Se observă faptul că se prelucrează cererea de tip POST, iterându-se pe toți parametrii incluși în scopul identificării valorilor asociate atributelor denumite nume de utilizator, adresă de poștă electronică și identificator de înregistrare (conform protocolului de comunicație definit de utilizator), aceștia fiind stocați în baza de date locală pentru a fi utilizați în transmiterea ulterioară a unui mesaj.

Este foarte important să fie specificată o stare în cadrul răspunsului (prin apelul metodei setStatus a obiectului HttpServletResponse) desemnând rezultatul procesării mesajului, în caz contrar clientul Android blocându-se până la expirarea perioadei de timp pentru așteptare (eng. timeout).

Activitate de Laborator

1. Să se acceseze Google Developer's Console, după ce a fost realizată autentificarea cu datele contului Google (nume de utilizator, parolă):

  • se generează un număr de proiect;
  • se activează serviciul Google Cloud Messaging;
  • se generează o cheie publică pentru transmiterea de mesaje provenind de la un server, indiferent de adresa Internet la care se găsește acesta.

Mai multe detalii pot fi obținute în secțiunea Configurare.

În cazul în care nu dispuneți de un cont Google, acesta va trebui creat înainte de a implementa aceste cerințe.

Clientul Android

2. Să se configureze clientul pentru a putea rula o aplicație Android folosind Google Cloud Messaging:

  • se instalează SDK-ul Google Cloud Messaging
  • se configurează mașina pe care va rula aplicația
    • în cazul în care se utilizează un emulator, se configurează astfel încât acesta să aibă instalat Google Play Services
    • în cazul în care se utilizează un dispozitiv fizic, acesta trebuie să ruleze un sistem de operare Android cu o versiune ulterioară 2.2, având asociat un cont Google

Mai multe detalii pot fi obținute în secțiunea Configurare client

3. În mediul de dezvoltare să se deschidă proiectele google-play-services_lib (acesta ar trebui să se găsească și la <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib după ce a fost instalat SDK-ul Google Cloud Messaging) și GoogleCloudMessagingClient.

4. În interfața ro.pub.systems.pdsd.lab09.googlecloudmessagingclient.Configuration, trebuie modificate următoarele valori:

  • PROJECT_NUMBER - cu numărul de proiect generat de consola Google API;
  • GOOGLECLOUDMESSAGING_WEBSERVER_URL - cu adresa Internet la care rulează serverul de aplicații
    • dispozitiv mobil - adresa Internet indicată ca default gateway a rețelei de calculatoare din care fac parte dispozitivul mobil și mașina pe care se găsește serverul de aplicații
    • AVD - 10.0.2.1
    • Genymotion - 192.168.56.1
Portul pe care rulează serverul web Apache Tomcat este de regulă 8080 (dacă nu s-au realizat alte modificări în fișierul de configurare server.xml).

5. Să se ruleze aplicația Android și să se genereze identificatorul de înregistrare, care va fi transmis prin accesarea butonului Send de îndată ce serverul de aplicații devine disponibil.

Serverul de Aplicații

Informații referitoare la configurarea serverului de aplicații pot fi obținute la Configurare Server.

6. Să se descarce cea mai recentă versiune stabilă a serverului web Apache Tomcat și să se dezarhiveze.

7. Să se descarce cea mai recentă versiune stabilă a mediului de dezvoltare Eclipse IDE for Java EE Developers, integrându-se cu serverul web Apache Tomcat.

8. Să se instaleze motorul pentru gestiunea bazelor de date MySQL și să se ruleze script-ul Laborator09.sql din cadrul proiectului GoogleCloudMessagingWebServer care creează o bază de date cu o tabelă în care vor fi stocate informațiile referitoare la identificatorii de înregistrare transmiși de clienții Android.

CREATE DATABASE googlecloudmessaging;
 
USE googlecloudmessaging;
 
CREATE TABLE registered_devices (
	id              INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
	registration_id TEXT NOT NULL,
	username        VARCHAR(255),			
	email	        VARCHAR(255),
	creation_time   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Rularea acestuia se poate realiza folosind una dintre următoarele variante, presupunând că vă aflați în directorul în care se găsește acest fișier:

  1. mysql -u root -p <Laborator09.sql
  2. mysql -u root -p
    Enter password:
    1. mysql> source Laborator09.sql;

      sau

    2. mysql> \. Laborator09.sql;

9. În mediul de dezvoltare Eclipse IDE for Java EE Developers să se deschidă proiectul GoogleCloudMessaging.

10. În interfața general.Constants, trebuie modificate următoarele valori:

  • DATABASE_PASSWORD - parola de administrare a motorului pentru gestiunea bazei de date MySQL, care a fost introdusă la instalarea acestui produs;
  • API_KEY - cheia publică de acces a serverului Google Cloud Messaging de către serverul de aplicații, așa cum a fost generată de Consola Google API.

11. În acest moment, se poate realiza comunicația client Android ↔ server de aplicații prin intermediul serviciului Google Cloud Messaging:

  • clientul Android trimite identificatorul de înregistrare către serverul de aplicații;
    lab09-mobiledeviceunregistered.jpg
    lab09-mobiledeviceregistered.jpg
  • se accesează servletul GoogleCloudMessagingRegisterServlet prin intermediul unei aplicații de navigare (eng. browser) pentru a se transmite un mesaj către dispozitivul mobil, acesta fiind afișat sub forma unei notificări.
    lab09-googlecloudmessagingwebserver.jpg

Resurse Utile

laboratoare/laborator08.1430131209.txt.gz · Last modified: 2016/02/09 11:19 (external edit)
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0