Problem
An app that creates a Titanium.Media.Sound object and calls the the play method on it can cause a java.lang.IllegalStateException exception to occur when the main window closes and the app exits. The exception seems to rarely occur in apps that are light on resources, but an app using a lot of memory/resources might crash 1 out of 4 or 5 times it is executed. Output during the crash is:
05-10 17:47:20.445: W/dalvikvm(2004): threadid=28: thread exiting with uncaught exception (group=0xb1a18ba8)
05-10 17:47:20.475: E/TiApplication(2004): (Timer-28) [46093,46093] Sending event: exception on thread: Timer-28 msg:java.lang.IllegalStateException; Titanium 3.2.3,2014/04/22 10:17,b958a70
05-10 17:47:20.475: E/TiApplication(2004): java.lang.IllegalStateException
05-10 17:47:20.475: E/TiApplication(2004): at android.media.MediaPlayer.isPlaying(Native Method)
05-10 17:47:20.475: E/TiApplication(2004): at ti.modules.titanium.media.TiSound$2.run(TiSound.java:516)
05-10 17:47:20.475: E/TiApplication(2004): at java.util.Timer$TimerImpl.run(Timer.java:284)
05-10 17:47:20.495: E/AndroidRuntime(2004): FATAL EXCEPTION: Timer-28
05-10 17:47:20.495: E/AndroidRuntime(2004): Process: com.plezzus.magnoids, PID: 2004
05-10 17:47:20.495: E/AndroidRuntime(2004): java.lang.IllegalStateException
05-10 17:47:20.495: E/AndroidRuntime(2004): at android.media.MediaPlayer.isPlaying(Native Method)
05-10 17:47:20.495: E/AndroidRuntime(2004): at ti.modules.titanium.media.TiSound$2.run(TiSound.java:516)
05-10 17:47:20.495: E/AndroidRuntime(2004): at java.util.Timer$TimerImpl.run(Timer.java:284)
Test case
The exception seems to be caused by a race condition that's hard to reproduce... I've located the source of the problem at the onDestroy() method at line 534 in TiSound.java as the cause of the error. To make a reliable test case, I patched TiSound.java by adding a delay after mp.release() at line 537. The delay makes it easier for the race condition to happen. I changed onDestroy at line 534 in TiSound.java to read:
public void onDestroy()
{
if (mp != null) {
mp.release();
Log.d(TAG, "begin delay");
for (int i = 0; i < 1000000000; ++i) {} // delay-causing no-op loop to help race condition to occur
Log.d(TAG, "end delay");
mp = null;
}
// TitaniumMedia clears out the references after onDestroy.
}
The test case app.js is:
var win = Titanium.UI.createWindow({
title:'Sound Bug',
fullscreen: true,
exitOnClose: true
});
var bell = Ti.Media.createSound({url: 'bell.mp3', preload: true, volume: 1});
win.addEventListener('open', function() {
setTimeout(function() {
bell.play();
setTimeout(function() {
win.close();
}, 100);
}, 1000);
});
win.open();
The test might have to be run multiple times to produce the exception. Also, bell.mp3 is attached.
Logs
Output of test case:
[DEBUG] TiSound: (main) [1985,1985] begin delay
[WARN] dalvikvm: threadid=14: thread exiting with uncaught exception (group=0xb1a77ba8)
[ERROR] TiApplication: (Timer-0) [870,2855] Sending event: exception on thread: Timer-0 msg:java.lang.IllegalStateException; Titanium 3.2.3,2014/06/09 10:38,b958a70
[ERROR] TiApplication: java.lang.IllegalStateException
[ERROR] TiApplication: at android.media.MediaPlayer.isPlaying(Native Method)
[ERROR] TiApplication: at ti.modules.titanium.media.TiSound$2.run(TiSound.java:516)
[ERROR] TiApplication: at java.util.Timer$TimerImpl.run(Timer.java:284)
[INFO] Process: Sending signal. PID: 1938 SIG: 9
Discussions
Q/A Link:
http://developer.appcelerator.com/question/174077/using-sound-objects-in-android-app-causes-java-exception-and-crash-on-app-exit
Current workaround: I've modifed the onDestroy() method in TiSound.java to read:
public void onDestroy()
{
stopProgressTimer(); // stop the timer so it won't cause exception
if (mp != null) {
mp.release();
mp = null;
}
// TitaniumMedia clears out the references after onDestroy.
}
David - Excellent description and great writeup. Much appreciated. Thanks for the workaround. However, this is a duplicate of TIMOB-16134. Please watch TIMOB-16134 for latest updates.