+ All Categories
Home > Documents > © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi [email protected].

© 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi [email protected].

Date post: 01-May-2015
Category:
Upload: capricia-carboni
View: 220 times
Download: 0 times
Share this document with a friend
51
© 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi [email protected]
Transcript
Page 1: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

© 2012 - CEFRIEL

Componenti di Android

Docente: Gabriele [email protected]

Page 2: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

© 2012 - CEFRIEL

The present original document was produced by CEFRIEL and the Teacher for the benefit and internal use of this course, and nobody else may claim any right or paternity on it. No right to use the document for any purpose other than the Intended purpose and no right to distribute, disclose, release, furnish or disseminate it or a part of it in any way or form to anyone without the prior express written consent of CEFRIEL and the Teacher.© copyright Cefriel and the Teacher-Milan-Italy-23/06/2008. All rights reserved in accordance with rule of law and international agreements.

Page 3: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

© 2012 - CEFRIEL

SommarioSommario

SLIDE CONTENUTO

Attività Cosa sono, ciclo di vita

Servizi Come si creano, un esempio concreto

Intent Cosa sono, una visione dall’alto, esempi

Permessi Come definirli, un esempio concreto

ContentProvider Come accedere a dati esterni

Broadcast receiver Ricevere richieste broadcast dalle altre app

Page 4: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Attività: ma cosa sono?

Sono componenti Android:– quindi sono gestite (più di quanto sembri);– vengono create e distrutte dal framework;– possono essere fermate e riavviate;– se serve ram (o altri casi) possono essere distrutte;– l’utente non nota mai nulla (se l’app è scritta bene).

Sono porzioni di interfaccia grafica:– rappresentano strumenti di interazione con l’utente;– le app non sono obbligate ad averne solo una;– normalmente ogni attività è coesa e disaccoppiata:

•low copuling, high coesion (GRASP);– può offrire i propri servigi anche ad altre app. (?).

© 2012 - CEFRIEL

Page 5: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una vita piena di imprevisti

Scenario di esempio con 2 Activity:– sto giocando a sudoku, sono preso e concentrato…– …arriva una telefonata a cui rispondere:

•deve avviarsi un’attività di gestione della chiamata con interfaccia (rispondi, chiudi);

• l’attività Sudoku viene messa in pausa:– chiamate callback relative;

– la RAM è insufficiente per entrambe le attività:•Android decide di eliminare Sudoku:

– chiamate callback relative;– l’applicazione Sudoku deve persistere il proprio stato;

– la telefonata può avere atto (con la RAM libera);– finisce la telefonata, si chiude l’attività di gestione;

•Android avvia Sudoku passandogli le info;– chiamate callback relative;– l’applicazione dalle info persistite deve ripristinare il proprio

stato; L’utente è ignaro di tutto e si illude di

parallelismo.© 2012 - CEFRIEL

Page 6: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Ciclo di vita delle Activity

© 2012 - CEFRIEL

Nasce:– Creata– Avviata– Ripristinato lo stato

Vive:– L’utente

interagisce, viene mantenuta;

Dorme:– Pause se tocca a

qualcun altro;– Stop se manca ram;

– Viene mantenuta traccia della sua esistenza;

Muore:– Viene fermata e

uccisa, eliminate le tracce di esistenza.

Page 7: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Salvare le proprie info Le Activity vengono distrutte a fronte di:

– altre Activity in foreground che richiedono RAM;– cambiamenti di configurazione (es orientazione).

FractionCalc è a posto da questo punto di vista?– No, ha stato interno non mantenuto: isInitial.

© 2012 - CEFRIEL

Restore stato per una pausa

Restore per una

ricreazione

Page 8: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Rivediamo la calcolatrice sotto nuova luce Definire una attività:

– la classe FractionCalc estende Activity:•altre classi estendono Activity per noi:

– AliasActivity Alias di un’altra attività, la avvia e poi termina.– LouncherActivity GUI di scelta tra attività per eseguirne una.– ListActivity Lista di item, con un layout per ogni riga.– ExpandableListActivity Lista espandibile, come sopra.– TabActivity Attività basata su Tab (pagine con linguette).

– ascolta degli eventi tra cui onCreate per il setup;– l’AndroidManifest.xml e strings.xml vengono

aggiornati:

© 2012 - CEFRIEL

<activity android:name="FractionCalc" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter></activity>

<resources> <string name="app_name">FractionCalc</string></resources>

Page 9: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Rivediamo la calcolatrice sotto nuova luce

Dare una faccia nuova all’attività:– ogni attività si mostra attraverso un albero di view;– questo può essere descritto in XML;– insufflare una vista significa inserirla creandola da XML;– ad ogni View nel layout può essere associato un id:– findViewById ci permette di recuperare le istanze dei widget;– ogni widget implementa Observable per l’ascolto degli eventi;– ogni evento viene gestito nel main thread, quello della UI;

• Nota: se abbiamo lavori lunghi, NON devono essere eseguiti nel main thread (vedremo dove).

Gestire il salvataggio di stato:– Bugfixiamo FractionCalc salvandone lo stato:

• in onSaveInstanceState salviamo:– @Override protected void onSaveInstanceState(Bundle outState) {– super.onSaveInstanceState(outState);– outState.putBoolean("isInitial", isInitial); }

• in onCreate utiliziamo i dati salvati:– if (savedInstanceState!=null)– isInitial = savedInstanceState.getBoolean("isInitial",

isInitial);

© 2012 - CEFRIEL

Page 10: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Rivediamo la calcolatrice sotto nuova luce

Più attività nella nostra applicazione:– aggiungiamo una history delle espressioni;– ogni espressione valutata viene aggiunta alla

history;– una attività apposita ci permette di visualizzarle;– un menu ci fa accedere a questa nuova attività;– l’attività può tornare con o senza espressione;– se una espressione è stata scelta va inserita.

Come muoversi tra attività:– gli Intent rappresentano «gli URL» di Android:

•definiscono cosa interessa raggiungere;•cosa interessa eseguire (verbo);•permettono il passaggio di informazioni.

© 2012 - CEFRIEL

Page 11: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Creare l’history durante il normale utilizzo

© 2012 - CEFRIEL

private LinkedList<String> history = new LinkedList<String>();

// Computing:String exprStr = new StringBuilder(expr).toString();Fraction res = Calculator.compute(exprStr); // Updating the history:history.add(exprStr);

@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("isInitial", isInitial); outState.putSerializable("history", history); }

if (savedInstanceState!=null) { isInitial = savedInstanceState.getBoolean("isInitial", isInitial); history = (LinkedList)savedInstanceState.getSerializable("history"); if (history==null) history = new LinkedList<String>();}

Page 12: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Creare un’attività «a lista» per l’history

© 2012 - CEFRIEL

public class HistoryList extends ListActivity { @Override public void onCreate(Bundle icicle) { … } … }

<activity android:name="HistoryList“ android:label="History list"></activity>

Creo la classe e adatto il manifest

@Override public void onCreate(Bundle icicle) { super.onCreate(icicle); List<String> history = (List<String>)getIntent() .getSerializableExtra(FractionCalc.HISTORY_EXTRA); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, history)); }

Ottengo l’history

Setto l’adattatore

@Override protected void onListItemClick(ListView l, View v, int position, long id) {

setResult(Activity.RESULT_OK, new Intent().putExtra(FractionCalc.POSITION_EXTRA, position)); finish(); }

Page 13: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Creare i menu per la navigazione (da codice) Vedremo come creare menu da XML; In caso di creazione da codice:

– vengo avvertito e aggiungo le mie voci:

© 2012 - CEFRIEL

@Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, MENU_HISTORY, Menu.NONE, "History") .setIcon(android.R.drawable.ic_menu_gallery); return super.onCreateOptionsMenu(menu); }

@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_HISTORY: Intent i = new Intent(this, HistoryList.class); i.putExtra(HISTORY_EXTRA, history); startActivityForResult(i, HISTORY_POSITION); return true; } return super.onOptionsItemSelected(item); }

Se una voce viene selezionata…– …uso un Intent per aprire la lista di history items.

Page 14: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

public void onCreate(Bundle savedInstanceState) { … prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs.registerOnSharedPreferenceChangeListener( new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if ("theme".equals(key)) applyTheme(); } }); applyTheme(); …

Gestire le preferenze utente

L’attività di gestione delle preferenze:– esiste già una classe per definirla: – la descrizione delle preferenze viene indicata in XML;– dobbiamo aggiungere però una voce di menu;– gestiamo (in maniera «casereccia») i temi.

© 2012 - CEFRIEL

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <ListPreference android:key="theme" android:title="UI Theme" android:summary="Choose the theme for your calc" android:entries="@array/themes" android:entryValues="@array/theme_values" android:dialogTitle="Choose the preferred theme"/></PreferenceScreen>

res/xml/preferences.xml

<resources> <string-array name="themes"> <item>Dark UI</item> <item>Clean light</item> <item>Coloured</item> </string-array> <string-array name="theme_values"> <item>dark</item> <item>light</item> <item>coloured</item> </string-array></resources>

res/values/arrays.xml

case MENU_PREFERENCES: startActivity(new Intent(this, Preferences.class)); return true; …

Page 15: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Quali componenti abbiamo assemblato?

© 2012 - CEFRIEL

FractionCalc Intent HistoryList

Dati(extra)

Menu

Intent

Risultati(extra) Adapter

View da

XMLView da???

Gli Intent sono degli URI con più informazioni e capacità: possono trasportare dati strutturato, azione, valore di ritorno, modalità di accesso, …

Page 16: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Attività, task e back/stack Arrivati all’history possiamo «tornare indietro»:

– la nostra app ha un solo task (potremmo crearne altri);– ogni task possiede uno stack di (stati di) attività:

•se la nostra app passa in background:– invocati i metodi relativi ai passaggi di stato onPause

onStop;– viene mantenuta traccia dello stato dell’intero back-stack;

•se viene premuto il tasto «back»:– distrutta l’attività corrente: onPause onStop onDestroy;– viene ripristinata la precedente: onResume

© 2012 - CEFRIEL

Back-stack del Task

FractionCalc

Back-stack del Task

FractionCalc

HistoryList

Back-stack del Task

FractionCalc

Intent verso

HistoryList

Pulsante back oppure

finish()

Page 17: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Gli intent

Rappresentano gli URL in Android:– hanno un destinatario a cui si vuole accedere:

• definito esplicitamente (classe o nome di componente);

• definito tramite un URI definito nei filtri:– le app dichiarano di gestire richieste per determinati URI;

– hanno un’azione richiesta (come i verbi HTTP):• ACTION_MAIN inizia un’attività come task• ACTION_CALL inizia una chiamata …

– definiscono una categoria per il destinatario dell’Intent:• CATEGORY_LAUNCHER attività iniziale• CATEGORY_PREFERENCE pannello preferenze …

– trasportano dei dati (negli Intent i «dati» sono l’URI):• ad esempio associato ad ACTION_CALL:

– tel:+39340123456

• avvia una telefonata verso il numero indicato• il tipo MIME può essere indicato

– trasportano degli extra, ovvero dati serializzabili custom;– per indicare ad Android come gestire l’intent ci sono alcuni

flag.© 2012 - CEFRIEL

Page 18: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Gli intent: accedere ai contatti

Scopo dell’app di esempio:– creazione di una lista di chiamate da effettuare in

sequenza scegliendo i destinatari dalla rubrica;– la sequenza dovrà essere mostrata in una lista

comprendente varie informazioni e dei pulsanti;– i contatti dovranno venire scelti dal tool della

rubrica;– le chiamate dovranno essere effettuate in ordine.

Attività principale dell’app:– una ListActivity con la lista di chiamate schedulate;

•vogliamo gestirci noi il layout di riga;– voce «Add» dal menu per accedere alla rubrica;– cominciamo con queste funzionalità!

© 2012 - CEFRIEL

Page 19: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Accedere ai contatti

Core features dell’applicazione sono:1. costruire una lista di contatti da chiamare;2. effettuare le chiamate in sequenza.

Come effettuare il pick di un contratto?– Tramite un Intent non rivolto alla nostra app!

© 2012 - CEFRIEL

@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_add: startActivityForResult( new Intent(Intent.ACTION_PICK,

ContactsContract.Contacts.CONTENT_URI), CONTACT_PICK); return true; } return super.onOptionsItemSelected(item); }

Chiamo una attività perché voglio

qualcosa

URI dato da una costante del framework

Azione di prelievo dei dati di un

contatto

Page 20: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Cosa ci regala l’attività rubrica?

Un Intent di risposta… non senza avvisarci!– gli intent-filter vengono utilizzati per identificare il

destinatario della richiesta (vedremo come);– il destinatario viene interpellato con il nostro Intent

come richiesta da cui recuperare gli argomenti;– il destinatario crea un Intent e genera una risposta;– il nostro metodo onActivityResult viene chiamato.

© 2012 - CEFRIEL

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Controllo se devo gestire un contatto: if (resultCode==RESULT_OK) { switch (requestCode) { case CONTACT_PICK: onContactPick(data); break; } } }

Page 21: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Persistenza: accedere con i cursori! Cerchiamo il nome/numero del contatto fornito:

– dobbiamo cercare nel database dei contatti;– eseguiamo una query con un ContentResolver;– possiamo navigare le «tabelle» in diversi modi:

– (quello che vediamo in questo caso sono dati aggregati).

© 2012 - CEFRIEL

// Otteniamo un risolutore di contenuti:ContentResolver cr = getContentResolver();

// Otteniamo un cursore su Phone:Cursor c = cr.query(Phone.CONTENT_URI, new String[] {Contacts._ID,

Contacts.DISPLAY_NAME, Phone.NUMBER},Contacts._ID + " = ? AND " + Phone.TYPE + " = ?",new String[] { String.valueOf(ContentUris.parseId(data.getData())),

String.valueOf(Phone.TYPE_MOBILE)}, null);if (c.moveToFirst()) {

String name = c.getString(1); String number = c.getString(2);… }

c.close(); // Chiudo:

Page 22: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Persistenza: accedere con i cursori!

I contatti sono persistenti…– …e vengono mantenuti quindi in una base dati;– DBMS SQLite, parzialmente nascosto dalle API;– per accedere ai dati dobbiamo conoscere lo schema:

© 2012 - CEFRIEL

Contact_ID

DISPLAY_NAME

DataCONTACT_ID

NUMBER

Page 23: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Persistenza: accedere con i cursori! Per accedere ai dati dobbiamo:

– procurarci un punto di accesso al DB dei contatti:• ContentResolver fornito dal Context;

– eseguire una query fornendo un URI:• ci viene fornito come risultato nell’Intent;• identifica un contatto (vediamo come è fatto);• ci viene restituito un Cursor da scorrere;

– estrarre i dati da Contact (_ID e DISPLAY_NAME);– solo se è disponibile un numero di telefono:

• eseguire una query su Data:– accedendo ai dati relativi al contatto di interesse;– estraendo il numero di telefono (il primo ci va bene).

© 2012 - CEFRIEL

Contact_ID

DISPLAY_NAMEData

CONTACT_ID

NUMBER

Intent

Cursor

ContentResolver

Cursor

Creiamo e salviamo un CallSchedule nella lista

Page 24: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Persistenza: accedere con i cursori!

Vedremo come persistere i dati della nostra app.© 2012 - CEFRIEL

ContentResolver cr = getContentResolver(); // Da qui accediamo ai dati..Cursor c = cr.query(data.getData(), null, null, null, null); // ..ottenedo un cursore!

if (c.moveToFirst()) { // Estraiamo le informazioni che ci servono: String id = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID)); String name = c.getString(c.getColumnIndex(

ContactsContract.Contacts.DISPLAY_NAME)); String number = "-"; if (Integer.parseInt(c.getString(c.getColumnIndex(

ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) { /* Qui otterremo il numero di telefono. */ } adapter.add(new CallSchedule(name, number)); // Aggiungiamo lo schedule.} c.close(); // I cursori vanno poi rilasciati.

Cursor pCur = cr.query( // Otteniamo un cursore sui dati del contatto:ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,

ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",new String[]{id}, null);

if (pCur.moveToFirst()) { // Scelgo il primo: number = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));} pCur.close();

Page 25: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Abbiamo chiesto il permesso? Cenni di sicurezza da parte dell’utente:

– caro utente.. questa app vuole fare questo e quello…– …per effettuare certe operazioni serve il consenso:

•dell’utente che deve esserne consapevole;•a tempo di installazione dell’app.

– il manifest dichiara le caratteristiche dell’app:

•versioni supportate del framework;•supporto per tipi di display;• feature richieste dall’app per funzionare:

– camera, accelerometro, touch…

•permessi per funzionalità e contenuti. Nel nostro caso:

– <uses-sdk android:minSdkVersion="10"/>– <uses-permission android:name=

"android.permission.READ_CONTACTS"/>© 2012 - CEFRIEL

Page 26: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una lista completamente custom Vogliamo mostrare la lista di schedule con:

– nome visualizzato del contatto con sotto il numero;– tasti per eliminare l’elemento o effettuare la chiamata;

Per farlo dobbiamo costruire una lista che:– non sia «standard» (ogni riga deve essere un layout);– permetta di agire attivamente sui dati con pulsanti;– sia efficiente per poter gestire tante righe:

– alcuni dispositivi hanno ram e capacità di calcolo molto limitate!

© 2012 - CEFRIEL

FastCall

ListActivityCallScheduleAdapte

r

CallScheduleHolder

In getView si preoccupa di costruire la vista di ogni riga

«insuflandola» da XML, riciclando altre righe quando

possibile, salvando per efficienza i dati nel tag della

vistaMantiene i dati/componenti per una riga e ne nasconde i dettagli di aggiornamento e

accesso (efficienza e disaccoppiamento)

Page 27: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una lista completamente custom

© 2012 - CEFRIEL

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/name" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="10pt" android:paddingLeft="4pt" android:text="Gabriele Lombardi"/> <TextView android:id="@+id/number" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="6pt" android:paddingLeft="8pt" android:text="+39 340 123456"/> </LinearLayout> <ImageButton android:id="@+id/remove" android:src="@android:drawable/ic_delete" android:contentDescription="Delete this schedule" android:layout_width="20pt" android:layout_height="20pt" android:layout_weight="0" android:layout_gravity="center"/></LinearLayout>

Layout XML relativo ad ogni singola riga:

Page 28: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una lista completamente custom

© 2012 - CEFRIEL

// Lista contenente i dati:private List<CallSchedule> schedules = new LinkedList<CallSchedule>();@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Insufliamo la nostra lista. // Usiamo il nostro adattatore: setListAdapter(adapter = new CallScheduleAdapter()); …private class CallScheduleAdapter extends ArrayAdapter<CallSchedule> { public CallScheduleAdapter() { super(FastCall.this, …, schedules); } @Override public View getView(int position, View convertView, ViewGroup parent) { // Verifico se esiste una vista da riciclare: View row = convertView; CallScheduleHolder holder; if (row==null) { row = getLayoutInflater().inflate(R.layout.contact, null); // Ne creo una: row.setTag(holder = new CallScheduleHolder(row)); } // Attacco l’holder: else holder = (CallScheduleHolder)row.getTag(); // Ottengo il modello e inserisco i dati usando l'holder: holder.populateFrom(getItem(position)); return row;} }

Page 29: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una lista completamente custom

© 2012 - CEFRIEL

private class CallScheduleHolder { TextView name, number; CallSchedule schedule; CallScheduleHolder(View row) { name = (TextView)row.findViewById(R.id.name); number = (TextView)row.findViewById(R.id.number);

// Collego un ascoltatore per ogni tasto: row.findViewById(R.id.remove).setOnClickListener(

new View.OnClickListener() { @Override public void onClick(View view) {

adapter.remove(schedule); } }); } // Incapsulo la funzionalità di refresh della UI: void populateFrom(CallSchedule sc) { name.setText(sc.getName()); number.setText(sc.getNumber()); schedule = sc; }}

row view

holder

row view

holder

row view

holder

row view

holder

Dati

Page 30: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Aggiungiamo il tasto di chiamata Widget nel layout e drawable tra le risorse:

© 2012 - CEFRIEL

<ImageButton android:id="@+id/call" android:src="@drawable/call_contact" android:contentDescription="Call this schedule" android:layout_width="20pt" android:layout_height="20pt" android:layout_weight="0" android:layout_gravity="center"/>

Ascoltatore di eventi per il tasto nell’holder:row.findViewById(R.id.call).setOnClickListener(new View.OnClickListener() {

@Override public void onClick(View view) { // Avvio l'attività che risponde all'intent corretto: startActivity(new Intent(Intent.ACTION_CALL,

Uri.parse("tel:" + schedule.getNumber()))); } });

I permessi per poter effettuare chiamate: <uses-permission android:name="android.permission.CALL_PHONE"/>

Proviamolo… problemi riscontrati?– Al termine della chiamata non torna all’attività.– Ascolteremo lo stato del telefono in un servizio.

Page 31: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Rendiamo persistenti i nostri schedule Molti modi per gestire la persistenza:

– il più corretto consiste nell’utilizzare un database;– in FastCall utilizzeremo le SharedPreferences:

– si tratta di una mappa persistente di preferenze tipizzate.

Leggiamo le preferenze shared in «onCreate»:

© 2012 - CEFRIEL

savedSchedules = getSharedPreferences("schedules", MODE_PRIVATE);for (Map.Entry<String,?> entry: savedSchedules.getAll().entrySet()) { // Ne aggiungo uno: adapter.add(new CallSchedule(entry.getKey(), entry.getValue().toString())); }

Aggiungiamo se l’utente lo richiede:

Eliminiamo quando lo decide l’utente:

adapter.add(new CallSchedule(name, number));SharedPreferences.Editor editor = savedSchedules.edit();editor.putString(name, number); editor.apply();

adapter.remove(schedule);SharedPreferences.Editor editor = savedSchedules.edit();editor.remove(schedule.getName()); editor.apply();

Page 32: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Eliminazione accidentale… preveniamola!

Quando si deve eliminare qualcosa…– …è sempre meglio chiedere all’utente;– per farlo serve una dialog… – …in Android le dialog sono asincrone!

© 2012 - CEFRIEL

// Lo facciamo in una dialog asincrona:new AlertDialog.Builder(FastCall.this) // Configuro un creatore di alert:

.setTitle("Confermi la cancellazione?").setCancelable(true)

.setPositiveButton("Si", new DialogInterface.OnClickListener() {@Override // Solo qui dentro faccio quello che devo:public void onClick(DialogInterface dialogInterface, int i) {

adapter.remove(schedule);SharedPreferences.Editor editor =

savedSchedules.edit();editor.remove(schedule.getName());editor.apply();

}}).setNegativeButton("No", null)

.show(); // Mostro la dialog (in maniera asincrona, non subito):

Page 33: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Utilizzo di esempio dell’app

© 2012 - CEFRIEL

Menu

Add

Anna Morra

Tasto call

Page 34: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Formaliziamo: i ContentProvider Scopo del gioco… accedere a dati strutturati:

– utilizzando un unico tool standard che li incapsuli;– nascondendo i dettagli relativi alla sorgente;– gestendo le problematiche di sicurezza.

Cosa ci permettono di ottenere?– Accesso trasparente a dati strutturati;– Inter Process Communication (IPC).

Attori che entrano in gioco:– ContentProvider:

•classe che si occupa di fornire i contenuti;•molte implementazioni già disponibili;•da estendere/implementare solo se si

vogliono fornire contenuti di un nuovo proprio tipo.

– ContentResolver:•strumento per accedere ai dati;•associato a un contesto (es.: Activity).

© 2012 - CEFRIEL

Page 35: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Accedere ai dati Struttura mostrata dei dati (strutturati):

– come nei DBMS… tabelle con tuple e attributi;– i dati vengono scanditi per mezzo di un cursore;– il cursore è il risultato di una query su una tabella;– la query viene eseguita tramite il ContentResolver.

Struttura di una query:– clausola FROM indica la tabella di interesse:

• per noi un URI del tipo «content://…»;– le colonne vengono scelte tramite proiezione:

• elenco di nomi, null per sceglierle tutte;– una clausola di selezione può essere definita:

• sintassi simile all’SQL con parametri JDBC;– se viene definita selezione, servono gli argomenti:

• passati per ordine in un array;– ordinamento definito come ultimo parametro.

© 2012 - CEFRIEL

Cursor cur = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // Scelgo le parole a dizionario. new String[] {UserDictionary.Words._ID, UserDictionary.Words.WORD}, UserDictionary.Words.LOCALE + " = ?", // Verifico il locale. new String[] {"it_IT"}, // Locale italiano. null // Non voglio ordinare i risultati in un particolare ordine.);

Page 36: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Struttura dell’URI I ContentProvider offrono accesso a URI:

– tutti del tipo «content://…»;– riferiti a una autorità provider indicata dopo:

•content://authority_name/…– in cui sono indicati i nomi di tabelle:

•content://authority_name/table1/…– raggruppabili nel percorso (come fossero directory):

•content://authority_name/group1/table1…– fino alla singola tupla tramite ID numerico:

•content://authority_name/table1/id1– è ammesso l’utilizzo di wildcard:

•content://authority_name/table1/* Esempi:

– content://user_dictionary/words– content://com.android.calendar/time/

1335543165348– content://com.android.contacts/contacts/1

© 2012 - CEFRIEL

Page 37: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Muoversi sui dati

Dato un cursore… possiamo scorrere i dati:– getColumnNames: nomi colonne;– getColumnName/getColumnIndex:

• converte nome di colonna in indice e viceversa;– move/moveToForst/moveToLast/moveToNext/moveToPosition:

• sposta il cursore e ci dice se ci è riuscito;– get* (getInt/getString/getDouble…):

• dati di una colonna (dato l’indice);– getExtra: meta-dati extra passati al richiedente;– respond: meta-dati (in un bundle) comunicati al cursore;– deactivate/requery/close:

• disattivazione cursore, reinizializzazione, chiusura. Ascoltiamo gli eventi del cursore:

– (un)registerContentObserver: ascolto variazioni dei dati;– (un) registerDataSetObserver: ascolto azioni sul cursore.

© 2012 - CEFRIEL

Page 38: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Operazioni CRUD

ContentResolver: oltre alla query c’è di più!– Per completare le operazioni CRUD:

•insert: inserimento di una nuova tupla;

•update: aggiornamento di una tupla;•delete: cancellazione di una tupla.

– Con le loro varianti:

•bulkInsert: inserimenti multipli;•applyBatch: batch di operazioni.

Oltre alle operazioni CRUD:– openInputStream/openOutputStream;– openAssetFileDescriptor/…– (un)registerContentObserver;– requestSynch & co.

© 2012 - CEFRIEL

Page 39: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Classi-contratto, tipi MIME, permessi

Classi-contratto:– Dobbiamo accedere a contenuti standard di Android?– Come ricordarci nomi di tabelle e campi?– Come scrivere codice robusto ai cambiamenti di nome

tra versioni differenti di Android?– Semplice: usando nomi definiti nelle classi-contratto!– Esempi: ContactsContract, CalendarContract,

SyncStateContract, VoicemailContract. Tipi MIME:

– Per ogni URI, i provider forniscono il timo MIME:• identificabile con ContentResolver.getType;

– con i tipi MIME si indentificano tipo e formato dei dati. Permessi:

– L’utente deve sempre poter decidere se permettere a un’applicazione di accedere alle risorse del telefono!

– Permessi temporanei su un URI:• l’app che restituisce l’URI può settare:

– FLAG_GRANT_READ_URI_PERMISSION– FLAG_GRANT_WRITE_URI_PERMISSION

© 2012 - CEFRIEL

Page 40: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Modalità di accesso ai dati

Tramite Intent:– non abbiamo permessi, non accediamo direttamente;– possiamo richiedere l’accesso a un’altra app.

Tramite content provider/resolver:– come abbiamo fatto per i contatti.

Tramite accesso batch:– sempre utilizzando un provider/resolver;– creando delle ContentProviderOperation:

•utilizzando la nested class Builder;• impostando li dati e direttive:

– ContentProviderOperation.newInsert(uri).withValues(values)

•con valori definiti in un oggetto ContentValues:

– ContentValues values = new ContentValues();– values.put("NAME", "Gabriele"); …

– Fornendo un array di operazioni al resolver:•getContentResolver().applyBatch(ops);

© 2012 - CEFRIEL

Page 41: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tornando alla nostra app… Notato la scomodità???

– Effettuando una chiamata si esce dall’app;– eliminiamo gli schedule di chiamate già effettuate;– l’utente potrebbe aver trovato occupato, libero, voler

chiamare nuovamente… in generale meglio chiedere;– vogliamo chiedere se eliminare lo schedule al rientro;– vogliamo farlo SOLO se l’utente è riuscito a parlare.

Come gestiamo il problema:– aggiungiamo un ascoltatore dello stato del telefono;– se si passa da idle a chiamata e poi viceversa:

•fine telefonata, mostriamo una AltertDialog.

Cosa ci serve?– Un PhoneStateListener custom (nostro);– il servizio di telefonia a cui registrarsi;– i permessi giusti.

© 2012 - CEFRIEL

Page 42: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Un ascoltatore «al telefono»

© 2012 - CEFRIEL

// Registriamo un ascoltatore di chiamate:TelephonyManager telMgr = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);telMgr.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);

onCreate

private PhoneStateListener phoneListener = new PhoneStateListener() { AtomicBoolean isCalling = new AtomicBoolean(false); public void onCallStateChanged(int state, String incomingNumber) { if (state == TelephonyManager.CALL_STATE_OFFHOOK) { isCalling.set(true); // Chiamata iniziata.. ricordiamolo: } if (state == TelephonyManager.CALL_STATE_IDLE) { // Fine chiamata? if (isCalling.compareAndSet(true, false)) { // Mostro una dialog: new AlertDialog.Builder(FastCall.this) .setTitle("Vuoi eliminare lo schedule?").setCancelable(true) .setPositiveButton("Si", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialogInterface, int i) { removeSchedule(schedule); } }) .setNegativeButton("No", null).show(); } } } };

manifest

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

Page 43: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Problemi di questo approccio

Non posso s-registrare l’ascoltatore:– per farlo è sufficiente ascoltare l’evento NONE;– se voglio smettere di ascoltare devo farlo in onStop;– se smetto di ascoltare… non vengono notificati gli

eventi di chiamata proprio quando servono; Notifiche non volute arrivano:

– registrazioni multiple ascoltano lo stesso evento;– se la chiamata è avviata da altre app...?

Notifiche possono arrivare ad attività non attiva:– impossibile mostrare la dialog.

Qualcosa dovrebbe rimanere in ascolto di eventi telefonici lavorando in background… un servizio.

© 2012 - CEFRIEL

Page 44: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Servizi: cosa sono?

Le nostre app fino ad ora:– hanno aderito all’automa a stati finiti delle Activity;– non operavano negli stati di pause e stop;– FastCall non può ascoltare il servizio di telefonia:

non in maniera corretta per lo meno, Cosa ci manca?

– La possibilità di eseguire task in background, indipendentemente dal ciclo di vita dell’Activity.

– I servizi di Android assomigliano a quelli di Windows:

•sempre attivi in background;•mai fissi in esecuzione:

– altrimenti il dispositivo eseguirebbe solo quel task.

Esempi:– servizio di download e notifica delle mail;– aggiornamento dello stato di Facebook o Twitter.© 2012 - CEFRIEL

Page 45: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tipologie di servizi Servizi locali:

– sono strettamente legati all’applicazione che li ha creati e comunicano solo con quella, mai con altre;

– sono definiti come sottoclassi di Service. Servizi remoti:

– sono accessibili a più applicazioni (oltre alla creante);– sono definiti come sottoclassi di Service;– sono descritti ai client tramite un’interfaccia Android:

•Android Interface Definition Language (AIDL).

Tutto formalmente dichiarato… nel manifest:– come per le Activity, anche i Service devono essere

dichiarati nel manifest, con eventuali opzioni a corredo. Per ora iniziamo con i servizi locali:

– Nel nostro esempio un servizio monitorerà la telefonia.© 2012 - CEFRIEL

Page 46: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Servizi locali e remoti: cicli di vita Servizi locali:

– meccanismo per eseguire operazioni in background;

– altri meccanismi che vedremo sono:• thread avviati da noi

in una attività;• estensione della

classe AsyncTask;• utilizzo di un

Handler di un thread;

– il servizio viene avviato con startService.

Servizi remoti:– permette la comunicazione

tra processi (IPC)– stessa classe da estendere,

ciclo di vita diverso;– avviato implicitamente, ci

si connette con bindService.

© 2012 - CEFRIEL

Page 47: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Servizi… un esempio di utilizzo pratico

Vogliamo ascoltare chiamate uscenti/entranti:– usiamo un servizio locale (avviato con startService);– ci inseriamo l’ascoltatore di telefonia;– ascoltiamo anche gli eventi di chiamate in uscita:

•utilizzeremo un BroadcastReceiver;– aggiorneremo l’elenco chiedendo all’utente.

© 2012 - CEFRIEL

FastCallCallNotificationServic

e

PhoneStateListener

BroadcastReceiver

Page 48: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Il servizio

© 2012 - CEFRIEL

public class CallNotificationService extends Service { private static CallNotificationService service; // Gestisco un riferimento all’istanza: public static CallNotificationService getInstance() { return service; } private TelephonyManager telMgr; private Set<String> calledNumbers = new HashSet<String>();

@Override public void onCreate() { super.onCreate(); telMgr = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); telMgr.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);

// Registro un ricevitore per le chiamate uscenti: registerReceiver(outgoingCallReceiver, new IntentFilter("android.intent.action.NEW_OUTGOING_CALL")); service = this; }

@Override public void onDestroy() { telMgr.listen(phoneListener, PhoneStateListener.LISTEN_NONE); super.onDestroy(); }

@Override public IBinder onBind(Intent intent) { return null; }… }

Page 49: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Ascoltare le chiamate entranti/uscenti

© 2012 - CEFRIEL

private PhoneStateListener phoneListener = new PhoneStateListener() { AtomicBoolean isCalling = new AtomicBoolean(false); @Override public void onCallStateChanged(int state, String incomingNumber) { if (state == TelephonyManager.CALL_STATE_OFFHOOK) { isCalling.set(true); // Chiamata iniziata.. ricordiamolo: } if (state == TelephonyManager.CALL_STATE_IDLE) { if (isCalling.compareAndSet(true, false) && incomingNumber!=null && !incomingNumber.isEmpty()) { // Aggiungo il numero all'insieme: addNumber(incomingNumber); } } } };

private BroadcastReceiver outgoingCallReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String outgoingNumber = // Ottengo il numero:

intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); // Lo aggiungo: if (outgoingNumber!=null && !outgoingNumber.isEmpty()) { addNumber(outgoingNumber); } } };

Page 50: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Nella nostra attività…

© 2012 - CEFRIEL

@Override protected void onStart() { super.onStart(); // Avvio il servizio (se non è già avviato): startService(new Intent(this, CallNotificationService.class));}@Override protected void onResume() { super.onResume(); updateFromService(); // Aggiorno dal servizio:}

protected void updateFromService() { CallNotificationService service = CallNotificationService.getInstance(); if (service==null) return; Set<String> nums = service.getNumbers(); final List<CallSchedule> toBeRemoved = new LinkedList<CallSchedule>(); for (CallSchedule schedule: schedules) { for (String num2: nums) { if (PhoneNumberUtils.compare(schedule.getNumber(),num2)) { toBeRemoved.add(schedule); } } } // Chiedo ed elimino se serve…}

<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

Page 51: © 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Da qui? Abbiamo solamente iniziato:

– con la rassegna delle componenti;– con la rassegna dei pattern applicati negli esempi;– con gli strumenti per la costruzione di UI.

Giochiamoci un po’:– «Learning by doing», Richard Feynman;– costruiamo delle app di esempio che sfruttino

servizi.

Nelle prossimo puntate:– ampliamo il set di strumenti per la gestione della

UI;– impariamo ad interagire con le altre app;– molto altro ancora!© 2012 - CEFRIEL


Recommended