Connessione Android Wear e Smartphone (Message.API)
Introduzione
In questo articolo vorrei mostrare i codici essenziali per connettere in maniera stabile la tua applicazione Android su smartphone con la tua applicazione sugli smartwatch Android Wear.
Requisiti per seguire il tutorial:
- conoscere Android
- aver già visto la libreria Android Wear
- conoscere Android Studio
In questo articolo farò uso solo delle Message API di Android Wear e del WearableListenerService per creare un canale di comunicazione dallo smartwatch allo Smartphone e viceversa. La comunicazione tra il Service in ascolto dei messaggi inviati dalla controparte avviene tramite LocalBroadcastManager e BroadcastReceiver.
Partiamo creando un progetto su Android Studio che sia anche un progetto android wear come in figura:
build.gradle
Siccome stiamo usando Gradle, dobbiamo innanzitutto aggiungere la libreria google-play-services per poter connettere lo smartphone con android wear.
all’interno delle dependencies{} dentro al file build.gradle (module mobile) aggiungiamo la riga
compile ‘com.google.android.gms:play-services:+’,
dove is simbolo + sostituisce il numero della versione delle api che cambia con gli aggiornamenti. Con il + potremmo non preoccuparci di riferimenti a versioni sbagliate o vecchie.
La stessa riga dovrà essere aggiunta all’interno delle dependencies{} dentro al file build.gradle (module wear) .
Manifest
Adesso bisognerà modificare il manifest di entrambi i progetti mobile e wear.
Aggiungiamo all’interno del manifest <manifest ..> … </manifest>, fuori dal tag <application.. /> il seguente meta:
1 2 3 |
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> |
MainActivity
Andiamo ad aggiungere il seguente codice alla MainActivity dell’applicazione sullo SmartPhone.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mApiClient = new GoogleApiClient.Builder(this).addApi(Wearable.API).addConnectionCallbacks(connCallback).addOnConnectionFailedListener(connFailCallback).build(); connThread = new GoogleApiConnectThread(mApiClient); disconnectThread = new GoogleApiDisConnectThread(mApiClient); receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String result = intent.getStringExtra("MESSAGE"); } }; |
Le prime righe di codice non hanno bisogno di spiegazioni.
Nella riga:
1 |
mApiClient = new GoogleApiClient.Builder(this).addApi(Wearable.API).addConnectionCallbacks(connCallback).addOnConnectionFailedListener(connFailCallback).build(); |
dichiariamo ed inizializiamo la variabile GoogleApiClient mApiClient dichiarando di voler usurfruire delle Api Wearable.API e attacchiamo due callbacks, uno per tenere sotto controllo la connessione, l’altro per ricereve eventuali errori di connession per provare poi a risolverli.
Ma perchè vediamo due thread e a cosa serve il BroadcastRecaeiver: usiamo due Thread perchè la connessione e la disconessione con il Google Play Services possono impiegare del tempo e bloccano l’interfaccia rendendola poco “responsive” per cui è consigliato avviarle da altri Thread.
Il BroadcastReceiver è invece usato per ricevere i dati dal Service in background che riceverà per noi i messaggi dal Wearable. Ma di questo parlerò dopo.
Sistemiamo intanto i riferimenti alle varie variabili: connCallback, connFailCallback.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private GoogleApiClient.ConnectionCallbacks connCallback = new GoogleApiClient.ConnectionCallbacks() { @Override public void onConnected(Bundle bundle) { state = CONNECTION_STATE.CONNECTED; Wearable.NodeApi.addListener(mApiClient, mNodeListener); if (connThread != null) { try { connThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void onConnectionSuspended(int i) { state = CONNECTION_STATE.SUSPENDED; } }; |
Ho usato un enum CONNECTION_STATE { CONNECTED, SUSPENDED, LOST} per tenere traccia sullo stato della connessione ma si può usare tranquillamente un boolean settato a true se viene chiamato onConnected(..) oppure settato a false in onConnectionSuspended(…) .
Non appena ci siamo connessi, dopo che onConnected viene chiamato dal sistema, possiamo finalmente far terminare il nostro thread usato per la connessione con join(). Solo se siamo connessi possiamo finalmente registrare un nodeListener, un altro callback che si attiverà non appena ci conneteremo ad un Node. Ma cosaè un Node? In Android wear, un Node è un’istanza di qualsiasi devices connesso tramite le Wearable API e quindi in questo caso un Node può esssere sia uno smarwatch Android Wear che uno smartphone.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private GoogleApiClient.OnConnectionFailedListener connFailCallback = new GoogleApiClient.OnConnectionFailedListener() { @Override public void onConnectionFailed(ConnectionResult connectionResult) { if (mResolvingError) { return; } else if (connectionResult.hasResolution()) { try { mResolvingError = true; connectionResult.startResolutionForResult(MainActivity.this, REQUEST_RESOLVING_ERROR); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); if (connThread != null) { try { connThread.join(); connThread = new GoogleApiConnectThread(mApiClient); connThread.start(); } catch (InterruptedException e1) { e1.printStackTrace(); } } } } else { showErrorDialog(connectionResult.getErrorCode()); mResolvingError = true; } } }; |
Se la connessione non dovesse avvenire, per varie ragioni come ad esempio il bluetooth di uno dei devices è spento, allora il sistema proverà a risolvere il problema mostrando all’utente l’appropriata azione da intrapprendere per risolvere il problema.
1 |
connectionResult.startResolutionForResult(MainActivity.this, REQUEST_RESOLVING_ERROR); |
Se il problema risulta irrisolto, allora forzeremo l’app a tentare di nuovo a connettersi, latrimenti se nessuna soluzione è disponibile, mostreremo all’utente un ErrodDialofFragment contenente il codice relativo all’errore.
Il codice per l’ ErrorDialogFragment è:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * @param errorCode the error code launched by the OnConnectionFailCallback */ private void showErrorDialog(int errorCode) { Dialog errodDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, this, REQUEST_RESOLVING_ERROR); ErrorDialogFragment dialogFragment = ErrorDialogFragment.newInstance(errodDialog, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { mResolvingError = false; } }); dialogFragment.show(getFragmentManager(), ERROR_FRAGMENT_TAG); } |
e l’ ” onActivityResult” necesario per la funzion “startResolutionForResult” è:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_RESOLVING_ERROR) { mResolvingError = false; if (resultCode == RESULT_OK) { if (!mApiClient.isConnecting() && !mApiClient.isConnected()) { if (connThread != null) { try { connThread.join(); connThread = new GoogleApiConnectThread(mApiClient); connThread.start(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } |
Coonection Thread
I thread per connessione al Google Play Services inizializzati nell’ onCreate della MainActivity sono cosi implementati:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private class GoogleApiConnectThread extends Thread { private GoogleApiClient mApiClient; public GoogleApiConnectThread(GoogleApiClient client) { mApiClient = client; } @Override public void run() { super.run(); mApiClient.connect(); } } |
La connessione viene avviata nell’ onStart(..) dell’Activity.
1 2 3 4 5 6 7 8 |
@Override protected void onStart() { super.onStart(); if (!mResolvingError) { connThread.start(); } } |
Il controllo sulla variabile mResolvingError avviene per evitare di riconnetterci in caso il sistema stia gia gestendo un precedente failConnection registrato sul GooglePlayApiClient.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private class GoogleApiDisConnectThread extends Thread { private GoogleApiClient mApiClient; public GoogleApiDisConnectThread(GoogleApiClient client) { mApiClient = client; } @Override public void run() { super.run(); mApiClient.disconnect(); } } |
Viene avviato nell’ onStop dell’Activity assieme ad altri controlli come la rimozionde di Listeners (nodeListener and BoradcastReceiver).
1 2 3 4 5 6 7 8 9 10 |
@Override protected void onStop() { if (!mResolvingError) { Wearable.NodeApi.removeListener(mApiClient, mNodeListener); disconnectThread.start(); } LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver); super.onStop(); } |
BroadcastReceiver
Veniamo ora al BroadcastReceiver inizializzato nell’ onCreate(..) dell’Activity.
Usiamo questo BroadcastReceiver per intercettare tutti i messaggi che riceveremo dallo smartwatch o dallo smartphone. Il BroadcastReceiver va registrato nell’ onResume dell’Activity.
1 2 3 4 5 |
@Override protected void onResume() { super.onResume(); LocalBroadcastManager.getInstance(this).registerReceiver(receiver, new IntentFilter("REQUEST_PROCESSED")); } |
e rilasciato nell’onStop().
Mandare un Messaggio?
Per mandare un messaggio dallo smartwatch android wear al device android tramite le Message Api basta chiamare questo metodo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private void sendMessage(final String path, final String text) { if (state == CONNECTION_STATE.CONNECTED) { new Thread(new Runnable() { @Override public void run() { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mApiClient).await(); for (Node node : nodes.getNodes()) { Wearable.MessageApi.sendMessage(mApiClient, node.getId(), path, text.getBytes()).await(); } } }).start(); } } |
Usiamo un thread perchè l’operazione può bloccare la ui e richiedere piu tempo del dovuto, inoltre solo se siamo connessi avrà senso mandare il messaggio.
Al Wearable.MessageApi.sendMessage dobbiamo passare innanzitutto l’instanza ddel nostro GoogleApiClient, poi l‘id del Node a cui vogliamo mandare il messaggio ( Al momento ogni android device può connettersi comunicare con un solo smartwatch alla volta nodes.getNodes() ritorna un solo node), il path che non è altro che una stringa identificativa che deve essere univoca e identica sia per chi manda il messaggio che per chi lo riceve. Infine l’informazione che vogliamo mandare: text.getBytes(). Abbiamo dovuto convertire la string in bytes perchè possiamo inviare solo array di bytes byte[]. Ma poi convertiremo i byte[] nuovamente nella Stringa di origine.
WearableListenerService
Questo service è quello che gestirà i dati in entrata e lo farà in background per noi. Verrà gestito a seconda del bisogno dal systema per cui non dovremo preoccuparci. Per usurfruire del service basterà implmentare un nostro service che lo estenda. DataListenerService è il mio service.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class DataListenerService extends WearableListenerService { private static final String IN_DATA_PATH= "/in/data"; @Override public void onMessageReceived(MessageEvent messageEvent) { if (messageEvent.getPath().equalsIgnoreCase(IN_DATA_PATH)) { String result = new String(messageEvent.getData(), Charset.forName("UTF-8")); sendResult(result); } super.onMessageReceived(messageEvent); } public void sendResult(String message) { Intent intent = new Intent("REQUEST_PROCESSED"); if (message != null) { intent.putExtra("MESSAGE", message); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } } } |
Il nostro path in questo caso è la stringa “/in/data”,. Non appena riceviamo il messaggio lo mandiamo in broadcast locale con il LocalBroadcastManager. Qualsiasi BroadcastReceiver registarto per l’intent filter “REQUEST_PROCESSED”) ricereà il mesaggio.
Il Service deve essere dichairato nel manifest all’interno del tag <application ..> </application>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <service android:name=".DataListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter> </service> . . . </application> |
Conclusione
I codici sopra sono validi sia per l’applicazione su smartwatch che su smartphone. Il codice completo lo puoi trovare sul mio profilo GitHub al seguente link: