I use this standard SoundManager. It works fine on all my devices but on the market only now and then I get these errors
NullPointerException in SoundManager.playSound(SoundManager.java:87)
NullPointerException in SoundManager.cleanup(SoundManager.java:107)
Here is the code:
public class SoundManager {
private static SoundManager _instance;
private static SoundPool mSoundPool;
private static HashMap<Integer, Integer> mSoundPoolMap;
private static AudioManager mAudioManager;
private static Context mContext;
private SoundManager(){ }
static synchronized public SoundManager getInstance(){
if (_instance == null)
_instance = new SoundManager();
return _instance;
}
public static void initSounds(Context theContext){
mContext = theContext;
mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
mSoundPoolMap = new HashMap<Integer, Integer>();
mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
}
public static void addSound(int Index,int SoundID){
mSoundPoolMap.put(Index, mSoundPool.load(mContext, SoundID, 1));
}
public static void loadSounds(){
mSoundPoolMap.put(1, mSoundPool.load(mContext, R.raw.kick1, 1));
mSoundPoolMap.put(2, mSoundPool.load(mContext, R.raw.kick2, 1));
mSoundPoolMap.put(3, mSoundPool.load(mContext, R.raw.kick3, 1));
}
public static void playSound(int index, float volume){
**line 87:** float streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
streamVolume = streamVolume / mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mSoundPool.play(mSoundPoolMap.get(index), streamVolume*volume, streamVolume*volume, 1, 0, 1);
}
public static void stopSound(int index){
mSoundPool.stop(mSoundPoolMap.get(index));
}
public static void cleanup(){
**line 107:** mSoundPool.release();
mSoundPool = null;
mSoundPoolMap.clear();
mAudioManager.unloadSoundEffects();
_instance = null;
}
}
This is a call for cleanup which is in start activity:
//REMOVE SOUND MEMORY ALLOCATION
@Override
public void onDestroy()
{
super.onDestroy();
SoundManager.cleanup();
}
Does anybody know what could be causing these occasional rare errors and how to prevent them? This happens in all my apps which use this SoundManager... Even a bit od speculation could help.
When you initialize your SoundManager use the Application Context. You could be having issues move between activities. If the SoundManager is longer living than your activity. You can even initialize in your application.
public class MyAndroidApp extends Application {
@Override
public void onCreate() {
super.onCreate();
SoundManager.initSounds(this);
}
}
I also agree with WarrenFaith. The only statics should be _instance and getInstance().
Also if you load your sounds in the Application class you won't need to worry about synchronization.
If it helps you can look at the code I use. It makes use of the OpenSL SoundPool library from http://code.google.com/p/opensl-soundpool/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import com.kytomaki.openslsoundpool.JavaSoundPool;
import com.kytomaki.openslsoundpool.OpenSLSoundPool;
import com.kytomaki.openslsoundpool.SoundPoolIf;
final public class SoundManager
{
// Predetermined sound ID's
public static final int NO_SOUND = -1 ;
public static final int WINNER = -2 ;
// Tag for logging
protected static final String TAG = "SoundManager" ;
/** Used to load and play sounds **/
private Context context ;
/** Sound can be disable from separate thread **/
private final AtomicBoolean useSound ;
// Sound Arrays
private final ArrayList<Integer> winningSounds ;
private final SoundPoolIf soundPool ;
private final HashMap<Integer, Integer> soundPoolMap ;
private final AudioManager audioManager ;
/** Singleton object for sound play back **/
private static SoundManager soundManagerInstance ;
private static final int USE_SOUNDPOOL = 1 ;
private static final int USE_OPENSL = 2 ;
private static int use = USE_SOUNDPOOL ;
/**
* Private Method to create a new SoundManager<br>
* This is a Singleton Object
* @param context Should be the Application Context
*/
private SoundManager( final Context context )
{
setContext( context ) ;
useSound = new AtomicBoolean( true ) ;
audioManager = (AudioManager) context.getSystemService( Context.AUDIO_SERVICE ) ;
soundPoolMap = new HashMap<Integer, Integer>() ;
winningSounds = new ArrayList<Integer>() ;
if ( use == USE_OPENSL )
{
soundPool = new OpenSLSoundPool( 2, OpenSLSoundPool.RATE_44_1, OpenSLSoundPool.FORMAT_16, 1) ;
} else {
soundPool = new JavaSoundPool( 2 ) ;
}
}
/**
* Must be called before using<br>
* Best to initialize in Application Class
* @param context
*/
public static void initSoundManager( final Context context )
{
if ( soundManagerInstance == null )
{
soundManagerInstance = new SoundManager( context ) ;
}
else
{
throw new UnsupportedOperationException( "Sound manager has already been created" ) ;
}
}
/**
* Overloaded method to allow use of OpenSL
* @param context
* @param useOpenSL
*/
public static void initSoundManager( final Context context, final boolean useOpenSL){
if(useOpenSL){
use = USE_OPENSL;
}
initSoundManager(context);
}
/**
* Must initialize first with {@link SoundManager#initSoundManager(Context)}
* @return instance of SoundManager
*/
public static SoundManager getSoundManagerInstance()
{
if ( soundManagerInstance != null )
{
return soundManagerInstance ;
}
else
{
throw new UnsupportedOperationException( "SoundManager must be initalized" ) ;
}
}
/**
* Add a sound from an android resource file R.id.sound<br>
* To be played back with SoundManager.play(soundId)
* @param soundId
* @param soundResourceId
*/
public void addSound( final int soundId, final int soundResourceId )
{
soundPoolMap.put(soundId, soundPool.load(getContext(), soundResourceId));
}
/**
* Adds a winning sound from a resource to be played at random<br>
* Called by SoundManager.play(WINNER)
* @param soundResourceId
*/
public void addWinningSound( final int soundResourceId )
{
winningSounds.add( soundResourceId ) ;
}
/**
* Plays a sound first checking if sound is enabled
* @param soundToPlay soundId or WINNER to play random winning sound
*/
public synchronized void play( final int soundToPlay )
{
if ( isUseSound() )
{
switch ( soundToPlay )
{
case NO_SOUND :
break ;
case WINNER :
// Play a random winning sound using media player
final MediaPlayer mp ;
mp = MediaPlayer.create( getContext(), randomWinnerSound() ) ;
if ( mp != null )
{
mp.seekTo( 0 ) ;
mp.start() ;
}
break ;
default :
playSound( soundToPlay ) ;
break ;
}
}
}
/**
* Calls soundpool.play
* @param soundToPlay
*/
private void playSound( final int soundToPlay )
{
float streamVolume = audioManager.getStreamVolume( AudioManager.STREAM_MUSIC ) ;
streamVolume = streamVolume / audioManager.getStreamMaxVolume( AudioManager.STREAM_MUSIC ) ;
soundPool.play(soundPoolMap.get(soundToPlay), streamVolume);
}
/**
* @return random winning sound position
*/
private int randomWinnerSound()
{
final Random rand = new Random() ;
final int playNumber = rand.nextInt( winningSounds.size() ) ;
return winningSounds.get( playNumber ) ;
}
/**
* @param context the context to set
*/
private final void setContext( final Context context )
{
this.context = context ;
}
/**
* @return the context
*/
private final Context getContext()
{
return context ;
}
/**
* @param useSound false to disable sound
*/
public final void setUseSound( final boolean useSound )
{
this.useSound.set( useSound ) ;
}
/**
* @return the useSound
*/
public boolean isUseSound()
{
return useSound.get() ;
}
}
Still a work in progress but gets the job done.
There is a bit of a mix up. You don't (and shouldn't) use a Singleton pattern with static methods and variables (except the getInstance() and mInstance variable). This doesn't makes sense.
You should get rid of the statics and use the class completely as a singleton to make sure that no variables might be null because of concurrency problems (I guess your null issue is the result of concurrency)
Here is the class I would use:
public class SoundManager {
// syncronized creation of mInstance
private final static SoundManager mInstance = new SoundManager();
private SoundPool mSoundPool;
private HashMap<Integer, Integer> mSoundPoolMap;
private AudioManager mAudioManager;
private Context mContext;
private SoundManager() {}
public static SoundManager getInstance() {
return _instance;
}
public void initSounds(Context theContext) {
mContext = theContext;
mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
mSoundPoolMap = new HashMap<Integer, Integer>();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
public void addSound(int Index,int SoundID){
mSoundPoolMap.put(Index, mSoundPool.load(mContext, SoundID, 1));
}
public void loadSounds() {
mSoundPoolMap.put(1, mSoundPool.load(mContext, R.raw.kick1, 1));
mSoundPoolMap.put(2, mSoundPool.load(mContext, R.raw.kick2, 1));
mSoundPoolMap.put(3, mSoundPool.load(mContext, R.raw.kick3, 1));
}
public void playSound(int index, float volume){
float streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
streamVolume = streamVolume / mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mSoundPool.play(mSoundPoolMap.get(index), streamVolume*volume, streamVolume*volume, 1, 0, 1);
}
public void stopSound(int index) {
mSoundPool.stop(mSoundPoolMap.get(index));
}
// I wouldn't use this until I am extremely sure that I
// will never ever use the SoundManager again... so
// probably never. Let the SoundManager die when the application dies...
public void cleanup() {
mSoundPool.release();
mSoundPool = null;
mSoundPoolMap.clear();
mAudioManager.unloadSoundEffects();
}
}
The usage is now a bit longer but should remove the random NPEs. You should call this in your Application class inside the onCreate()
.
SoundManager.getInstance().initSounds(context);
Then wherever you need to use the class:
SoundManager.getInstance().playSound(index, volume);
// or what ever you need
Update:
To answer your comment:
If you create the instance in the Application::onCreate() you will always have the instance around and with the instance the internal variable, too. Two cases might happen when the user leaves the app:
So in both cases you will never loose the instance.
Just because others might do it in a specific way doesn't make this way the right one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With