question on handles and runnables

8 replies [Last post]
mmingfeilam1
Joined: 02/03/2010
User offline. Last seen 1 week 2 days ago.

hi,

i am using handle and runnables to switch/change the content of the TextView using a "timer". for some reason, when running, the app always skips the second step ("Step Two: fry egg"), and only show the last (third) step ("Step three: serve egg").

TextView t;
private String sText;

private Handler mHandler = new Handler();

private Runnable mWaitRunnable = new Runnable() {
public void run() {
t.setText(sText);
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mMonster = BitmapFactory.decodeResource(getResources(),
R.drawable.monster1);

t=new TextView(this);
t=(TextView)findViewById(R.id.TextView01);

sText = "Step One: unpack egg";
t.setText(sText);

sText = "Step Two: fry egg";
mHandler.postDelayed(mWaitRunnable, 3000);

sText = "Step three: serve egg";
mHandler.postDelayed(mWaitRunnable, 4000);
...
}

mmingfeilam1
Joined: 02/03/2010
User offline. Last seen 1 week 2 days ago.
i got the first method to work

Method 1:

public class RecipeDisplay extends Activity {
/** Called when the activity is first created. */
TextView t;
private TTS tts1;

private String sText;

private Handler mHandler = new Handler();

private int counter = 0;

private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts1.setLanguage(ALARM_SERVICE);
tts1.speak(sText, 0, null);
}
};

private Runnable mWaitRunnable = new Runnable() {
public void run() {

if (counter == 0) {
sText = "Step one: unpack egg";
}
else if (counter == 1) {
sText = "Step two: fry egg";
}
else if(counter == 2)
{
sText = "Step three: serve egg";
}
counter++;

t.setText(sText);
tts1 = new TTS(getApplicationContext(), ttsInitListener, true);
}
};

private Runnable mWaitRunnable2 = new Runnable() {
public void run() {
t.setText(sText);

tts1 = new TTS(getApplicationContext(), ttsInitListener, true);
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

t=new TextView(this);
t=(TextView)findViewById(R.id.TextView01);

mHandler.postDelayed(mWaitRunnable, 0);

mHandler.postDelayed(mWaitRunnable, 3000);

mHandler.postDelayed(mWaitRunnable, 8000);

Button prevButton = (Button) findViewById(R.id.prev);

prevButton.setOnClickListener(new View.OnClickListener() {
private TTS tts;
private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts.speak("Step One: unpack egg", 0, null);
}
};

public void onClick(View view) {
t.setText("Step One: unpack egg");
tts = new TTS(getApplicationContext(), ttsInitListener, true);
setResult(RESULT_OK);
}

});
}
}

Method 2:

public class RecipeDisplay extends Activity {
/** Called when the activity is first created. */
TextView t;
private TTS tts1;

private String sText;
private long lStartTime;
private Handler mHandler = new Handler();

private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts1.setLanguage(ALARM_SERVICE);
tts1.speak(sText, 0, null);
}
};

private Runnable mWaitRunnable = new Runnable() {
public void run() {

try
{
while(true)
{
long lElapsedTime = System.currentTimeMillis() - lStartTime;

if (lElapsedTime < 3000) {
sText = "Step one: blast egg";
}
else if (lElapsedTime >= 3000) {
sText = "Step two: fry egg";
}
else // if(lElapsedTime >= 0 && lElapsedTime < 9000)
{
sText = "Step three: serve egg";
}

t.setText(sText);

tts1 = new TTS(getApplicationContext(), ttsInitListener, true);
}
}
catch(Exception e)
{
Log.e("mWaitRunnable", e.getMessage(), e);
}
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

t=new TextView(this);
t=(TextView)findViewById(R.id.TextView01);

lStartTime = System.currentTimeMillis();

Thread thr = new Thread(mWaitRunnable);
thr.start();

Button prevButton = (Button) findViewById(R.id.prev);

prevButton.setOnClickListener(new View.OnClickListener() {
private TTS tts;
private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts.speak("Step One: blast egg", 0, null);
}
};

public void onClick(View view) {
t.setText("Step One: blast egg");
tts = new TTS(getApplicationContext(), ttsInitListener, true);
setResult(RESULT_OK);
}

});
}
}

but the second method, in which i try to use a main loop, seems to only display step one. more over, when i click on the button, the reaction time is slower. i suppose i can go with the first method for now.

Robert Green
Robert Green's picture
Joined: 02/18/2009
User offline. Last seen 21 hours 34 min ago.
Method 2 is probably having

Method 2 is probably having problems because you're blasting calls to setText and creating new TTS objects every single iteration!

First of all, add a little Thread.sleep() in there. Maybe 32ms?
Second, only set text and perform an action when the text changes. Put in a log statement then telling you that it changed. You'll have an easier time figuring it out then.

BTW - your logic is flawed, you will never get to step 3. When over 3000, it will always report true that time is over 3000 and stop at 2.

You want that in reverse order.'

If > 6000, do this
else if > 3000 do this
else (this is the under 3000 case)

Or the opposite can work
if < 3000
else if < 6000
else if < 9000
else (this is the over 9000 case)

:)

mmingfeilam1
Joined: 02/03/2010
User offline. Last seen 1 week 2 days ago.
ok here is the whole code

ok sorry, while trying to simplified my code for purpose of readability, i overmodified the code and ruin the original logics. here is the whole code, i have added the log code and tried Thread.sleep() (which didn't help).

i did get this exception from the log, "android.view.ViewRoot$CalledFromWrongThreadException: Only the original
thread that created a view hierarchy can touch its views".

this suggests that i have to use the main (UI) thread to perform the actions in the Runnable below (http://groups.google.com/group/android-developers/browse_thread/thread/0... ), which means i have to use Handler (Method 1). i also tried using runOnUiThread() by moving all the code within run() into it, also didn't work. from online research, i think runOnUiThread() really just does a Handler.post() (http://www.anddev.org/viewtopic.php?p=30662 ).

anyway, i can work on this later, right now i think i will use method 1.

public class RecipeDisplay extends Activity {
/** Called when the activity is first created. */
TextView t;
private Bitmap mMonster;
private TTS tts1;

private String sText;
private int iDrawable;
private long lStartTime;

ImageView imageView;

private Handler mHandler = new Handler();

private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts1.speak(sText, 0, null);
}
};

private Runnable mWaitRunnable = new Runnable() {
public void run() {

try
{
while(true)
{
sText = "";

long lElapsedTime = System.currentTimeMillis() - lStartTime;

if (lElapsedTime < 3000) {
sText = "Step one: unpack egg";
iDrawable = R.drawable.monster1;
}
else if (lElapsedTime < 8000) {
sText = "Step two: fry egg";
iDrawable = R.drawable.monster2;
}
else// if(lElapsedTime < 13000)
{
sText = "Step three: serve egg";
iDrawable = R.drawable.penguine1;
}

if(sText != "" && iDrawable > -1)
{
Log.d("mWaitRunnable", "sText has changed to: " + sText);

t.setText(sText);
mMonster = BitmapFactory.decodeResource(getResources(),
iDrawable);
imageView = (ImageView) findViewById(R.id.ImageView01);
imageView.setImageBitmap(mMonster);

tts1 = new TTS(getApplicationContext(), ttsInitListener, true);
}

// Thread.sleep(32);
}
}
catch(Exception e)
{
Log.e("mWaitRunnable", e.getMessage(), e);
}
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

t=new TextView(this);
t=(TextView)findViewById(R.id.TextView01);

lStartTime = System.currentTimeMillis();

Thread thr = new Thread(mWaitRunnable);
thr.start();

Button prevButton = (Button) findViewById(R.id.prev);

prevButton.setOnClickListener(new View.OnClickListener() {
private TTS tts;
private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts.speak("Previous Step", 0, null);
}
};

public void onClick(View view) {
t.setText("Previous Step");
tts = new TTS(getApplicationContext(), ttsInitListener, true);
setResult(RESULT_OK);
}

});

ImageButton nextButton = (ImageButton) findViewById(R.id.next);

nextButton.setOnClickListener(new View.OnClickListener() {
private TTS tts;
private TTS.InitListener ttsInitListener = new TTS.InitListener() {
public void onInit(int version) {
tts.speak("Next Step", 0, null);
}
};

public void onClick(View view) {
t.setText("Next Step");
tts = new TTS(getApplicationContext(), ttsInitListener, true);
setResult(RESULT_OK);
}

});
}
}

as for creating the new TTS object all the time, i am sorry to say this is just some library (TTS Extened) i got from the Android market, with very little documentation, and that's the only way i know how to use it. all the online tutorials do it this way as well. thanks.

Robert Green
Robert Green's picture
Joined: 02/18/2009
User offline. Last seen 21 hours 34 min ago.
Also, I feel like you're not

Also,

I feel like you're not understanding what I'm saying. If you do this code in your thread:

while (true) {
if (time < 3000) {
view.setText("sometext");
}
}

and that loop runs every millisecond, which it will without a sleep, then you will be calling view.setText 3000 times! You probably don't want to do that and your users will not appreciate it either :)

Here's what you want to do:

int STEP_TIME = 3000;
int step = 0;
int timeLeft = 0;
while (true) {
timeLeft -= (SystemClock.uptimeMillis() - lastTime);
if (timeLeft < 0) {
step++;
if (step == 1) {
// do step 1 (egg in frying pan or whatever)
} else if (step == 2) {
// do step 2
} else if (step == 3) {
// do step 3
}
timeLeft = STEP_TIME;
}
lastTime = SystemClock.uptimeMillis();
Thread.sleep(32);
}

I do pretty much that exact loop for every kind of animation in my games. The key is to ONLY execute stuff when a new step is to be taken, which is the part inside when timeLeft is less than 0. See, this loop constantly counts down from STEP_TIME to 0, increments, then restarts the counter. You can create your TTS stuff inside that step block but I'm 99.99% sure you don't want to do it in the main part of that loop. All that main part should do is count down to execution.

Robert Green
Robert Green's picture
Joined: 02/18/2009
User offline. Last seen 21 hours 34 min ago.
There are lots of ways to do

There are lots of ways to do this but I think you're starting to get the gist of it. Just keep working at it and put lots of logging in when you get confused :) You'll get there!

mmingfeilam1
Joined: 02/03/2010
User offline. Last seen 1 week 2 days ago.
your step time is constant

but in a recipe, the time it takes to do the various part of the food preparation is different, i.e. frying the egg and cooking the sausage. therefore i will need to change the step time depending on which step i am actually on. also, i have to account for the possibility that the user may want to repeat one or more of the steps if he screws up that step the first time. so when he pauses or hit the back/repeat button, the step and step time will have to be updated.

i think i can modify what you have to suit those needs. I misunderstood you before because from your first reply:

If you want to have a sequence of stuff happen, you have to increment a counter.
counter = 0;
tick() {
if (counter == 0) {
text = "first tick";
} else if (counter == 1) {
text = "second tick";
}
counter++;
}
Now each time you call tick(), it'll set the text to something different.
You can schedule it like this:
mHandler.postDelayed(tickRunnable, 1000);
mHandler.postDelayed(tickRunnable, 2000);
and so long as tickRunnable calls tick(), you'll get a tick in 1 second and another tick in a second after that. Each tick increments counter and therefor can do something different based on the current tick count.

I wouldn't do it that way - I'd just start a thread that loops and if a certain amount of time has gone by, it executes something...

It sounded like using a counter with Handler.PostDelayed() and using a main loop that check for amount of time gone by are two different approaches. But your last reply seems to have combined the two approaches.

Robert Green
Robert Green's picture
Joined: 02/18/2009
User offline. Last seen 21 hours 34 min ago.
When running UI code from a

When running UI code from a different thread than the UI thread, you must put the code to execute into a runnable like this:

runOnUiThread(new Runnable() {
public void run() {
view.setText(text);
}
});

That puts the chunk of code to execute into the UI's work queue and works around the multithreading issue.

Robert Green
Robert Green's picture
Joined: 02/18/2009
User offline. Last seen 21 hours 34 min ago.
It's because the execution of

It's because the execution of code does not stop when you call mHandler.postDelayed(). What happens is this:

sText is set to "Step One"
t's text is set to sText ("Step One")
sText is set to "Step Two"
mHandler is told that in 3000 ms, mWaitRunnable should be executed
sText is set to "Step three"
mHandler is told that in 4000 ms, mWaitRunnable should be executed
... 3000 ms go by ....
mWaitRunnable is executed
... another 1000 ms go by (4000 from post time) ...
mWaitRunnable is executed

See how that works?

If you want to have a sequence of stuff happen, you have to increment a counter.

counter = 0;

tick() {
if (counter == 0) {
text = "first tick";
} else if (counter == 1) {
text = "second tick";
}
counter++;
}

Now each time you call tick(), it'll set the text to something different.
You can schedule it like this:
mHandler.postDelayed(tickRunnable, 1000);
mHandler.postDelayed(tickRunnable, 2000);
and so long as tickRunnable calls tick(), you'll get a tick in 1 second and another tick in a second after that. Each tick increments counter and therefor can do something different based on the current tick count.

does that make more sense?

I wouldn't do it that way - I'd just start a thread that loops and if a certain amount of time has gone by, it executes something, but that's the game programming side of me talking. I'm a fan of main loops.