La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

© 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi

Presentazioni simili


Presentazione sul tema: "© 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi"— Transcript della presentazione:

1 © CEFRIEL Componenti di Android Docente: Gabriele Lombardi

2 © 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.

3 © CEFRIEL Sommario SLIDECONTENUTO Attività Cosa sono, ciclo di vita Servizi Come si creano, un esempio concreto Intent Cosa sono, una visione dallalto, esempi Permessi Come definirli, un esempio concreto ContentProvider Come accedere a dati esterni Broadcast receiver Ricevere richieste broadcast dalle altre app

4 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; –lutente non nota mai nulla (se lapp è scritta bene). Sono porzioni di interfaccia grafica: –rappresentano strumenti di interazione con lutente; –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. (?). © CEFRIEL

5 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 unattività di gestione della chiamata con interfaccia (rispondi, chiudi); lattività Sudoku viene messa in pausa: –chiamate callback relative; –la RAM è insufficiente per entrambe le attività: Android decide di eliminare Sudoku: –chiamate callback relative; –lapplicazione Sudoku deve persistere il proprio stato; –la telefonata può avere atto (con la RAM libera); –finisce la telefonata, si chiude lattività di gestione; Android avvia Sudoku passandogli le info; –chiamate callback relative; –lapplicazione dalle info persistite deve ripristinare il proprio stato; Lutente è ignaro di tutto e si illude di parallelismo. © CEFRIEL

6 Ciclo di vita delle Activity © CEFRIEL Nasce: –Creata –Avviata –Ripristinato lo stato Vive: –Lutente 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.

7 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. © CEFRIEL Restore stato per una pausa Restore per una ricreazione

8 Rivediamo la calcolatrice sotto nuova luce Definire una attività: –la classe FractionCalc estende Activity: altre classi estendono Activity per noi: –AliasActivity Alias di unaltra 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; –lAndroidManifest.xml e strings.xml vengono aggiornati: © CEFRIEL FractionCalc

9 Rivediamo la calcolatrice sotto nuova luce Dare una faccia nuova allattività: –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 lascolto 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: 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); © CEFRIEL

10 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à; –lattività 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. © CEFRIEL

11 Creare lhistory durante il normale utilizzo © CEFRIEL private LinkedList history = new LinkedList (); // Computing: String exprStr = new StringBuilder(expr).toString(); Fraction res = Calculator.compute(exprStr); // Updating the history: 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 (); }

12 Creare unattività «a lista» per lhistory © CEFRIEL public class HistoryList extends ListActivity public void onCreate(Bundle icicle) { … } … } Creo la classe e adatto il public void onCreate(Bundle icicle) { super.onCreate(icicle); List history = (List )getIntent().getSerializableExtra(FractionCalc.HISTORY_EXTRA); setListAdapter(new ArrayAdapter (this, android.R.layout.simple_list_item_1, history)); } Ottengo lhistory Setto protected void onListItemClick(ListView l, View v, int position, long id) { setResult(Activity.RESULT_OK, new Intent().putExtra(FractionCalc.POSITION_EXTRA, position)); finish(); }

13 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: © 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); 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.

14 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 Lattività 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. © CEFRIEL res/xml/preferences.xml Dark UI Clean light Coloured dark light coloured res/values/arrays.xml case MENU_PREFERENCES: startActivity(new Intent(this, Preferences.class)); return true; …

15 Quali componenti abbiamo assemblato? © CEFRIEL FractionCalc Intent HistoryList Dati (extra) Menu Intent Risultati (extra) Adapter View da XML View da??? Gli Intent sono degli URI con più informazioni e capacità: possono trasportare dati strutturato, azione, valore di ritorno, modalità di accesso, …

16 Attività, task e back/stack Arrivati allhistory 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 dellintero back-stack; se viene premuto il tasto «back»: –distrutta lattività corrente: onPause onStop onDestroy; –viene ripristinata la precedente: onResume © CEFRIEL Back-stack del Task FractionCalc Back-stack del Task FractionCalc HistoryList Back-stack del Task FractionCalc Intent verso HistoryList Pulsante back oppure finish()

17 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 unazione richiesta (come i verbi HTTP): ACTION_MAIN inizia unattività come task ACTION_CALL inizia una chiamata … –definiscono una categoria per il destinatario dellIntent: CATEGORY_LAUNCHER attività iniziale CATEGORY_PREFERENCE pannello preferenze … –trasportano dei dati (negli Intent i «dati» sono lURI): ad esempio associato ad ACTION_CALL: –tel: 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 lintent ci sono alcuni flag. © CEFRIEL

18 Gli intent: accedere ai contatti Scopo dellapp 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 dellapp: –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à! © CEFRIEL

19 Accedere ai contatti Core features dellapplicazione 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! © 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

20 Cosa ci regala lattività 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. © 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; }

21 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). © 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:

22 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: © CEFRIEL Contact _ID DISPLAY_NAME Data CONTACT_ID NUMBER

23 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 nellIntent; 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). © CEFRIEL Contact _ID DISPLAY_NAME Data CONTACT_ID NUMBER Intent Cursor ContentResolver Cursor Creiamo e salviamo un CallSchedule nella lista

24 Persistenza: accedere con i cursori! Vedremo come persistere i dati della nostra app. © 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();

25 Abbiamo chiesto il permesso? Cenni di sicurezza da parte dellutente: –caro utente.. questa app vuole fare questo e quello… –…per effettuare certe operazioni serve il consenso: dellutente che deve esserne consapevole; a tempo di installazione dellapp. –il manifest dichiara le caratteristiche dellapp: versioni supportate del framework; supporto per tipi di display; feature richieste dallapp per funzionare: –camera, accelerometro, touch… permessi per funzionalità e contenuti. Nel nostro caso: – © CEFRIEL

26 Una lista completamente custom Vogliamo mostrare la lista di schedule con: –nome visualizzato del contatto con sotto il numero; –tasti per eliminare lelemento 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! © CEFRIEL FastCall ListActivityCallScheduleAdapter 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 vista Mantiene i dati/componenti per una riga e ne nasconde i dettagli di aggiornamento e accesso (efficienza e disaccoppiamento)

27 Una lista completamente custom © CEFRIEL Layout XML relativo ad ogni singola riga:

28 Una lista completamente custom © CEFRIEL // Lista contenente i dati: private List schedules = new LinkedList 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 { public CallScheduleAdapter() { super(FastCall.this, …, schedules); 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 lholder: else holder = (CallScheduleHolder)row.getTag(); // Ottengo il modello e inserisco i dati usando l'holder: holder.populateFrom(getItem(position)); return row; }

29 Una lista completamente custom © 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() 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

30 Aggiungiamo il tasto di chiamata Widget nel layout e drawable tra le risorse: © CEFRIEL Ascoltatore di eventi per il tasto nellholder: row.findViewById(R.id.call).setOnClickListener(new View.OnClickListener() 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: Proviamolo… problemi riscontrati? –Al termine della chiamata non torna allattività. –Ascolteremo lo stato del telefono in un servizio.

31 Rendiamo persistenti i nostri schedule Molti modi per gestire la persistenza: –il più corretto consiste nellutilizzare un database; –in FastCall utilizzeremo le SharedPreferences: –si tratta di una mappa persistente di preferenze tipizzate. Leggiamo le preferenze shared in «onCreate»: © CEFRIEL savedSchedules = getSharedPreferences("schedules", MODE_PRIVATE); for (Map.Entry entry: savedSchedules.getAll().entrySet()) { // Ne aggiungo uno: adapter.add(new CallSchedule(entry.getKey(), entry.getValue().toString())); } Aggiungiamo se lutente lo richiede: Eliminiamo quando lo decide lutente: 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();

32 Eliminazione accidentale… preveniamola! Quando si deve eliminare qualcosa… –…è sempre meglio chiedere allutente; –per farlo serve una dialog… –…in Android le dialog sono asincrone! © 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() // 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):

33 Utilizzo di esempio dellapp © CEFRIEL Menu Add Anna Morra Tasto call

34 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). © CEFRIEL

35 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 allSQL con parametri JDBC; –se viene definita selezione, servono gli argomenti: passati per ordine in un array; –ordinamento definito come ultimo parametro. © 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. );

36 Struttura dellURI 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 lutilizzo di wildcard: content://authority_name/table1/* Esempi: –content://user_dictionary/words –content://com.android.calendar/time/ –content://com.android.contacts/contacts/1 © CEFRIEL

37 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 lindice); –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. © CEFRIEL

38 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. © CEFRIEL

39 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, Voic Contract. 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: –Lutente deve sempre poter decidere se permettere a unapplicazione di accedere alle risorse del telefono! –Permessi temporanei su un URI: lapp che restituisce lURI può settare: –FLAG_GRANT_READ_URI_PERMISSION –FLAG_GRANT_WRITE_URI_PERMISSION © CEFRIEL

40 Modalità di accesso ai dati Tramite Intent: –non abbiamo permessi, non accediamo direttamente; –possiamo richiedere laccesso a unaltra 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); © CEFRIEL

41 Tornando alla nostra app… Notato la scomodità??? –Effettuando una chiamata si esce dallapp; –eliminiamo gli schedule di chiamate già effettuate; –lutente potrebbe aver trovato occupato, libero, voler chiamare nuovamente… in generale meglio chiedere; –vogliamo chiedere se eliminare lo schedule al rientro; –vogliamo farlo SOLO se lutente è 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. © CEFRIEL

42 Un ascoltatore «al telefono» © 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

43 Problemi di questo approccio Non posso s-registrare lascoltatore: –per farlo è sufficiente ascoltare levento 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. © CEFRIEL

44 Servizi: cosa sono? Le nostre app fino ad ora: –hanno aderito allautoma 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 dellActivity. –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. © CEFRIEL

45 Tipologie di servizi Servizi locali: –sono strettamente legati allapplicazione 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 uninterfaccia 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. © CEFRIEL

46 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. © CEFRIEL

47 Servizi… un esempio di utilizzo pratico Vogliamo ascoltare chiamate uscenti/entranti: –usiamo un servizio locale (avviato con startService); –ci inseriamo lascoltatore di telefonia; –ascoltiamo anche gli eventi di chiamate in uscita: utilizzeremo un BroadcastReceiver; –aggiorneremo lelenco chiedendo allutente. © CEFRIEL FastCallCallNotificationService PhoneStateListenerBroadcastReceiver

48 Il servizio © CEFRIEL public class CallNotificationService extends Service { private static CallNotificationService service; // Gestisco un riferimento allistanza: public static CallNotificationService getInstance() { return service; } private TelephonyManager telMgr; private Set calledNumbers = new HashSet 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; public void onDestroy() { telMgr.listen(phoneListener, PhoneStateListener.LISTEN_NONE); super.onDestroy(); public IBinder onBind(Intent intent) { return null; } … }

49 Ascoltare le chiamate entranti/uscenti © CEFRIEL private PhoneStateListener phoneListener = new PhoneStateListener() { AtomicBoolean isCalling = new 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() 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); } } };

50 Nella nostra attività… © protected void onStart() { super.onStart(); // Avvio il servizio (se non è già avviato): startService(new Intent(this, CallNotificationService.class)); protected void onResume() { super.onResume(); updateFromService(); // Aggiorno dal servizio: } protected void updateFromService() { CallNotificationService service = CallNotificationService.getInstance(); if (service==null) return; Set nums = service.getNumbers(); final List toBeRemoved = new LinkedList (); for (CallSchedule schedule: schedules) { for (String num2: nums) { if (PhoneNumberUtils.compare(schedule.getNumber(),num2)) { toBeRemoved.add(schedule); } } } // Chiedo ed elimino se serve… }

51 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! © CEFRIEL


Scaricare ppt "© 2012 - CEFRIEL Componenti di Android Docente: Gabriele Lombardi"

Presentazioni simili


Annunci Google