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:
Zoom its main View in (a TextView);
After the view is fully zoomed in, emit a sound;
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:
Run MediaPlayer from a Handler().postDelayed, and
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
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:
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;
AsyncTask is limited to a single pair of events, and cannot be reused within an application;
Looper and Handler looked promising for awhile, but apparently Looper does not wait for events to finish before dequeuing further ones;
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;
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;
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;
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:
How do I make Java wait for a method to finish before continuing?
How do I do something when an animation finishes?
Android: trying to add a wait in between two animations, getting Object not locked by thread before wait() error
Waiting for a function to finish execution and using the results
Android how to wait for code to finish before continuing
Android - AnimatorSet, Object Animator - Bounce animation chain is amalgamating?
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
Related
I have a question about the Android Studio:
I have an Image view. Let's call it ImageView. I call the method setOnCLickListener on this ImageView with a listener new View.OnclickListener().
In the method onClick() of this listener, I change an attribute (the color, the image itself...) of this imageView. For example, I change the image with imageView.setImageResource(R.drawable.new_image). I then wait for 10 sec with Thread.sleep(10000). I then set back the previous image with imageView.setImageResource(R.drawable.previous_image).
Here is my question:
I expect the ImageView to change its image for 10 sec and then have the previous image back. However, I see no change on this ImageView. I only see the first image...Could someone explain me why?
I hope I was clear enough...Thank you in advance for your help :) !!
R.id.drawable.previous_image ?. It should be R.drawable.previous_image
First of all, you should read the Processes and Threads guide to understand how threading works on Android.
If you're done with with that, you'll understand that what you do is really bad because you freeze the main (UI) thread which means the application will not respond to any events until the 10 seconds pass. This also results in an ANR (Application Not Responding) dialog which is pretty bad UX.
You basically need to delegate the waiting period to another thread (or a queue at least), then when the time comes, go back to the UI thread and set whatever view attribute you want. There are many ways to achieve this, you should read Communicating with the UI Thread for more details. Here's just a quick sample:
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
#Override
public void run() {
// write here what you want to happen in 10 seconds on the UI thread
}
}, 10000); // 10s = 10,000ms
Here are some notes though:
Save the Runnable you create here to a variable because if the user navigates away from this screen within the 10 seconds, it will still run the code inside, which might result in various exceptions. In this case you need to remove that Runnable from the handler by calling handler.removeCallbacks(runnable); when the user leaves the screen.
Don't create a new Handler instance every time the click event happens. Create it in the Activity's onCreate(...) method and use that instance in the rest of the screen.
You could create the Handler without the Looper parameter, which would create the handler for the current thread's Looper and that would be fine now since it's being created on the UI thread. However, I decided it's better to show you the safer way since you might end up doing something similar on a background thread that could result in unexpected behavior if you don't understand the threading yet.
I am writing an android game and here is what i have:
Game loop is implemented as:
I have made a custom view which extends the view.Then inside my onDraw() method, i call invalidate();
MainActivity:
Here i make an instance of a custom view and use setContentView() to set that as my current view.
I also use OnTouch() method to get touch events
Everything was working fine and smoothly till i did this:
I made a new class Graphthread and inside the run method, i created a loop.
public class Graphthread extends Thread
{
#Override
public void run() {
while(true)
{
}
}
}
Then i created an instance of this in my MainActivity
Graphthread gth = new Graphthread();
and used gth.start() in my onCreate method.
Now following happened:
The game did not run smoothly.Or i should say it was like..run for sometime..freeze for a few milliseconds...run again ...freeze again and so on.
What is happening?
Slight jitters like that sounds like garbage collection. If your background thread (or something else) is consuming a very large amount of memory, the GC may need to run more often then expected. That would cause momentary jitters such as you described.
Try more suitable approach for games: use SurfaceView and background thread to draw on it. This should free you from stuttering, cased by irregular message processing by main thread.
onDraw hack, from my point of view, suitable only if you want to animate something inside a regular app (not game), and animation framework has little use for you. Panning and scaling image for example.
I have a MediaPlayer (.wav file) that sometimes needs to be repeated in rapid succession. If it's already playing, I restart it:
if (player.isPlaying()) {
player.pause();
player.seekTo(0);
}
player.start();
The problem is that when the MediaPlayer is interrupted at some random position, there's often a tiny but noticeable scratchy noise at the end.
One solution I've tried is to create an array of MediaPlayers that load the same wav file, cycle through them, never interrupt, and ignore the call if the current
item is already playing (which happens rarely, and the missed call isn't noticed in the general barrage anyway).
MediaPlayer player = players.get(current);
if (!player.isPlaying())
player.start();
if((++current) >= players.size())
current = 0;
This gets rid of the noise, but the solution is kind of ugly. Is there a better way?
The noise is probably the waveform being cut mid-cycle. It can possibly be fixed with calls to setVolume before stopping and starting. Having multiple MediaPlayers isn't a terrible idea, but you may want to have a look at SoundPool.
I'm trying to accomplish something very simple. First, load my layout (main.xml). Then wait 1 second, modify an image, wait 1 second and modify it to a third image. (My end goal is more complex, of course, but I can't even get this to work).
Basically, I get a black screen when the app loads. It stays that way until all the waiting is over, then it shows the final image. Here's my code:
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageCard[0] = (ImageView)findViewById(R.id.imageView11);
Thread.sleep(1000);
ImageCard[0].setImageDrawable(getResources().getDrawable(R.drawable.secondimage));
Thread.sleep(1000);
ImageCard[0].setImageDrawable(getResources().getDrawable(R.drawable.thirdimage));
ImageCard[0] is the image I'm trying to change. It loads in main.xml and shows fine if I remove the rest of the code.
If I change the sleep time to 5000, it then takes 10 seconds before it finally comes away from the black screen and shows the third image. This is how I know it's not just loading slow, but that it's actually waiting.
It's like it's not running sequentially. I'm no java expert, so I'm assuming I'm doing something dumb...
Thanks for any help!
I think you are blocking the UI Thead. Try Handler.postDelayed on a static Handler object.
Ok heres your problem, you can never do a sleep(...) when you are in the UIThread. The UIThread is never suppose to be locked up, it causes a lot of very bad things to happen in android. But there is a very easy way around it, just get off the UIThread and hop back on it when you need to. Heres what i would recommend:
public void onCreate(...)
{
super.onCreate(...);
myActivity.setContentView(R.layout.main);
new Thread(this).start();
}
public void run()
{
try
{
changeLayout(R.layout.main2);
Thread.sleep(5000);
changeLayout(R.layout.main3);
Thread.sleep(10000)
changeLayout(R.layout.main4);
}catch(Exception e){}
}
public void changeLayout(int id)
{
this.id = id;
myActivity.post(new Runnable()
{
public void run()
{
myActivity.setContentView(id);
}
});
}
private int id;
Of course with this example your class must implement Runnable to work. Only the UIThread can access the UI, no other thread can. Thats why you have to hop on and off the UIThread. Hope this worked!
Try adding ImageCard[0].invalidate() when you want it to draw.
I think Hovercraft Full of Eels is pointing you in the right direction. Essentially, you're not doing any multi-threading, you're telling the main thread to wait which means that it never completes the drawing. I'm not sure about Android, but Swing uses double-buffering by default (to avoid screen flashes), but that means that what is drawn, is actually drawn on to a buffer, not the window itself which is why you don't see anything. You could try disabling the double buffering (which Android is likely using) but that could cause other issues.
You might want to actually do multi-threading, or, I'm sure Android likely has a Timer component. If it does, I'd suggest you use it over Thread.sleep or actual multi-threading. Using a Timer you can have it fire an event after one second. That event will execute the other code.
do you have that code in constructor or in init() function? if yes, draw just the first picture and the Thread.sleep() function move after the place which the constructor or the init() function was called from.
then call repaint() function or something.
I wanted to rewrite my simple game engine to run on Android and I am wondering how can I synchronize two running threads. Right now I have the following:
Runner is the main activity, entry point for this game;
CanvasView is just a canvas on which drawing is being made;
GameWorld is - as the name suggests - class which stores the current information about the state of the game. For now, lets just say that it also contains a Level.
GameLoop is a separate thread which is updating the game state;
CanvasThread is a separate thread, which is being run to draw the current Level on CanvasView.
As the level is just a simple array, CanvasThread just iterates through the array and draws it on screen. And I have few questions regarding this:
is there a possibility to run the onDraw() method of CanvasThread on demand? In current state of the engine it is just being relaunched when the execution of previous one is finished.
I want to implement some kind of three way handshake between GameLoop and CanvasThread, something similar to:
GameLoop -> CanvasThread: please stop updating
Canvas -> GameLoop: ok, stopped
GameLoop -> CanvasThread: ok, you may resume.
What is the best way to do so? I am a total Java / Android newbie so my way of setting the engine is most probably not the best / optimal one. If you have any suggestions to the design, I will gladly appreciate them.
Thank you.
PS: If I had violated all best practices while creating the diagram above, please forgive me.
Android has an easy way to handle thread communication. You can use a Looper and a Handler to send messages, or complete Runnables, between the Threads.
Look at Android Guts: Intro to Loopers and Handlers on mindtherobot.com for an introduction.
You can send an "empty" message to signal something, or supply some arguments. You can send the message immediately, or with some delay.
If you design your game loop by sending messages to itself, it would be easy to inject messages from other threads.
What I created long long time ago. Maybe it helps a little
http://code.google.com/p/candroidengine/
I believe you'll find it a lot easier if they don't communicate.
Instead, put a couple constraints on to keep yourself sane:
Make a very tight game loop. If you're going to be updating your canvas at, let's say, 30 fps (about 33 ms) make your game loop take no longer than 10 or 20 ms.
Put a lock on your game loop and canvas update. Now, each one will wait for the other to finish before doing their thing. You won't end up with a canvas that has the half the screen representing before the loop and the other half of the screen after the loop.
Example:
class GameLoop extends Thread {
public void run() {
while(true) {
synchronized(theGameWorldObject) {
// update game info here
}
}
}
}
class Canvas {
updateCanvas() { // or whatever you call it
synchronized(theGameWorldObject) {
// update canvas here
}
}
}