2012-08-11 18 views
6

Utilizo este SoundManager estándar. Funciona bien en todos mis dispositivos en el mercado, pero sólo de vez en cuando me siento estos erroresNullPointerException ocasional en SoundManager

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

  2. NullPointerException en SoundManager.cleanup (SoundManager .java: 107)

Aquí está el código:

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; 

    } 
} 

Este es un llamado para la limpieza que está en la actividad de inicio:

//REMOVE SOUND MEMORY ALLOCATION 
    @Override 
    public void onDestroy() 
     { 
      super.onDestroy(); 
      SoundManager.cleanup(); 
     } 

¿Alguien sabe qué podría estar causando estos errores raros ocasionales y cómo prevenirlos? Esto sucede en todas mis aplicaciones que usan este SoundManager ... Incluso un poco de especulación podría ayudar.

+0

Si puede reproducir los errores, 'Registre 'los valores de' mSoundPool', 'mSoundPoolMap', y' mAudioManager' en los métodos 'playSound' y' cleanup'. Uno de ellos será nulo. Sin embargo, supongo que es en alguna circunstancia donde se llama algo antes de 'initSounds'. – Eric

+0

Gracias Eric. No puedo reproducir el error, nunca en mis dispositivos, de lo contrario sería mucho más fácil encontrar la causa. – Lumis

+1

¿podría marcar las líneas 87 y 107? – WarrenFaith

Respuesta

3

Cuando inicialice su SoundManager, utilice el contexto de la aplicación. Podría tener problemas para moverse entre actividades. Si el SoundManager tiene más vida que tu actividad. Incluso puede inicializar en su aplicación.

public class MyAndroidApp extends Application { 
    @Override 
    public void onCreate() { 
     super.onCreate(); 
     SoundManager.initSounds(this); 
    } 
} 

También estoy de acuerdo con WarrenFaith. La única estática debe ser _instance y getInstance().

Además, si carga sus sonidos en la clase de aplicación, no tendrá que preocuparse por la sincronización.

Si te ayuda, puedes mirar el código que uso. Se hace uso de la biblioteca OpenSL soundpool de 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() ; 
    } 


} 

todavía un trabajo en progreso, pero hace el trabajo.

+1

Gracias por esta buena información y ejemplos. Dice en la página de openSl: "SoundPool parece sufrir bloqueos en el Samsung Galaxy S2 (y posiblemente en otros teléfonos con dos núcleos)" Quizás esa también podría ser la causa, ya que la mayoría de los móviles ahora están en la versión 2.3x. – Lumis

+0

¿Sabe si OpenSL tiene una latencia inferior a la de SoundPool normal? Encuentro que las aplicaciones de instrumentos musicales con Android son inútiles (demasiado lentas para tocar al tacto). – Lumis

+0

El problema de SoundPool es mucho más amplio de lo que la mayoría de la gente cree. Creo que todos los teléfonos con doble núcleo de pan de jengibre se ven afectados. Sí, la latencia de OpenSL es muchas veces mejor. – theJosh

3

Hay un poco de confusión. No utiliza (y no debería) un patrón Singleton con métodos y variables estáticos (excepto la variable getInstance() y mInstance). Esto no tiene sentido.

Usted debe deshacerse de la estática y el uso de la clase completamente como un producto único para asegurarse de que no hay variables podrían ser nulos debido a problemas de concurrencia (supongo que su problema nula es el resultado de la concurrencia)

Aquí está la clase que utilizaría:

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(); 
    } 
} 

El uso ahora es un poco más largo pero debería eliminar los NPE aleatorios. Debe llamar esto en su clase de Aplicación dentro del onCreate().

SoundManager.getInstance().initSounds(context); 

A continuación, siempre que se necesite utilizar la clase:

SoundManager.getInstance().playSound(index, volume); 
// or what ever you need 

Actualización:

Para responder a su comentario:

Si crea la instancia de la aplicación :: OnCreate () tendrá siempre tiene la instancia alrededor y con la instancia la variable interna, también. Dos casos pueden ocurrir cuando el usuario sale de la aplicación:

  1. que puede ser destruido, pero que el onCreate serán llamados de nuevo tan pronto como el usuario entra en la aplicación de nuevo
  2. no pasa nada y la instancia todavía está allí.

Por lo tanto, en ambos casos, nunca perderá la instancia.

El hecho de que otros lo hagan de una manera específica no lo hace de la manera correcta.

+0

Creo que lo he recogido de un sitio web, tal vez el tuyo en Sound Tutorial, pero puedo ver que ahora está cambiado. Mucha gente parece usar un Administrador de sonido con variables estáticas Puedo verlo en StackOverwlow. El punto con variables estáticas es que pueden preservar los valores cuando alguien se va y regresa a la aplicación, así que quizás esto no sea bueno para Sound Manager, incluida la instancia en sí ... – Lumis

+0

actualizó mi respuesta con algunas explicaciones – WarrenFaith

+0

De qué Lo he leído, dejando una variable estática inicializada puede dar como resultado pérdidas de memoria ya que el proceso no se puede eliminar cuando se destruye la aplicación. – Jochem

Cuestiones relacionadas