I’m developing simple video recording app for Android 2.2+ and having trouble with getting Timer thread to work as expected. Code is below and so steps are:
- when user pres start recording button, recording starts and fightTimer.start() method is called.
- it calls timer.start() method starting to run the thread. timer is the thread object inside of fightTimer
- when user clicks stop button fightTimer method stop() is called. There I set a flag stopTimer=true and so that stops the thread method from running and calls timer.wait() method so the thread waits
- when user clicks start recording button then fightTimer.start() method is called again. It calls threads timer.start() as the timer has already started however it throws exception.
- I catch exception and call fightTimer.restart() method. This method set flag stopTimer I set in step 3 to true so now we have stopTimer=true. timer thread is still waiting inside of run() method
- then it calls timer.notify() to let timer know that it doesn’t need to wait anymore and can continue running
- Now I was expecting the timer thread start running again but for some reason at this point execution jumps to beginning of the same method that called notify() (restart()) and sets the flag stopTimer=false, and then tries to notify timer thread again. It throws runtime exception and that’s where it ends.
I assume that my understanding of whole synchronisation of threads is not correct so if anyone could point out where I screw up that would be great. The code for FightTimeris below and I don’t even get any output information in logCat. Like I said any help will be much appreciated as I don’t understand why is this happening and how to fix it.
public class FightTimer extends SurfaceView implements Runnable, SurfaceHolder.Callback {
public Thread timer;
public SurfaceHolder surfaceHolder;
public Canvas canvas;
public int counter=0;
public volatile boolean stopTimer=false;
public volatile boolean pauseTimer=false;
public FightTimer(Context context)
{
super(context);
surfaceHolder=getHolder();
// this is necessary to make surfaceview transparent
surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
surfaceHolder.addCallback(this);
timer=new Thread(this);
}
public void start()
{
try
{
timer.start();
}
catch(IllegalThreadStateException ex)
{
reStart();
}
}
public synchronized void reStart()
{
// here the method is executed twice as I described in step 7
// after notify() it actually jumps back to stopTimer=false again and then exits the function. Then outside of this object I catch RuntimeException
stopTimer=false;
timer.notify();
}
public synchronized void pause()
{
pauseTimer=true;
}
public synchronized void resume()
{
pauseTimer=false;
timer.notify();
}
public void stop()
{
stopTimer=true;
}
public void run() {
TimeWatch timeWatch=TimeWatch.start();
Paint paint=new Paint();
paint.setColor(Color.RED);
paint.setTypeface(Typeface.create("Arial",Typeface.NORMAL));
paint.setTextSize(20);
while(true)
{
// this is to pause timer
try
{
if(pauseTimer)
{
synchronized(timer) {
while(pauseTimer)
timer.wait();
}
// TODO heres the code should be executed when timer is resumed eg.
// maybe calculate how timer should count now as it wasn't counting for a while etc
}
} catch(InterruptedException ex)
{
}
// this is to pause timer
try
{
if(stopTimer)
{
synchronized(timer) {
while(stopTimer)
timer.wait();
}
// TODO heres the code should be executed when timer is restarted
// maybe reset timer completely etc
timeWatch.reset();
}
} catch(InterruptedException ex)
{
}
canvas=surfaceHolder.lockCanvas();
// canvas might not exists at this point as we might be in activitis onStop() callback and stopping preview
// etc. so we need to check if so then we exit run function
if(canvas==null) return;
//canvas.drawARGB(0,255,255,255);
canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
long minutes=timeWatch.time(TimeUnit.SECONDS)/60;
canvas.drawText(counter+" "+minutes+":"+timeWatch.time(TimeUnit.SECONDS)%60,0,counter%60, paint);
counter++;
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
//Toast.makeText(getContext(), "Surface Changed", Toast.LENGTH_LONG).show();
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
//timer.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
// when surface is destroyed it means it cannot be displayed anymore and there is no canvas to draw
// meaning the run() method cannot draw anything and calls to surfaceHolder will throw exception
// so we need to stop thread here
// this will happen when activity is in onStop() callback and when is already invisible and we are going to
// remove the object anyway so we don't care what will happenn later and make it wait. All we need is stop
// run() from calling any other methods on canvas from surfaceHolder
Toast.makeText(getContext(), "Surface Destroyed", Toast.LENGTH_LONG).show();
}
public void setSurfaceHolder(SurfaceHolder surfaceHolder2) {
// TODO Auto-generated method stub
surfaceHolder=surfaceHolder2;
}
}
Please also see edited comment in restart() method. Below is call stack, when restart() method exited. Please let me know if any more information is needed.
DalvikVM[localhost:8754]
Thread [<1> main] (Suspended)
<VM does not provide monitor information>
MyFirstAppActivity.startRecording(View) line: 271
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 521
View$1.onClick(View) line: 2077
Button(View).performClick() line: 2461
View$PerformClick.run() line: 8888
ViewRoot(Handler).handleCallback(Message) line: 587
ViewRoot(Handler).dispatchMessage(Message) line: 92
Looper.loop() line: 123
ActivityThread.main(String[]) line: 4627
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 521
ZygoteInit$MethodAndArgsCaller.run() line: 858
ZygoteInit.main(String[]) line: 616
NativeStart.main(String[]) line: not available [native method]
Thread [<7> Binder Thread #2] (Running)
Thread [<6> Binder Thread #1] (Running)
Thread [<8> Binder Thread #3] (Running)
Thread [<9> Thread-9] (Running)
Methods reStart() and resume() must acquire monitor on the object timer before calling notify() on it. (Similar to how timer.wait() is being done in code above().)
Failure to do so results IllegalMonitorStateException on a JRE. And stacktrace posted points towards similar issue.