Waiting For Soundpool To Load Before Moving On With Application
Solution 1:
You can have an elegant solution using Semaphore
, however try to avoid blocking onCreate()
, since the app will not be responsive, the code here is just to illustrate the use of Semaphore
publicclassSoundPoolExampleextendsActivityimplementsOnTouchListener {
publicstaticfinalSemaphoresemaphore=newSemaphore(0);
/** Called when the activity is first created. */@OverridepublicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
soundPool.setOnLoadCompleteListener(newOnLoadCompleteListener() {
@OverridepublicvoidonLoadComplete(SoundPool soundPool, int sampleId,
int status) {
semaphore.release();
}
});
soundID = soundPool.load(this, R.raw.sound1, 1);
// Will block since there is not permits (the semaphore is initialized with 0)// then will continue once semaphore.release(); is called i.e. after loading is finished
semaphore.acquire();
}
// rest of your class
}
Solution 2:
I think while using SoundPool, everyone should pay attention to two cases:
- SoundPool is deprecated for API+21, instead you should use
SoundPool.Builder
- If your SoundPool runs in the main thread, take it to another thread as it blocks your UI
Solution 3:
I tried iTech's semaphore solution and it didn't work. The application sits there waiting forever.
onCreate is called with the main thread. This thread creates the sound pool, starts the loading of the sound, and then parks itself waiting for the loading to complete. Seems like it should work, right?
After some investigation, I found that SoundPool has the undocumented behavior of always running onLoadComplete with the main thread using the Looper/Handler mechanism. Since the main thread is parked, it cannot receive that event, and the application hangs.
We can see how this works in the Android source code in SoundPool.java:
publicvoidsetOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
{
synchronized(mLock) {
if (listener != null) {
// setup message handlerLooper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = newEventHandler(mProxy, looper);
} elseif ((looper = Looper.getMainLooper()) != null) {
mEventHandler = newEventHandler(mProxy, looper);
} else {
mEventHandler = null;
}
} else {
mEventHandler = null;
}
mOnLoadCompleteListener = listener;
}
}
This shows that it uses the main thread looper by default unless you have a looper of your own set up.
The simplest solution is to run the entire sound loading procedure in a new thread and return from the main thread immediately. You can then use the main thread to show a loading screen if necessary. When the sound is loaded, the main thread is free to receive the onLoadComplete call and signal the sound loading thread. The sound loading thread can wait for that signal with a semaphore or wait/notify on a shared Object.
Solution 4:
This is a bit tricky to solve correctly. I handle similar scenario in the following way. Structure-wise, it looks like this:
handler
is instantiated on the main thread.handleMessage
is implemented, which takes care of a custom notification message of all sound samples being loaded.- A
SoundPool
is instantiated on the main thread. New thread is started which:
4.1 Sets up
SoundPool.OnLoadCompleteListener
which sends the custom notification message using the instantiated handler from the main thread.4.2 Uses instantiated soundPool to issue async loading of sound samples (
load
method ofSoundPool
class)
Then, there is a variable which holds a state of sound samples loading. It can have three states: not loading, loading, loaded.
The state of this variable is set to loading in the thread from the 4th step above.
The state of this variable is set to loaded in the handleMessage
when the custom notification message arrives.
This state variable is checked before any sound is played. If no sound samples are loaded yet, a semi-fullscreen dialog is shown stating that the sound samples are being loaded and automatically disappears when the loading is done. The dialog can't be dismissed by a user. That is does programmatically from handleMessage
method by calling the dismiss
method of that dialog, if shown. The dialog has a nice progress bar animation. This is specific to my app and it can be done differently for other apps.
You might ask why it is done so complicated. The thing is that the whole Android GUI API is an event machine and by definition, your own code running on the main thread should not execute any time consuming procedure because that would render the whole GUI non-responsive. In fact, when Android detects such cases, it logs them into logcat. For example, if I had used a semaphore, there would be no progress bar animation because the main GUI thread would be blocked. Using a semaphore this way just exhibits the fact of not understanding Android GUI architecture and it can easily cause an app to hang if something goes wrong with a part which releases the semaphore.
Hope this helps.
Post a Comment for "Waiting For Soundpool To Load Before Moving On With Application"