My app is implementing Text-to-Speech that is working well. But when I rotate my phone, the dictation stops itself.
You have to know that I use two different layout for landscape/portrait mode.
How should I handle the screen rotation ?
1) With android:configChanges="orientation|screenSize" // I don't think it's the best way
2) With something else?
Thank you
There are several steps to enabling the TTS to continue working on rotation, without needing to handle the configuration changes in your app. The steps include:
Make the TTS variable static
private static TextToSpeech tts;
Only initialize it once, if it's null
if (tts == null) { ... }
Initialize the TTS object using the Application Context
tts = new TextToSpeech(getApplicationContext(), this);
Here's a snapshot of my experimental app that incorporates these elements http://code.google.com/p/android-tts-experiments/source/detail?r=b879fd1042151cbebc736b89a9fb660c895081ea I've managed to test it on various android devices and android versions. Sometimes the TTS seems to hesitate slightly on rotation on my older, less powerful devices, however it continues speaking the text it's being given to say and queued messages are retained in the queue and spoken once the current text has been spoken completely by the TTS engine.
You basically have 2 options:
1) Handle the orientation change yourself. Usually this is pretty easy. You just need to implement onConfigurationChange() and in there you can just rebuild the layout by calling setContentView(). Be aware that this will recreate all your UI views so you will have to call findViewById() and reset onClickListeners again for all views that you need to keep track of.
2) Ensure that the TTS instance doesn't get shutdown on orientation change. You can do this either by passing the TTS instance from one instance of the activity to the next OR keep track of the TTS instance in a static variable (if you do this you need to make sure that you call shutdown() on it at the appropriate time, not in onDestroy() of your activity.
Related
I have a stock Nexus 5 running 4.4.2 (using ART if it matters) and I've found an interesting scenario. I have this as my onDestroy():
#Override
protected void onDestroy() {
super.onDestroy();
t.setText("onDestroy");
t.show();
}
It's a GPS oriented app so I'm up and walking around. I am using the technique mentioned in this question to show a lot of debug toast messages.
Anyway, when I rotate my app, the toast appears. I understand that the activity is destroyed and recreated for the new orientation, but how can I know what's really going on? How can I tell when my app is REALLY getting destroyed and not just being rotated? Similar to this question, I want to log out when a particular activity is destroyed.
Since Honeycomb, the isChangingConfigurations() method can be queried to check whether the Activity is being recreated due to configuration changes. Alternatively, the isFinishing() method can be queried on any API level to check whether the Activity is actually being finished, or is only being destroyed temporarily by the system.
As far as I can determine, the two methods should always return mutually consistent results in practice. The only point where they might have diverged is when the system kills the process to clear memory, but there are no callbacks or interaction with the app at that point.
The documentation of the onDestroy() method mentions the use of the isFinishing() method:
Perform any final cleanup before an activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.
You can put it in a fragment with setRetainInstanceState(true) set. Place your code in the onDestroy() method of the fragment. Then, the fragment will not be destroyed on orientation changes.
First of all, you should not use onDestroy() to do anything because its not guaranteed to be called. I would put things on the onPause() method; I wouldn't even put things in onStop().
Also, Im not sure why you want to log out a user when they navigate away from the app. I would rather implement some kind of timer on the app or server to log out after x time.
Now, the answer lies in the documentation: http://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges
You might want to override onConfigurationChanged so that your activity is not restarted.
I found a couple of solutions which are really just patterns to detect when the screen rotates. Alternatively, you can determine that the device was actually destroyed by checking some static data member to see if it was initialized or not.
Configuration changed solutions:
The first one involves handling all of the configuration changes in the onConfigurationChanged callback.
"Note that this will only be called if you have selected
configurations you would like to handle with the configChanges
attribute in your manifest."
The second involves listening for Display.getRotation() which returns a Surface.ROTATION_* object. Which is the new orientation of your screen relative to the natural state of the device orientation.
Again, you can use the configuration changes along with the static member.
Add ConfigChanges.UiMode flag to the ConfigurationChanges attribute for your MainActivity class, and this solves the problem.
More details: Android Launcher "OnDestroy" gets called twice
From the Activity, I am creating a Handler to fire off my AsyncTask every 45 seconds in order to refresh the content of my ListView's DataAdapter. The AsyncTask works great and keeps the user informed on the progress through ProgressUpdates and Toast messages.
Since the thread's doInBackground is fire and forget and not re-usable, I am having to create a new instance of the AsyncTask from my Hander that is firing off every 45 seconds. The problem is when the screen is rotated and and then I get concurrent messages going off because the Hander was recreated and created a new instance of the AsyncTask, so the friendly user progress through ProgressUpdates and Toast messages is overwhelming and makes utilizing the ListView difficult.
And please don't suggest this as a solution: android:screenOrientation="portrait" is not an option.
For something that has to run so frequently, should I just be using a custom Thread and not the AsyncTask class? ToDo: Not shown, I have to update the Adapter later from the Sensor's onSensorChanged event to update bearings on for each location in the ListView, I was going to run that on a separate AsyncTask class because I don't need to notify the user everytime the device bearing has changed.
Since the AsyncThread cannot be reused, am I doing this all wrong? In short, what is the best way to have the Activity refresh the ListView and keeping off the UI thread when doing so?
The problem is when the screen is rotated and and then I get concurrent messages going off because the Hander was recreated and created a new instance of the AsyncTask.
Reason quoting from API Activity - Configuration Changes:
Unless you specify otherwise, a configuration change (such as a change in screen orientation, language, input devices, etc) will cause your current activity to be destroyed, going through the normal activity lifecycle process of onPause(), onStop(), and onDestroy() as appropriate.
So every object has a activity-scope life cycle (i.e. Handler, AsyncTask and etc. defined within your activity class) is suffered by this activity recreation. However, you can bypass this activity recreation, as stated in the later paragraph of Activity - Configuration Changes section:
In some special cases, you may want to bypass restarting of your activity based on one or more types of configuration changes. This is done with the android:configChanges attribute in its manifest. For any types of configuration changes you say that you handle there, you will receive a call to your current activity's onConfigurationChanged(Configuration) method instead of being restarted. If a configuration change involves any that you do not handle, however, the activity will still be restarted and onConfigurationChanged(Configuration) will not be called.
Not related to topic, but as a good practice, you should always destroy used object (Handler, AsyncTask and etc.) properly when activity is about to finish (i.e. in onDestroy() method).
For something that has to run so frequently, should I just be using a custom Thread and not the AsyncTask class?
AsyncTask is pretty handy but not suit for periodic task, I would use ScheduledExecutorService or TimerTask in this case, check out my answer here for sample code.
Can you please post a bit of your code ? It may be useful to understand where your problem is.
As york has pointed it out, you should probably use TimerTask. It seems that it suit better with what you are trying to do.
If it is the creation of a new instance of the Handler that create the probleme you can try something like this :
private Handler mHandler = null;
#Override
public void onCreate(Bundle _savedInstanceState) {
super.onCreate(_savedInstanceState);
setContentView(R.layout.my_layout);
if (mHandler == null) {
// TODO create your handler here
}
}
EDIT :
You can test _savedInstanceState == null too.
_savedInstanceState is used to save the state of the activity so turning the phone shouldn't be a problem anymore.
However, if you leave the activity and then go back to it, it will create a new handler (except if you instanciate it as a static variable).
In my app, I want a media file to play, and to keep playing if the user rotates the screen (destroying the Activity), but I want it to stop playing if the user moves to a different Activity or another Activity appears over this one, they press the back button, whatever.
I believe there's an API for this in Honeycomb, but I need something that will work in Android 2.2.
I'd rather not use onConfigurationChanged to handle all the configuration changes myself--that sounds like a lot of work and potential bugs--and the issue with onRetainNonConfigurationInstance() is that it doesn't run until after onStop fires--but onPause would be the logical place to pause the media, if appropriate.
Is there a way, in onPause, to tell if the Activity is being paused for a configuration change versus another reason?
Your solution has at least one potential problem.
According to Android documentation:
Some device configurations can change during runtime (such as screen orientation, keyboard availability, and language). When such a change occurs, Android restarts the running Activity (onDestroy() is called, followed by onCreate()).
Testing for a change in rotation only handles one case. Since this is likely to be the most common cause of configuration change, it's an OK solution. But what if you could handle all cases, existing or added in some future version of Android, without the overhead of handling configuration yourself?
Updated to use onRetainNonConfigurationInstance. Android docs have this to say about it:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration.
Also changed chain to super.onDestroy() to happen after stopping media, but not entirely sure about that. Guess it depends on what stopping media means, and what effect destroying may have on stopping media.
private Boolean mConfigurationChange = false;
public Object onRetainNonConfigurationInstance() {
mConfigurationChange = true;
return null;
}
public void onDestroy() {
if (!mConfigurationChange) {
// Code to stop media file goes here.
}
super.onDestroy();
}
Prior to Honeycomb, onRetainNonConfigurationInstance() as mentioned in the accepted answer is the best way to do this.
Starting with Honeycomb, that's deprecated, but there is a much simpler way: call isChangingConfigurations() instead, during any of your onPause()/onStop()/onDestroy().
Finally, if using the support library FragmentActivity (
android.support.v4.app.FragmentActivity) on pre-Honeycomb, onRetainNonConfigurationInstance() is declared final, but you can override onRetainCustomNonConfigurationInstance() instead for the same effect.
I finally figured out the answer to my own question!
public class MyActivity extends Activity {
Display display;
int originalRotation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
originalRotation = display.getRotation();
}
public boolean endingDueToConfigurationChanging() {
int newRotation = display.getRotation();
return (newRotation != originalRotation);
}
}
Display.getRotation() is designed to return a constant indicating whether the device is 0, 90, 180 or 270 degrees off its natural rotation. However, when the device is rotated, this value is updated before the activity ends--it's actually updated as early as in onPause()! So if you compare the value in onPause() (the new value) with the one in onCreate() (the original value) then you know the Activity it shutting down due to a screen rotation or not.
You can try listening for the ACTION_CONFIGURATION_CHANGED intent and setting an internal flag indicating the pause is from a configuration change.
I'm not sure if you will receive the intent in time or that you will be guaranteed it will arrive in time as it is probably asynchronous. Might be worth a try though.
You can also try using View#onConfigurationChanged in case its applicable in your case(i.e - you have a view for your media player and have a reference to it in the view).
My application makes heavy use of the Text to Speech API.
It is a game which leads the player to change the orientation frequently. I do have different layouts for portrait and landscape and this is ok.
I use onRetainNonConfigurationInstance() and onCreate() to keep the state.
However, there is one fact that causes problems:
I am calling tts.shutdown() in onDestroy() which I think is necessary to free resources properly. However, this causes the current text and all queued messages to be lost when the user changes the orientation. As the Text-to-Speech object has a reference on the "old" activity, I cannot transfer it to the new activity.
How can I solve this problem?
This could be a good use for the Application class, where you initiate the TTS in onCreate() (and forget about it, as onTerminate() is not called on real devices).
From the documentation regarding the android:configChanges='orientation' attribute of the activity tag in the manifest:
Note: Using this attribute should be avoided and used only as a last-resort. Please read Handling Runtime Changes for more information about how to properly handle a restart due to a configuration change.
Why does it say this?
In the case of threads and networking requests via a service API library, a request could be made with a reference to the original Activity, and then an orientation change could occur, leaving the thread pointing to the old Activity.
While this can be fixed, it's tedious and ugly compared to just handling the configuration changes yourself.
Why should it be avoided?
Edit: I guess I should also ask: would this be an acceptable reason for doing the orientation configuration changes yourself?
Why does it say this?
Because they want you to read the "Handling Runtime Changes" section in the docs. :-)
In the case of threads and networking
requests via a service API library, a
request could be made with a reference
to the original Activity, and then an
orientation change could occur,
leaving the thread pointing to the old
Activity.
For cases where you care about rotations, don't use implicit references to the Activity (e.g., regular inner class), but rather explicit ones (e.g., static inner class). Here is a brand-spankin'-new sample project that demonstrates what I mean.
While this can be fixed, it's tedious
and ugly compared to just handling the
configuration changes yourself.
The recommendation is there, I suspect, because they are afraid newcomers to Android will mess up "handling the configuration changes yourself". For example, they decide to have some different strings for landscape (where you have more horizontal room) and forget to reload them. Or, they decide to have some different images for landscape and forget to reload them. And so on.
Most activities in most apps aren't going to have background threads or sockets or whatever of their own, either because they just don't need them, or because something else is managing them (e.g., a Service). Their stock implementation of destroy-and-recreate typically "just works", particularly with the built-in support for saving the widget state of EditTexts and such.
In addition, you may not save that much by "handling it yourself", because you still need to implement onSaveInstanceState() anyway, to handle scenarios other than configuration changes (e.g., your activity is kicked out of RAM to free up space).
Now, is their phrasing a bit harsh? Probably. Seasoned Android developers can make their own decisions as to which rotation handling strategy to employ. I suspect their tone is to try to scare newcomers into thinking twice before going down this route.
I agree, in certain cases it seems like overkill to have to save state and redraw my views. I can understand if you want to configure a different layout when orientation changes but otherwise it is so much easier to add this to my activity.
#Override
public void onConfigurationChanged(Configuration newConfig) {
if(newConfig.equals(Configuration.ORIENTATION_LANDSCAPE)
|| newConfig.equals(Configuration.ORIENTATION_PORTRAIT)
|| newConfig.equals(Configuration.ORIENTATION_SQUARE)
|| newConfig.equals(Configuration.ORIENTATION_UNDEFINED)) {
} else {
super.onConfigurationChanged(newConfig);
}
}