Dopo la prima costruzione guidata da Eclipse ho questo:
public class Servizio extends Service{ @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }Gli eventi che voglio esplorare sono onCreate, onStartCommand e onDestroy.
Li inserisco.
public class Servizio extends Service{ @Override public void onCreate(){ super.onCreate(); Log.v("SEGNALE", "ONCREATE"); } @Override public int onStartCommand(Intent intent, int flags, int startUI){ Log.v("SEGNALE", "ONSTARTCOMMAND"); return startUI; } @Override public void onDestroy(){ super.onDestroy(); Log.v("SEGNALE", "ONDESTROY"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }E ora chiamo questo Service da MainActivity:
public class MainActivity extends Activity { Button button; Intent intent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button=(Button)findViewById(R.id.button1); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { intent=new Intent(getApplicationContext(),Servizio.class); startService(intent); } }); } }... chiamo con la pressione di un pulsante.
E vado.
E schiaccio ripetutamente il Button.
Ecco:
06-16 19:26:01.314: V/SEGNALE(2305): ONCREATE 06-16 19:26:01.314: V/SEGNALE(2305): ONSTARTCOMMAND 06-16 19:26:06.289: V/SEGNALE(2305): ONSTARTCOMMAND 06-16 19:26:08.170: V/SEGNALE(2305): ONSTARTCOMMAND 06-16 19:26:09.662: V/SEGNALE(2305): ONSTARTCOMMAND 06-16 19:26:11.203: V/SEGNALE(2305): ONSTARTCOMMANDONCREATE viene chiamato soltanto alla prima pressione.
Ci riprovo ancora, reinstallando sul cellulare da Eclipse dopo aver chiuso l'App sul cellulare:
06-16 19:28:28.678: V/SEGNALE(4387): ONSTARTCOMMAND 06-16 19:28:30.099: V/SEGNALE(4387): ONSTARTCOMMAND 06-16 19:28:31.340: V/SEGNALE(4387): ONSTARTCOMMAND 06-16 19:28:32.401: V/SEGNALE(4387): ONSTARTCOMMANDEcco: ONCREATE non viene più evocato.
Dopo un po' ottengo un messaggio di errore "L'applicazione Esercizio6 non risponde. Chiudere?".
Digito "SI" e vedo in LogCat a vedere di capirci qualcosa...
Nessuna segnalazione.
Dunque, il Service viene creato una sola volta, mentre le volte successive è già creato e quindi viene soltanto eseguito startCommand.
Passato un po' di tempo, riapro l'App e compare di nuovo ONCREATE.
E mi segnala poi di nuovo l'errore di sopra, senza alcuna segnalazione in LogCat.
Provo a chiudere l'App e a riaprirla, e ridigito.
06-16 19:36:25.543: V/SEGNALE(5494): ONCREATE 06-16 19:36:25.543: V/SEGNALE(5494): ONSTARTCOMMAND 06-16 19:36:25.553: V/SEGNALE(5494): ONSTARTCOMMAND 06-16 19:36:26.844: V/SEGNALE(5494): ONSTARTCOMMAND 06-16 19:36:27.555: V/SEGNALE(5494): ONSTARTCOMMAND 06-16 19:36:28.145: V/SEGNALE(5494): ONSTARTCOMMANDMa se faccio tutto, chiusura e riapertura dell'App, in tempi brevi, ONCREATE non viene richiamata.
Se lo faccio in tempi più lunghi viene richiamata, e comunque una singola pressione sul bottone mi causa ripetute chiamate di ONSTARTCOMMAND.
Comunque, ONCREATE, trovo sulla documentazione, viene eseguito se è necessario.
Resta l'enigma di quel messaggio di errore...
Che non sia perché onStartCommand ha necessariamente bisogno di un return STICKY o qualche altra cosa?
Ora ci metto Service.START_STICKY e vediamo se lo fa di nuovo...
Ecco, sembra che con lo START_STICKY non venga fuori quel problema.
Dunque bisogna necessariamente dare un valore al return di onStartCommand.
Con lo START_STICKY sembra che sia veramente "colloso": dopo diversi minuti non si ricrea ONCREATE, segno che il Service è ancora "vivo" nel cellulare.
Ora provo con un diverso valore...
@Override public int onStartCommand(Intent intent, int flags, int startUI){ Log.v("SEGNALE", "ONSTARTCOMMAND"); return Service.START_NOT_STICKY; }
06-16 20:31:21.959: V/SEGNALE(10437): ONCREATE 06-16 20:31:21.959: V/SEGNALE(10437): ONSTARTCOMMAND 06-16 20:31:23.891: V/SEGNALE(10437): ONSTARTCOMMAND 06-16 20:31:25.062: V/SEGNALE(10437): ONSTARTCOMMAND 06-16 20:31:25.662: V/SEGNALE(10437): ONSTARTCOMMANDChiudo l'App e la riapro, e ogni volta vedo questo:
06-16 20:31:21.959: V/SEGNALE(10437): ONCREATE 06-16 20:31:21.959: V/SEGNALE(10437): ONSTARTCOMMAND 06-16 20:31:23.891: V/SEGNALE(10437): ONSTARTCOMMAND 06-16 20:31:25.062: V/SEGNALE(10437): ONSTARTCOMMAND 06-16 20:31:25.662: V/SEGNALE(10437): ONSTARTCOMMANDAttendo più tempo...
06-16 20:33:10.655: V/SEGNALE(11963): ONCREATE 06-16 20:33:10.665: V/SEGNALE(11963): ONSTARTCOMMAND 06-16 20:33:11.676: V/SEGNALE(11963): ONSTARTCOMMAND 06-16 20:33:12.306: V/SEGNALE(11963): ONSTARTCOMMAND
Controprova: ritorno con lo START_STICKY:
@Override public int onStartCommand(Intent intent, int flags, int startUI){ Log.v("SEGNALE", "ONSTARTCOMMAND"); return Service.START_STICKY; }
06-16 20:34:35.368: V/SEGNALE(12274): ONCREATE 06-16 20:34:35.368: V/SEGNALE(12274): ONSTARTCOMMAND 06-16 20:34:36.389: V/SEGNALE(12274): ONSTARTCOMMAND 06-16 20:34:36.919: V/SEGNALE(12274): ONSTARTCOMMAND 06-16 20:34:37.490: V/SEGNALE(12274): ONSTARTCOMMAND 06-16 20:34:39.171: V/SEGNALE(12274): ONSTARTCOMMANDChiudo e riapro...
06-16 20:36:15.355: V/SEGNALE(14166): ONSTARTCOMMAND 06-16 20:36:16.376: V/SEGNALE(14166): ONSTARTCOMMAND 06-16 20:36:17.057: V/SEGNALE(14166): ONSTARTCOMMAND 06-16 20:36:17.667: V/SEGNALE(14166): ONSTARTCOMMANDMa vedo anche:
06-16 20:36:14.344: V/SEGNALE(14166): ONCREATE 06-16 20:36:14.344: V/SEGNALE(14166): ONSTARTCOMMANDsia pure non ogni volta come con lo STICKY.
Ho riprovato con NOT_STICKY e ONCREATE mi appare ogni volta.
Sì, credo che la differenza sia questa: con STICKY il Service viene creato solo se necessario, mentre con NOT_STICKY viene regolarmente creato ogni volta.
Non vedo riferimenti di ONDESTROY, però.
Ecco, se io metto uno stopSelf() arriva il DESTROY, con la necessità di ricreare ogni volta il Service:
@Override public int onStartCommand(Intent intent, int flags, int startUI){ Log.v("SEGNALE", "ONSTARTCOMMAND"); stopSelf(); return Service.START_NOT_STICKY; }
06-16 20:52:43.849: V/SEGNALE(16417): ONCREATE 06-16 20:52:43.849: V/SEGNALE(16417): ONSTARTCOMMAND 06-16 20:52:43.849: V/SEGNALE(16417): ONDESTROY 06-16 20:52:45.151: V/SEGNALE(16417): ONCREATE 06-16 20:52:45.151: V/SEGNALE(16417): ONSTARTCOMMAND 06-16 20:52:45.151: V/SEGNALE(16417): ONDESTROY 06-16 20:52:48.004: V/SEGNALE(16417): ONCREATE 06-16 20:52:48.004: V/SEGNALE(16417): ONSTARTCOMMAND 06-16 20:52:48.014: V/SEGNALE(16417): ONDESTROY
Ecco perché nel mio Service parlante è assurdo mettere il codice con lo speak() nel contesto di onCreate: in tutti i casi in cui onCreate non viene creato, il Service non parlerà!
Provo ancora:
public class Servizio extends Service{ TextToSpeech tts; @Override public void onCreate(){ super.onCreate(); Log.v("SEGNALE", "ONCREATE"); tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { // TODO Auto-generated method stub if(status==TextToSpeech.SUCCESS){ tts.setLanguage(Locale.ITALIAN); } } }); } @Override public int onStartCommand(Intent intent, int flags, int startUI){ Log.v("SEGNALE", "ONSTARTCOMMAND"); tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null); stopSelf(); return Service.START_NOT_STICKY; } @Override public void onDestroy(){ super.onDestroy(); Log.v("SEGNALE", "ONDESTROY"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }Ecco, in questo caso ottengo un errore:
06-16 21:11:07.356: V/SEGNALE(19270): ONCREATE 06-16 21:11:07.536: W/ApplicationPackageManager(19270): getCSCPackageItemText() 06-16 21:11:07.596: I/TextToSpeech(19270): Sucessfully bound to com.google.android.tts 06-16 21:11:07.596: V/SEGNALE(19270): ONSTARTCOMMAND 06-16 21:11:07.616: W/TextToSpeech(19270): speak failed: not bound to TTS engine 06-16 21:11:07.696: V/SEGNALE(19270): ONDESTROY 06-16 21:11:07.726: E/ActivityThread(19270): Service com.example.esercizio6.Servizio has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@41e1a728 that was originally bound hereche è dovuto al fatto che non c'è connessione con il tts engine.
Troppo presto, probabilmente.
Provo a mettere lo speak() nel contesto di onCreate:
public class Servizio extends Service{ TextToSpeech tts; @Override public void onCreate(){ super.onCreate(); Log.v("SEGNALE", "ONCREATE"); tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { // TODO Auto-generated method stub if(status==TextToSpeech.SUCCESS){ tts.setLanguage(Locale.ITALIAN); tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null); stopSelf(); } } }); } @Override public int onStartCommand(Intent intent, int flags, int startUI){ Log.v("SEGNALE", "ONSTARTCOMMAND"); return Service.START_NOT_STICKY; } @Override public void onDestroy(){ super.onDestroy(); Log.v("SEGNALE", "ONDESTROY"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }Ottengo una risposta molto ritardata, incostante e incompleta.
Perché?
Forse è quello stopSelf() che dà fastidio... Lo levo.
public void onCreate(){ super.onCreate(); Log.v("SEGNALE", "ONCREATE"); tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { // TODO Auto-generated method stub if(status==TextToSpeech.SUCCESS){ tts.setLanguage(Locale.ITALIAN); tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null); } } }); }E vediamo:
Sì, adesso funziona, sia pure con notevole ritardo.
Alla seconda non funziona più, semplicemente perché ONCREATE non viene più eseguita!
E' un bel problema, come gestire il tts in un service.
L'inizializzazione del tts può anche essere messa in onCreate, ma lo speak() no, altrimenti non viene eseguito in tutte quelle situazioni in cui onCreate non viene eseguito.
Peraltro lo speak() rischia di non essere eseguito nemmeno in onStartCommand, se l'inizializzazione del tts in onCreate prende troppo tempo e nel frattempo viene eseguito onStartCommand senza che la connessione al tts sia completa.
Vediamo di trovare esempi in rete...
Ho trovato questo.
Ci provo.
public class Servizio extends Service implements TextToSpeech.OnInitListener,OnUtteranceCompletedListener{ TextToSpeech tts; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onUtteranceCompleted(String utteranceId) { // TODO Auto-generated method stub } @Override public void onInit(int status) { // TODO Auto-generated method stub } }Ecco lo scheletro che ho ottenuto estendendo Service e implementando le interfacce di TextToSpeech.
Ora in onInit piazzo tutto il codice per la creazione di un motore tts, mentre l'istanziazione la piazzo in onCreate.
public class Servizio extends Service implements TextToSpeech.OnInitListener,OnUtteranceCompletedListener{ TextToSpeech tts; @Override public void onCreate(){ tts=new TextToSpeech(this,this); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onUtteranceCompleted(String utteranceId) { } @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=tts.setLanguage(Locale.ITALIAN); tts.speak("Ciao, ciccio", TextToSpeech.QUEUE_FLUSH, null); } } }Ho piazzato l'istanziazione in onCreate e il codice per l'inizializzazione e lo speak() in onInit, ovviamente.
Vediamo se funziona...
Funziona solo la prima volta. Successivamente si blocca.
Ci metto anche onStartCommand, per metterci lo STICKY o meno...
Ecco, adesso mi è più chiaro.
06-17 01:26:09.764: V/SEGNALE(19408): ONCREATE 06-17 01:26:09.764: V/SEGNALE(19408): ONSTARTCOMMAND 06-17 01:26:12.156: I/TextToSpeech(19408): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 01:26:12.196: I/TextToSpeech(19408): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 01:26:12.216: V/SEGNALE(19408): ONINITViene chiamato ONCREATE, quindi ONSTARTCOMMAND e da ultimo ONINIT: tts viene istanziato in onCreate, da dove chiama onInit per l'inizializzazione e quindi lo speak.
onStartCommand viene chiamato direttamente dal richiamo del Service, credo, ma onInit viene chiamato da onCreate nel momento in cui in onCreate tts viene istanziato.
Alla successiva pressione del button, invece, si realizza solo onStartCommand, e per questo il programma non emette alcun suono.
Ora, dunque, bisognerebbe fare in modo da chiudere il Service ogni volta, in modo che si possa riaprire onCreate.
A questo proposito cerchiamo di usare onUtteranceCompletedListener.
E se mettessi tutto in onStartCommand?
Proviamo...
public class Servizio extends Service{ TextToSpeech tts; @Override public void onCreate(){ Log.v("SEGNALE", "ONCREATE"); } @Override public int onStartCommand(Intent intent, int flags, int startUI){ tts=new TextToSpeech(this,new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=tts.setLanguage(Locale.ITALIAN); tts.speak("Vieni avanti, cretino", TextToSpeech.QUEUE_FLUSH, null); } } }); Log.v("SEGNALE", "ONSTARTCOMMAND"); return Service.START_NOT_STICKY; } @Override public void onDestroy(){ super.onDestroy(); Log.v("SEGNALE", "ONDESTROY"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }Sembra funzionare in modo molto più efficiente di prima.
Potrei anche stoppare il Service, comunque.
Questa è la sequenza su LogCat:
06-17 11:39:04.933: V/SEGNALE(12422): ONCREATE 06-17 11:39:04.933: W/ApplicationPackageManager(12422): getCSCPackageItemText() 06-17 11:39:04.943: I/TextToSpeech(12422): Sucessfully bound to com.google.android.tts 06-17 11:39:04.943: V/SEGNALE(12422): ONSTARTCOMMAND 06-17 11:39:05.053: I/TextToSpeech(12422): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 11:39:05.063: I/TextToSpeech(12422): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 11:39:20.208: W/ApplicationPackageManager(12422): getCSCPackageItemText() 06-17 11:39:20.218: I/TextToSpeech(12422): Sucessfully bound to com.google.android.tts 06-17 11:39:20.218: V/SEGNALE(12422): ONSTARTCOMMAND 06-17 11:39:20.218: I/TextToSpeech(12422): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 11:39:20.228: I/TextToSpeech(12422): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}La prima volta il sintetizzatore vocale impiega più tempo, per partire, mentre le volte successive parte immediatamente.
Forse non mi conviene che il Service si chiuda...
Il ritardo è dovuto al tempo necessario per la creazione ex novo del Service.
Però, così facendo, se il service rimane attivo, a ogni interruzione del service seguita da ri-creazione, mi "parte" la voce...
Dal momento che non mi interessa l'esecuzione immediata, posso permettermi qualche secondo perché venga creato il Service ex novo, e quindi è meglio interromperlo a ogni esecuzione.
Come fare?
Riscrivo...
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{ TextToSpeech tts; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=tts.setLanguage(Locale.ITALIAN); tts.speak("Ciao, bello", TextToSpeech.QUEUE_FLUSH, null); } } @Override public void onUtteranceCompleted(String utteranceId) { // TODO Auto-generated method stub } }Questo dovrebbe essere sufficiente per parlare...
Vediamo...
No: ho dimenticato di istanziare tts nell'ambito di onCreate.
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{ TextToSpeech tts; @Override public void onCreate(){ tts=new TextToSpeech(this,this); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=tts.setLanguage(Locale.ITALIAN); tts.speak("Ciao, bello", TextToSpeech.QUEUE_FLUSH, null); } } @Override public void onUtteranceCompleted(String utteranceId) { // TODO Auto-generated method stub } }E infatti parla. Con i soliti alcuni secondi di ritardo, ma parla.
Ora voglio che il Service si chiuda per riaprirsi all'occorrenza.
Per prima cosa, mi marco con il Log tutti i metodi
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{ TextToSpeech tts; @Override public void onCreate(){ tts=new TextToSpeech(this,this); Log.v("SEGNALE", "ONCREATE"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=tts.setLanguage(Locale.ITALIAN); tts.speak("Ciao, bello", TextToSpeech.QUEUE_FLUSH, null); } Log.v("SEGNALE","ONINIT"); } @Override public void onUtteranceCompleted(String utteranceId) { // TODO Auto-generated method stub } }Ecco:
06-17 13:20:56.402: I/TextToSpeech(27081): Sucessfully bound to com.google.android.tts 06-17 13:20:56.412: V/SEGNALE(27081): ONCREATE 06-17 13:20:58.454: I/TextToSpeech(27081): Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 13:20:58.494: I/TextToSpeech(27081): Set up connection to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService} 06-17 13:20:58.564: D/SEGNALE(27081): ONINITE adesso andiamo a studiare onUtterance... eccetera...
Per prima cosa mi marco anche quello:
@Override public void onUtteranceCompleted(String utteranceId) { Log.v("SEGNALE","ONUTTERANCE"); }E scopro che non viene eseguito.
Aggiungo anche onDestroy:
@Override public void onDestroy(){ super.onDestroy(); Log.v("SEGNALE","ONDESTROY"); }E so già che non viene eseguito, comunque me lo riprovo ugualmente...
Infatti non viene eseguito.
Andiamo finalmente a studiare onUtteranceCompleted!
E' deprecato, consigliata la classe onUtteranceProgressListener, ma in entrembi i casi è necessario fornire un utteranceId
Che accidenti è un utteranceId?
Soluzione trovata. Ecco il Service:
public class Servizio extends Service implements TextToSpeech.OnInitListener,TextToSpeech.OnUtteranceCompletedListener{ TextToSpeech tts; @Override public void onCreate(){ tts=new TextToSpeech(this,this); Log.v("SEGNALE", "ONCREATE"); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=tts.setLanguage(Locale.ITALIAN); tts.setOnUtteranceCompletedListener(this); HashMapFunziona.hashMap=new HashMap (); hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "frase"); tts.setPitch(0.1f); tts.setSpeechRate(0.1f); tts.speak("Verrò a ucciderti stanotte", TextToSpeech.QUEUE_FLUSH, hashMap); } Log.v("SEGNALE","ONINIT"); } @Override public void onUtteranceCompleted(String utteranceId) { tts.stop(); tts.shutdown(); stopSelf(); Log.v("SEGNALE","ONUTTERANCE"); } @Override public void onDestroy(){ super.onDestroy(); Log.v("SEGNALE","ONDESTROY"); } }
Dopo aver detto la frase, il Service viene stoppato e si distrugge (non senza aver chiuso il tts).
Nessun commento:
Posta un commento