Android 2.2以降用のシンプルなビデオ録画アプリを開発していますが、タイマースレッドを期待どおりに機能させるのに問題があります。コードは以下のとおりです。手順は次のとおりです。
- ユーザーが録画開始ボタンを押すと、録画が開始され、fightTimer.start()メソッドが呼び出されます。
- スレッドの実行を開始するtimer.start()メソッドを呼び出します。timerはfightTimer内のスレッドオブジェクトです
- ユーザーが停止ボタンをクリックすると、fightTimerメソッドstop()が呼び出されます。そこで、フラグstopTimer = trueを設定して、スレッドメソッドの実行を停止し、timer.wait()メソッドを呼び出して、スレッドが待機するようにします。
- ユーザーが[記録の開始]ボタンをクリックすると、fightTimer.start()メソッドが再度呼び出されます。タイマーはすでに開始されているため、スレッドtimer.start()を呼び出しますが、例外をスローします。
- 例外をキャッチし、fightTimer.restart()メソッドを呼び出します。このメソッドは、ステップ3でフラグstopTimerをtrueに設定したので、stopTimer=trueになります。タイマースレッドはまだrun()メソッド内で待機しています
- 次に、timer.notify()を呼び出して、タイマーにもう待つ必要がなく、実行を継続できることを通知します。
- 今、私はタイマースレッドが再び実行を開始することを期待していましたが、何らかの理由でこの時点で実行はnotify()(restart())を呼び出した同じメソッドの開始にジャンプし、フラグstopTimer = falseを設定してから、タイマースレッドに通知しようとしますまた。実行時例外をスローし、それで終了します。
スレッドの同期全体についての私の理解は正しくないと思いますので、誰かが私がどこで失敗したかを指摘できれば、それは素晴らしいことです。以下のFightTimerisのコードと私はlogCatに出力情報さえも取得していません。私が言ったように、なぜこれが起こっているのか、そしてそれを修正する方法がわからないので、どんな助けでも大いに感謝されるでしょう。
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;
}
}
restart()メソッドの編集されたコメントも参照してください。以下は、restart()メソッドが終了したときのコールスタックです。さらに情報が必要な場合はお知らせください。
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)