Dopo aver esplorato con calma tutte le funzionalitá ed i possibili usi della prima applicazione per Android ;), mi sono cimentato nella scrittura di un altro applicativo di palese inutilitá, ma che, comunque esplora un po’ pi&ùacute; a fondo le potenzialitá del nostro androide. L’applicazione in questione utilizza il primo dispositivo di locazione disponibile per recuperare i dati di posizionamento attuali del dispositivo su cui Android é in esecuzione ed utilizza tali dati per recuperare un’immagine da panoramio.
L’applicazione in esecuzione.
(fare click sull’immagine per vederla a dimensioni reali)
Come per l’esempio precedente, naturalmente, anche in questo caso avremmo bisogno dell’SDK di Android prelevabile direttamente dalla pagina principale del progetto, dell’IDE Eclipse ed il plugin per Eclipse, installabile seguendo le apposite istruzioni. Per chi si fosse perso la puntata precedente consiglio un ripassino leggendosi il post precedente oppure consultando gli esempi sul sito del progetto. Una breve nota: essendo questa volta il codice sorgente un po’ pi&ùacute; complesso della “puntata precedente” l’ho provvisto di commenti che dovrebbero semplificarne la comprensione; chi segue il tutorial é tenuto a leggere sia la parte di articolo sia tali commenti. Bene, se siamo pronti possiamo cominciare…
Widgets & Layout
Dopo aver creato un nuovo progetto, che nel mio caso ho chiamato “Second Android Experiment” (a volte mi spavento da solo per la mia sfrenata fantasia…), come descritto nel tutorial precedente, modifichiamo il file main.xml (lo trovate in res/layout) per disporre i widgets che andremo ad usare in un layout verticale:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:gravity=”center_horizontal”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Cosa possiamo andare a vedere quí vicino?”
/>
<ListView id=”@+id/lstInfo”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
/>
<WebView id=”@+id/webPanoramio”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
/>
</LinearLayout>
I widgets usati sono:
- Una TextView: che utilizziamo per mostrare una piccola etichetta.
- Una ListView: é il corrispettivo di una listbox di swing che utilizzeremo per aggiungere le informazioni riguardo la locazione. Alla ListView viene assegnato un identificativo “lstInfo” per poterla poi utilizzare nel codice sorgente. Avremmo potuto anche utilizzare una normale TextView, ma ci si stanca di usare sempre gli sessi componenti no?

- Una WebView: questo widget fornisce un contenitore base per la navigazione sul web. Fornisce le funzioni base di un browser (fra cui anche l’handling di script e la gestione della cronologia). Nel nostro caso lo utilizzeremo per visualizzare l’immagine da Panoramio conoscendone l’URL. A questo controllo associamo l’ID “webPanoramio”.
Naturalmente se non stessimo semplicemente “giocando” il layout andrebbe studiato in modo pi&ùacute; accurato, ma per i nostri scopi puó giá andare bene cosí. Direi quindi che possiamo passare alla parte divertente.
Sorgente Java
Apriamo il file sorgente (nel mio caso SecondAndroidExperimentActivity.java nella cartella dei sorgenti) ed iniziamo con gli import delle classi che andremo ad utilizzare:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.webkit.WebView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
Fin quí direi che non c’é un granché da dire. Ora dichiariamo una nuova classeSecondAndroidExperimentActivity che estenda un’Activity, ridefinisca il metodoOnCreate ed assegniamole il layout che abbiamo definito precedentemente (in realtá tutto ció é fatto automaticamente dal plugin Android per Eclipse):
public class SecondAndroidExperimentActivity extends Activity {
/** Invocato alla creazione dell’Activity. */
public void onCreate(Bundle icicle) {
super.onCreate(icicle);// Imposta il layout dell’activity
setContentView(R.layout.main);
Bene, ora dobbiamo capire come possiamo interrogare il primo dispositivo di locazione disponibile nel device. Per fare questo innanzitutto dobbiamo procurarci un handle al gestore dei servizi di locazione del sistema. Android, infatti, mette a disposizione diversi servizi di sestema con cui é possibile interagire tramite i rispettivi gestori. tali servizi sono:
- Window Service: Gestore della finestra top-level su cui poter collocare nuovi widgets. Il gestore restituito é di tipo ViewManager.
- Inflate Service: Utile per gestire le risorse relative al layout nel Contex attuale. Il gestore restituito é di tipo ViewInflate.
- Power Service: Servizio di gestione energetica. Il gestore restituito é di tipoPowerManager.
- Alarm Service: consente di gestire la pianificazione delle operazioni (ad esempio eseguire una applicazione ad un tempo prefissato). Il gestore restituito é di tipo AlarmManager.
- Notification Service: Il servizio che si occupa di informare l’utente sulle operazioni in background. Il gestore restituito é di tipo NotificationManager.
- Keyguard Service: Servizio per il blocco/sblocco della tastiera. Il gestore restituito é di tipo KeyguardManager.
- Location Service: Servizio di gestione della locazione. Il gestore restituito é di tipo LocationManager. (é quello che andremo ad utilizzare noi!)
Per accedere ad un servizio di sistema utilizziamo il metodo getSystemServicefornito dalla classe Context, superclasse di Activity. Ottenuto l’handler al servizio di sistema lo useremo per farci restituire la lista dei Location Providers disponibili. E’ utile sapere che i location providers sono dotati di diverse caratteristiche detteCriteria che li differenziano l’uno dall’altro in base, ad esempio, all’hardware specifico richiesto (come nel caso dei GPS), o di accedere a servizi di locazione a pagamento o ancora in base alle risorse richieste (come, ad esempio, il consumo di carica della batteria).
// Ottinene l’handle al Location Service del sistema
LocationManager location_manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);// Ottiene la lista dei fornitori di servizi di locazione disponibili
Listproviders = location_manager.getProviders(); // Se non ci sono servizi disponibili termina l’esecuzione
if (providers.size() == 0)
return;
Ora che abbiamo ottenuto la lista dei location provider, bisogna che ne selezioniamo uno (nel caso l’applicazione sia poi eseguita nell’emulatore, non é che abbiamo poi tutta questa gran scelta, é disponibile un solo location provider basato su hardware GPS “fittizio”). Per comoditá ci limiteremo a scorrere tutta la lista leggendo lo stato di ciascun provider tramite il metodo getProviderStatus; non appena ne troviamo uno disponibile (LocationProvide.AVAILABLE) interrompiamo la ricerca. Naturalmente nel caso non ne trovassimo nessuno disponibile, termineremmo l’esecuzione.
LocationProvider used_provider = null;
// Cerca il primo provider di locazione dispobilile
// (in emulazione é rilevato solamente il servizio GPS)
for (LocationProvider provider : providers) {
if (location_manager.getProviderStatus(provider.getName()) == LocationProvider.AVAILABLE) {
used_provider = provider;
break;
}
}// Se non c’é neppure un provider disponibile termina l’esecuzione
if (used_provider == null)
return;
Ok, ora che abbiamo il nostro provider ci dobbiamo fare restituire le coordinate attuali.
// Ottiene un oggetto che rappresenta la locazione attuale del
// sipositivo
Location loc = location_manager.getCurrentLocation(used_provider
.getName());
Una volta ottenute possiamo cominciare a visualizzare, se non altro, qualche informazione come il nome del provider utilizzato e le coordinate attuali. Tali informazioni vogliamo inserirle nel controllo ListView che abbiamo inserito nel layout. Per farlo dobbiamo assegnare un ListAdapter (od un adapter da esso derivato) alla lista; un ListAdapter non é altro che una classe che agisce da ponte fra la ListView ed i dati in essa rappresentati. Esistono svariati derivati diListAdapter come, ad esempio: ActivityAdapter, CursorAdapter (uno dei pi&ùacute; utili), BaseAdapter (da derivare per ottenere un adapter “personalizzato”), etc. Nel nostro caso utilizzeremo un ArrayAdapter che ci consente di associare alla ListView una collezione arbitraria di oggetti (nel nostro caso stringhe). Nella creazione del ArrayAdaper utilizzeremo una delle risorse di sistema messe a disposizione da Android, cioé android.R.layout.simple_list_item_1che definisce un semplice elemento di testo per la ListView.
// Aggiunge alcune informazioni sulla locazione in una list view
String[] coords = new String[] {
“Location provider: ” + used_provider.getName(),
“Latitudine: ” + Double.toString(loc.getLatitude()),
“Longitudine: ” + Double.toString(loc.getLongitude()) };ListView lstInfo = (ListView) findViewById(R.id.lstInfo);
lstInfo.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, coords));
Ora, giusto perché ci piace complicarci un po la vita utilizzeremo l’api di Panoramio per recuperare una immagine con geotag il pi&ùacute; possibile vicino alla posizione attuale del nostro dispositivo mobile. Panoramio, infatti, mette a disposizione una semplicissima API basata su request/response per consentire la ricerca di panorami. Tale sistema si basa su una richiesta tipo:
http://www.panoramio.com/map/get_panoramas.php?order=popularity&set=public&from=0&to=20&minx=-180&miny=-90&maxx=180&maxy=90&size=medium
dove:
- order: definisce l’ordinamento dei dati in risposta e puó assumere i valoripopularity (popolaritá) o upload_date (data di caricamento).
- set: definisce l’insieme in cui sará eseguita la ricerca. Accetta come valoripublic (foto popolari) o full (tutte le foto).
- size: dimensione dell’immagine restituita. Accetta come valori: original, medium (valore predefinito), small, thumbnail, square, mini_square.
- minx, miny, maxx, maxy: definiscono l’area da cui prelevare le foto (longitudine e latitudine rispettivamente minima e massima).
- from, to: numero di foto restituite. Il valore 0 (zero) rappresenta l’ultima foto caricata su Panoramio. Il limite massimo di foto che si possono richiedere é 100.
In base alla richiesta la risposta sará un insieme di dati formattati usando JSON, del tipo:
{
“count”: 773840,”photos”: [
{
"photo_id": 532693,
"photo_title": "Wheatfield in afternoon light",
"photo_url": "http://www.panoramio.com/photo/532693",
"photo_file_url": "http://static2.bareka.com/photos/medium/532693.jpg",
"longitude": 11.280727,
"latitude": 59.643198,
"width": 500,
"height": 333,
"upload_date": "22 January 2007",
"owner_id": 39160,
"owner_name": "Snemann",
"owner_url": "http://www.panoramio.com/user/39160",
},
{
"photo_id": 505229,
"photo_title": "Etangs prés de Dijon",
"photo_url": "http://www.panoramio.com/photo/505229",
"photo_file_url": "http://static2.bareka.com/photos/medium/505229.jpg",
"longitude": 5.168552,
"latitude": 47.312642,
"width": 350,
"height": 500,
"upload_date": "20 January 2007",
"owner_id": 78506,
"owner_name": "Philippe Stoop",
"owner_url": "http://www.panoramio.com/user/78506"
}, ...
]
}
Il capo che a noi interessa é “photo_file_url”. Innanzitutto generiamo l’URL di richiesta definendo un area quadrata di 2 gradi di lato con al centro le coordinate restituiteci dal LocationProvider; inoltre vogliamo che Panoramio ci restituisca una sola immagine (from=0,to=1) ridimentionata come thumbnail (size=thumbnail), inviamo una richiesta a Panoramio utilizzando tale URL e, quindi, leggiamo la risposta (chiunque abbai nozioni base della API Java per la gestione di URL troverá il tutto molto famigliare):
// Invia una richiesta a panoramio per ottenere una sola immagine
// compresa in un area
// quadrata di 2×2 gradi (di latitudine e longitudine) con al centro le
// coordinate
// attuali
String minx = Double.toString(loc.getLatitude() – 1.0);
String miny = Double.toString(loc.getLongitude() – 1.0);
String maxx = Double.toString(loc.getLatitude() + 1.0);
String maxy = Double.toString(loc.getLongitude() + 1.0);try {
// crea un nuovo oggetto URL
URL panoramio_request_url = new URL(
“http://www.panoramio.com/map/get_panoramas.php?order=popularity&set=public&from=0&to=1&minx=”
+ minx
+ “&miny=”
+ miny
+ “&maxx=”
+ maxx
+ “&maxy=” + maxy + “&size=thumbnail”);// Invia una richiesta a tale URL aprendo una connessione
URLConnection panoramio_connection = panoramio_request_url.openConnection();// Crea un reader per leggere i dati di risposta alla richiesta precedente
BufferedReader reader = new BufferedReader(new InputStreamReader(panoramio_connection.getInputStream()));String readed = null;
String read_line = null;// Legge la risposta di panoramio
while ((read_line = reader.readLine()) != null) {
readed += read_line;
}
Ora dobbiamo cercare la linea di risposta contenente l’URL dell’immagine (se Panoramio ne ha restituita una) ed estrarre tale indirizzo per poi caricarlo tramite il metodo loadURL nella nostra WebView. In alternativa avremmo dovuto ugualmente estrarre l’url, per poi inviare una richiesta di download dell’immagine ed, infine, caricarla in una ImageView (magari esiste anche una soluzione pi&ùacute; comoda al problema, ma dovete essere clementi, sto ancora esplorando Android
):
// Estrae l’indirizzo URL dell’immagine dalla risposta e la
// visualizza tramite una WebView
if (readed != null) {
int photo_url_start = readed.indexOf(“\”photo_file_url\”: \”");
int photo_url_end = readed.indexOf(“\”,”, photo_url_start);
String photo_url = readed.substring(photo_url_start + 19, photo_url_end);// Se é stata restituita almeno una foto viene visualizzata nella WebView
if (photo_url.length() > 0) {
WebView webViewPanoramio = (WebView) findViewById(R.id.webPanoramio);
webViewPanoramio.loadUrl(photo_url);
}
}} catch (MalformedURLException e) {
showAlert(“Errore (URL Malformato)”, e.getMessage(), “Chiudi”,
false);
} catch (IOException e) {
showAlert(“Errore (Errore di I/O)”, e.getMessage(), “Chiudi”, false);
}
}
}
Eseguiamo il tutto
Ora, come giá visto nel tutorial precedente, creaimo un profilo di esecuzione per la nostra applicazione ed avviamola.
A questo punto dovremmo vedere una schermata simile a quella all’inizio del tutorial solo che le coordinate saranno lat:0.0 long:0.0. Ogni volta al primo avvio dell’applicazione dopo l’avvio dell’emulatore le coordinate restituite sono queste, ora per ottenre quelle di figura é necessario eseguire di nuovo l’applicazione senza peró chiudere la finestra dell’emultatore. Se qualcuno stesse pensando di chiedermi il perché vi preannuncio giá che non ne ho neanche la pi&ùacute; pallida idea 
Ad ogni moto se avete fatto tutto in modo corretto dovreste comunque vedere una thumbnail della foto pi&ùacute; vicina alle coordinate del dispositivo nel controllo WebView.
Come nel primo tutorial anche in questo caso l’inutilitá del’applicazione ottenuta é evidente, peró io mi sono spinto oltre, rendendo l’applicazione un servizio in background che ogni tot. secondi scarica il panorama pi&ùacute; vicino al device e lo imposta come sfondo del desktop. Sfortunatamente finché non disporró di un vero device equipaggiato con android non avró modo di provare il reale funzionamento, ma, se qualcuno fosse interessato, posso dare qualche “dritta” su come ottenre lo stesso risultato (o, se le richieste sono molte creare un terzo tutorial).
Riferimenti
- Sono disponibili per il download i Sorgenti Second Android Experiment (File progetto per Eclipse)
I riferimenti per i materiali necessari per la realizzazione del progetto sono:
- L’SDK, il plugin per Eclipse e tutta al documentazione su Androi sono reperibili nella pagina principale del progetto
- L’ambiente di sviluppo Eclipse é disponibile sul sito del progetto
- Informazioni sulle API di Panoramio sono disponibili nell’apposita sezione del sito Panoramio.com
[¹] I’ve seen things you people wouldn’t believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time, like tears in rain. Time to die.
(Ho visto cose che voi persone non non potreste immaginarvi. Navi da combattimento in fiamme al largo dei bastioni di Orione… e ho visto i raggi C balenare nel buio vicino alle porte di Tannhäuser. E tutti quei momenti andranno perduti nel tempo come lacrime nella pioggia. é tempo di morire.)Balde Runner, 1982


