Ho ancora bisogno di esercitarmi sul ciclo vitale dei Services. Costruisco un Service...
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): ONSTARTCOMMAND
ONCREATE 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): ONSTARTCOMMAND
Ecco: 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): ONSTARTCOMMAND
Ma 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): ONSTARTCOMMAND
Chiudo 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): ONSTARTCOMMAND
Attendo 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): ONSTARTCOMMAND
Chiudo 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): ONSTARTCOMMAND
Ma vedo anche:
06-16 20:36:14.344: V/SEGNALE(14166): ONCREATE
06-16 20:36:14.344: V/SEGNALE(14166): ONSTARTCOMMAND
sia 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 here
che è 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): ONINIT
Viene 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): ONINIT
E 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);
HashMap 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");
}
}
Funziona.
Dopo aver detto la frase, il Service viene stoppato e si distrugge (non senza aver chiuso il tts).