My Android application (minSdkLevel: 2.2, targetSdkLevel: 3.0, testing on Nexus 7 with Android 4.2) does the following in the loading phase:
SoundPool
and registers an OnLoadCompleteListener
on itSoundPool
calls my callback method whenever a sound is loaded.SoundPool.release()
is calledrelease()
was already called and a previously started sound loading completes, i.e. the system calls my callback, then my application detects that that my SoundPool
is already null
and ignores the callback, so it is safe).Testing on a Nexus 7, the following error occured at 210ms after SoundPool.release()
was called (logcat):
I.e. the event order was the following probably:
SoundPool.load(...)
callsSoundPool.release()
was called and SoundPool
set to null
I checked the Android source code and the JNI code for SoundPool.release()
indeed deletes the SoundPool
in JNI via DeleteGlobalRef
. It uses a weak reference to store the SoundPool
reference on Java level.
I cannot reproduce the bug, probably because I can't reproduce the exact conditions due to nondeterminism. The system should correctly handle that a SoundPool
might be released while it loads sounds asynchronously, but it seems under some rare circumstances, a bug appears. (Note that my code releases SoundPool
exactly once after a nullcheck, so the error is not in my code for sure).
Any idea about why exactly does such an error occur in Android? Can my above suspicions be correct, in theory? How can I protect my application against this Android bug? Maybe I can try setting in a boolean that the SoundPool
should be released, wait until all callbacks returned, and then release it (based on this boolean) in the last callback? I can't think of any other workaround, but I would like to be sure that it will work, at least.
As you say, this appears to be a bug in Android due to a race condition. As requested by user CommonsWare I opened a bug with the backtrace: http://code.google.com/p/android/issues/detail?id=53043
The offending code appears to be in Android's media/jni/soundpool/SoundPool.cpp which does:
void SoundPool::notify(SoundPoolEvent event)
{
Mutex::Autolock lock(&mCallbackLock);
if (mCallback != NULL) {
mCallback(event, this, mUserData);
}
}
Looks like mCallback is a Java object that can be deleted by the Garbage Collector, so when notify is called it tries to reference this object and the "accessed deleted global reference" crash happens.