2012-08-11 19 views
6

Uso questo SoundManager standard. Funziona bene su tutti i miei dispositivi, ma sul mercato solo di tanto in tanto ottengo questi erroriOccasional NullPointerException in SoundManager

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

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

Ecco il codice:

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; 

    } 
} 

Questa è una chiamata per la pulizia che è in inizio attività:

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

Qualcuno sa che cosa potrebbe causare questi errori rari occasionali e come prevenirli? Questo succede in tutte le mie app che usano questo SoundManager ... Anche un po 'di speculazione potrebbe aiutare.

+0

Se si è in grado di riprodurre gli errori, 'Log' i valori di' mSoundPool', 'mSoundPoolMap' e' mAudioManager' in entrambi i metodi 'playSound' e' cleanup'. Uno di questi è destinato a essere nullo. Suppongo, tuttavia, che sia in qualche circostanza in cui qualcosa viene chiamato prima che esista 'initSounds'. – Eric

+0

Grazie, Eric. Non sono in grado di riprodurre l'errore, mai sui miei dispositivi, altrimenti sarebbe molto più facile trovarne la causa. – Lumis

+1

potresti segnare le linee 87 e 107? – WarrenFaith

risposta

3

Quando si inizializza SoundManager utilizzare il contesto dell'applicazione. Potresti avere problemi a spostarti tra le attività. Se SoundManager è più lungo della tua attività. Puoi persino inizializzare nella tua applicazione.

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

Sono anche d'accordo con WarrenFaith. Le uniche statiche dovrebbero essere _instance e getInstance().

Inoltre, se si caricano i suoni nella classe Applicazione, non è necessario preoccuparsi della sincronizzazione.

Se ti aiuta puoi guardare il codice che uso. Si avvale della biblioteca OpenSL Soundpool da 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() ; 
    } 


} 

ancora un work in progress, ma ottiene il lavoro fatto.

+1

Grazie per queste buone informazioni ed esempi. Dice nella pagina openSl: "SoundPool sembra soffrire di crash sul Samsung Galaxy S2 (e probabilmente su altri dispositivi dual core)" Forse potrebbe anche essere la causa, dato che la maggior parte dei cellulari ora è in versione 2.3x. – Lumis

+0

Sai se OpenSL ha una latenza inferiore al normale SoundPool? Trovo che le app di strumenti musicali Android siano inutili (troppo lente per suonare al tocco). – Lumis

+0

Il problema SoundPool è molto più ampio di quanto la maggior parte della gente creda. Penso che tutti i telefoni dual core di pan di zenzero siano interessati. Sì, la latenza di OpenSL è molte volte migliore. – theJosh

3

C'è un po 'di confusione. Non si (e non si dovrebbe) utilizzare un modello Singleton con metodi e variabili statici (eccetto la variabile getInstance() e mInstance). Questo non ha senso.

si dovrebbe sbarazzarsi della statica e utilizzare la classe completamente come un Singleton per assicurarsi che nessuna variabile potrebbe essere nullo a causa di problemi di concorrenza (credo il problema nullo è il risultato di concorrenza)

Ecco la classe userei:

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

l'utilizzo è ora un po 'più lungo, ma dovrebbe rimuovere gli NPE casuali. Dovresti chiamare questo nella tua classe Application all'interno di onCreate().

SoundManager.getInstance().initSounds(context); 

Poi ovunque è necessario utilizzare la classe:

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

Aggiornamento:

Per rispondere a un commento:

Se si crea l'istanza nella domanda :: onCreate () sempre avere l'istanza intorno e con l'istanza anche la variabile interna. Due casi potrebbe accadere quando l'utente lascia l'applicazione:

  1. può essere distrutta ma che onCreate saranno chiamati nuovamente non appena l'utente inserisce l'applicazione nuovamente
  2. non succede nulla e l'istanza è ancora lì.

Quindi in entrambi i casi non si perderà mai l'istanza.

Solo perché altri potrebbero farlo in un modo specifico non fa in modo che sia quello giusto.

+0

Penso di averlo estratto da un sito web forse tuo in Sound Tutorial, ma posso vedere che ora è cambiato. Molte persone sembrano utilizzare un Sound Manager con variabili statiche che posso vedere su StackOverwlow. Il punto con variabili statiche è che possono conservare i valori quando qualcuno esce e torna all'app, quindi forse questa non è una buona cosa per Sound Manager inclusa l'istanza stessa ... – Lumis

+0

ha aggiornato la mia risposta con alcune spiegazioni – WarrenFaith

+0

Da cosa Ho letto, lasciando una variabile statica inizializzata potrebbe causare perdite di memoria poiché il processo potrebbe non essere eliminato quando l'applicazione viene distrutta. – Jochem

Problemi correlati