Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Occasional NullPointerException in SoundManager

Tags:

android

I use this standard SoundManager. It works fine on all my devices but on the market only now and then I get these errors

  1. NullPointerException in SoundManager.playSound(SoundManager.java:87)

  2. 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.

like image 600
Lumis Avatar asked Aug 11 '12 20:08

Lumis


2 Answers

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.

like image 184
theJosh Avatar answered Oct 23 '22 04:10

theJosh


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:

  1. it can be destroyed but than the onCreate will be called again as soon as the user enters the app again
  2. nothing happens and the instance is still there.

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.

like image 44
WarrenFaith Avatar answered Oct 23 '22 04:10

WarrenFaith