0

For a more detailed version of this question, please see How to make MediaPlayer wait for ObjectAnimator (and generally control Android UI sequence)? Thanks. - Code-Read

I am attempting to build an Android app that starts up with this sequence:

  1. Zoom its main View in (a TextView);
  2. After the view is fully zoomed in, emit a sound;
  3. After the sound is emitted, execute the main program.

I first tried simply entering the zoom code (ObjectAnimator) before the sound code (MediaPlayer) in my program. But the code does not run in order of appearance. MediaPlayer plays in parallel with ObjectAnimator, regardless of code order. Why this is happening and how do I prevent it?

Attempting to cope, I have applied various timing and locking approaches to make MediaPlayer wait on ObjectAnimator. I've searched much here and elsewhere for solutions. So far the only ones I've found are:

  1. Run MediaPlayer from a Handler().postDelayed, and
  2. Have AnimatorListenerAdapter() launch MediaPlayer when the animation completes.

These both work, but (1) isn't precise as I must hard code the amount of time to wait (and manage this value manually), and (2) isn't general and the main application code still starts before MediaPlayer finishes. Numerous Answers recommend using AsyncTask to control Android UI sequences, but AsyncTask only applies to two tasks, waiting on one in a background thread, which really isn't appropriate with sequences of more than two stages.

Android - wait for animation to finish before continuing? is on this problem also. The accepted answer is AnimatorListenerAdaptor(), but the OP asks, That works for now but if I then want more lines of code to run after the animation has finished do I have to basically have my whole code in the onAnimationEnd method? and I agree - this does not seem reasonable.

With further testing, it appears my question might be simplified to: How do I make subsequent routines wait for ObjectAnimator?

The code below illustrates this latter question. Boolean waitLock is set to true. Then ObjectAnimator is launched. Then a while() loop waits for waitLock to be set to false by onAnimationEnd(). ObjectAnimator never completes, waitLock is never set to false, and the while() loops indefinitely. Why?

However, if waitLock is set to false initially, ObjectAnimator completes and the application exits normally. Why does ObjectAnimator complete in this case but not in the other?

public class MainActivity extends Activity {
private boolean waitLock;
View mainView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mainView = findViewById(R.id.mainView);
    waitLock = true;
    ObjectAnimator scalexAnim = ObjectAnimator.ofFloat(mainView, "scaleX", 0f, 1f);
    scalexAnim.setDuration(1500);
    scalexAnim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animator) {
            Log.w("sA", "onAnimationStart reached");
            super.onAnimationStart(animator);
        }
        @Override
        public void onAnimationEnd(Animator animation) {
            Log.w("sA", "onAnimationEnd reached");
            waitLock = false;
            super.onAnimationEnd(animation);
        }
    });

    Log.w("zI", "starting.."); zoomIn.start(); Log.w("zI", "started");

    while (waitLock == true) {
        SystemClock.sleep(1000);
        Log.w("while(waitLock)", "sleep 1000ms");
    }
    Log.w("onCreate", "exit");
}

Here is corresponding logcat output when waitLock is set to true:

07-02 13:34:14.850    9233-9233/com.code_read.sequentialcontroltest W/sA﹕ starting..
07-02 13:34:14.860    9233-9233/com.code_read.sequentialcontroltest W/sA﹕ onAnimationStart reached
07-02 13:34:14.860    9233-9233/com.code_read.sequentialcontroltest W/sA﹕ started
07-02 13:34:15.861    9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
07-02 13:34:16.862    9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
07-02 13:34:17.863    9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
07-02 13:34:18.864    9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
Community
  • 1
  • 1
CODE-REaD
  • 2,819
  • 3
  • 33
  • 60

1 Answers1

2

Since nobody has answered this, I'm posting what I've found so far. However, I consider this question still open. I certainly don't know all there is to know about Android or Java, and I'm hopeful there's a better answer.

My findings are:

  1. Generally, the Android platform and native libraries are not friendly toward synchronized programming. I found numerous questions (see links below) asking how to make various operations happen in a controlled sequence, and no very satisfactory general method of doing so;

  2. AsyncTask is limited to a single pair of events, and cannot be reused within an application;

  3. Looper and Handler looked promising for awhile, but apparently Looper does not wait for events to finish before dequeuing further ones;

  4. It appears that visual routines are more easily synchronized than audio routines, possibly because Android's visual library calls are more mature than audio. I found many comments indicating problems synchronizing Android's audio routines such as MediaPlayer or AudioTrack. Often the recommended solution where timing is important is to go with compiled C code and specialized libraries;

  5. Locking mechanisms are geared toward use by separate threads. My attempts to employ them as a way of controlling sequence within a single thread always failed with deadlock;

  6. Conversely, only code in the UI thread can alter the screen or generate a sound. This leads to a paradox to which the only solution I found was either tightly controlling the UI thread's Looper queue, or coding interleaves between audio and visual function calls;

  7. Where in the Android "life cycle" I placed my code made no difference. E.g., moving code to be executed later in time from onCreate() to onResume() made no difference.

Given the above, below is my coded solution to the original problem of animating a View, then emitting a sound, then running the rest of the program. It relies on callbacks, which cause a curious inversion: code earlier in the source file runs later in time. I find this disturbing from the perspective of legibility and manageability. I also dislike the generally convoluted structure, but so far this is coded as plainly as I know how.

(I suspect these sync issues were masked by single core CPUs. Now that most Android hardware is multicore, the OS may have some catching up to do. I would like to see something like a @Synchronous class that would designate sections of code to run serially in the order they appear. Or maybe a way to specify that sections of code run on a specific core only).

Links to a few of the many related questions:


My coded solution. Notice how the sequence of execution is reversed. rotateZoomin() runs first, then MediaPlayer(), then playTracks():

MediaPlayer.OnCompletionListener smComplete = new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mediaPlayer) {
        startupMediaPlayer.stop();
        startupMediaPlayer.reset();
        startupMediaPlayer.release();
        playTracks();    // This is third to execute (the rest of my application)
    }
};

startupMediaPlayer = MediaPlayer.create(this, R.raw.doorbell2mp3);
startupMediaPlayer.setOnCompletionListener(smComplete);

ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(mainView, "rotation", 1080f, 0f);
ObjectAnimator scalexAnim = ObjectAnimator.ofFloat(mainView, "scaleX", 0f, 1f);
ObjectAnimator scaleyAnim = ObjectAnimator.ofFloat(mainView, "scaleY", 0f, 1f);
AnimatorSet rotateZoomIn = new AnimatorSet();
rotateZoomIn.play(rotateAnim).with(scalexAnim);
rotateZoomIn.play(rotateAnim).with(scaleyAnim);
rotateZoomIn.setDuration(1500);
rotateZoomIn.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        startupMediaPlayer.start();  // This is second to execute
    }
});
rotateZoomIn.start();  // This is first to execute
halfer
  • 19,824
  • 17
  • 99
  • 186
CODE-REaD
  • 2,819
  • 3
  • 33
  • 60