Below is a simplification of some code I have for a custom View
. It has a Runnable
for an animation, and a Runnable
for audio. The start()
function starts both the animation runnable and the audio runnable. Each runnable implements an inteface with a callback on completion开发者_运维技巧.
I need to know when both are finished, so I can call onBothRunnablesFinished()
.
public class Foo extends View {
RunnableA mRunnableA;
RunnableB mRunnableB;
// overrides of onCreate, onMeasure, onDraw, etc...
private void onBothRunnablesFinished() {
// Do stuff when both runnables are finished...
}
public void start() {
mRunnableA = new RunnableA();
mRunnableB = new RunnableB();
post(mRunnableA);
post(mRunnableB);
}
private class RunnableA implements Runnable, AnimationListener {
private MyAnimation mAnim;
private boolean mRunning = false;
public RunnableA() {
mAnim = new MyAnimation();
mAnim.setAnimationListener(this);
}
public boolean isRunning() {
return mRunning;
}
@Override
public void run() {
mRunning = true;
startAnimation(mAnim);
}
// Called when mAnim finishes
@Override
public void onAnimationEnd(Animation animation) {
mRunning = false;
// **WHAT IF THE OTHER RUNNABLE FINISHES NOW?**
if (mRunnableB.isRunning() == false) {
onBothRunnablesFinished();
}
}
}
private class RunnableB implements Runnable, OnCompletionListener {
private MyMediaPlayer mMediaPlayer;
private boolean mRunning = false;
public RunnableB() {
mMediaPlayer = MyMediaPlayer();
mMediaPlayer.setOnCompletionListener(this);
}
public boolean isRunning() {
return mRunning;
}
@Override
public void run() {
mRunning = true;
mMediaPlayer.start();
}
// Called when mMediaPlayer finishes
@Override
public void onCompletion(MediaPlayer mp) {
mRunning == false;
// **WHAT IF THE OTHER RUNNABLE FINISHES NOW?**
if (mRunnableA.isRunning() == false) {
onBothRunnablesFinished();
}
}
}
}
I've labeled a couple areas of interest with comments. What would happen if the audio and animation runnables finish at the same time? More specifically, could the callback of one runnable interrupt the callback of the other in the commented locations above? Is this possible??
I hope not, because then onBothRunnablesFinished
would be called twice. If this is the case, how can I resolve this issue?
I've deleted my other answer. Now that I think about it you don't even need Runnables since the animation and media player frameworks will use their own threads. However you still need to synchronize the call to onActivityFinished()
(formerly called onBothRunnablesFinished()) since the framework threads may finish simultaneously:
public class Foo extends View implements AnimationListener, OnCompletionListener {
private MyAnimation mAnim;
private MyMediaPlayer mMediaPlayer;
private boolean mIsOneActivityFinished = false;
synchronized private void onActivityFinished() {
if(!mIsOneActivityFinished) {
// The first activity is finished. Set the flag and return.
mIsOneActivityFinished = true;
return;
}
// Do stuff when both activities are finished...
}
public void start() {
mAnim = new MyAnimation();
mAnim.setAnimationListener(this);
startAnimation(mAnim);
mMediaPlayer = MyMediaPlayer();
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.start();
}
// Called when mAnim finishes
@Override
public void onAnimationEnd(Animation animation) {
onActivityFinished();
}
// Called when mMediaPlayer finishes
@Override
public void onCompletion(MediaPlayer mp) {
onActivityFinished();
}
}
Trust me this code will work and is way cleaner. Don't even give a thought to using Runnables as Android does the multi-threading for you.
Barry
you can use an AtomicInteger initialized to 2
and finish the run with
if(atomicInt.decrementAndGet()==0)onBothRunnablesFinished();
I would inject a POJO mutex object into both the runnable objects and use it to synchronize both the isRunning
and onCompletion
methods. Acutally, isRunning
does not need synchronization since it's only called from within the syncrhonized code in onCompletion
, but I would consider the synchronization semantics much clearer with it also synchronized.
Using RunnableA as a template (make the changes marked // completion
to both classes):
private class RunnableA implements Runnable, AnimationListener {
private MyAnimation mAnim;
private boolean mRunning = false;
private Object mExecutionMutex; // completion
public RunnableA(Object mtx) { // completion
mAnim = new MyAnimation();
mAnim.setAnimationListener(this);
mExecutionMutex=mtx;
}
public boolean isRunning() {
synchronized(mExecutionMutex) { // completion (Note: This is actually unnecessary)
return mRunning;
}
}
@Override
public void run() {
mRunning = true;
startAnimation(mAnim);
}
// Called when mAnim finishes
@Override
public void onAnimationEnd(Animation animation) {
synchronized(mExecutionMutex) { // completion
mRunning = false;
if(mRunnableB.isRunning() == false) {
onBothRunnablesFinished();
}
}
}
}
精彩评论