Search code examples
javaandroidandroid-servicetext-to-speech

Android activity accessing service's static reference before the service is ready


I am trying to fire a Text to speech service from an activity and the activity based on certain events would cause the appropriate string to be sent to the service which speaks out.

Here is my TTSService:

public class TTSService extends Service implements TextToSpeech.OnInitListener{

    private String str;
    private TextToSpeech mTts;
    private static final String TAG="TTSService";
    public static TTSService sInstance;



    @Override

    public IBinder onBind(Intent arg0) {

        return null;
    }


    @Override
    public void onCreate() {

        mTts = new TextToSpeech(this,
                this  // OnInitListener
        );
        mTts.setSpeechRate(0.5f);
        Log.v(TAG, "oncreate_service");
        str ="turn left please ";
        super.onCreate();
        sInstance=this;
    }


    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        if (mTts != null) {
            mTts.stop();
            mTts.shutdown();
        }
        super.onDestroy();
    }


    @Override
    public void onInit(int status) {
        Log.v(TAG, "oninit");
        if (status == TextToSpeech.SUCCESS) {
            int result = mTts.setLanguage(Locale.US);
            if (result == TextToSpeech.LANG_MISSING_DATA ||
                    result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Log.v(TAG, "Language is not available.");
            } else {

                sayHello(str);

            }
        } else {
            Log.v(TAG, "Could not initialize TextToSpeech.");
        }
    }
    public void sayHello(String str) {
        mTts.speak(str,
                TextToSpeech.QUEUE_FLUSH,
                null);
    }
}

Here is my activity's code to create the service:

public void onCreate(Bundle savedInstanceState) {
         //rest of code.....
        Intent serviceIntent = new Intent();
        serviceIntent.setAction("com.packagename.texttospeech.TTSService");
        startService(serviceIntent);
}

Here is the activity's onSensorChanged event handler:

 @Override
    public void onSensorChanged(SensorEvent event) {
        float distance = event.values[0];
        while(TTSService.sInstance==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
        TTSService.sInstance.sayHello(Float.valueOf(distance).toString());

    }

Now when I run the activity above, it hangs and does not respond.

On removing the while loop above, I get the following error trace:

ERROR/AndroidRuntime(7469): FATAL EXCEPTION: main
        java.lang.NullPointerException
        at com.packagename.texttospeech.SensorTestActivity.onSensorChanged(SensorTestActivity.java:51)
        at android.hardware.SensorManager$ListenerDelegate$1.handleMessage(SensorManager.java:456)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:123)
        at android.app.ActivityThread.main(ActivityThread.java:4633)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:521)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
        at dalvik.system.NativeStart.main(Native Method)

So clearly the problem is that in the line(SensorTestActivity.java:51):

TTSService.sInstance.sayHello(Float.valueOf(distance).toString());

Here the sInstance is null which suggests that TTSService's onCreate() is yet to be finished.So how should I make my activity wait till the TTSService is ready to be used?


Solution

  • So how should I make my activity wait till the TTSService is ready to be used?

    You don't. Most likely, you get rid of the service entirely, as it does not appear to be necessary. Have the activity use TextToSpeech directly.

    If you have a demonstrable reason why your app needs this to be in a service:

    Step #1: Delete public static TTSService sInstance and the sayHello() method.

    Step #2: Get rid of your existing startService() call.

    Step #3: Replace TTSService.sInstance.sayHello(Float.valueOf(distance).toString()); with a startService() call, to send a command to the service, where the message to be said is added as a String extra.

    Step #4: In onStartCommand() of your service, if the TTS initialization is complete, call speak() to speak the requested string (pulled from the extra). If the TTS initialization is not yet complete, append the string to an ArrayList<String>.

    Step #5: In onInit(), replace your NullPointerException-generating sayHello() call with a loop over the ArrayList<String> contents to speak() each of them.

    This way, your activity does not care whether the TTS engine is ready or not. It simply sends the request to the service, which queues it up until TTS is ready, at which time it will be played.