2012-08-11 53 views
6

我使用这个标准的SoundManager。它工作正常,我所有的设备,但市场上唯一一个现在,然后我得到这些错误在SoundManager.playSound(SoundManager.java:87)SoundManager中的偶尔NullPointerException

  1. NullPointerException异常

  2. 的NullPointerException在SoundManager.cleanup(SoundManager类的.java:107)

下面是代码:

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; 

    } 
} 

这是清理通话是在开始活动:

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

有谁知道这可能是导致这些偶然的罕见错误,以及如何预防呢?这发生在我使用SoundManager的所有应用程序中......即使有点炒作也可能有所帮助。

+0

如果您能够重现错误,请在'playSound'和'cleanup'方法中记录'mSoundPool','mSoundPoolMap'和'mAudioManager'的值。其中一个肯定是空的。不过,我猜想,在某些情况下,在'initSounds'之前调用了某些东西。 – Eric 2012-08-11 22:00:15

+0

谢谢埃里克。我无法在我的设备上重现错误,否则找到原因会更容易。 – Lumis 2012-08-15 09:58:11

+1

你能标记线条87和107吗? – WarrenFaith 2012-08-15 10:02:37

回答

3

初始化SoundManager时,使用应用程序上下文。活动之间可能会有问题。如果SoundManager比您的活动寿命更长。你甚至可以在你的应用程序中初始化。

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

我也同意WarrenFaith。唯一的静态应该是_instance和getInstance()。

另外,如果您在Application类中加载声音,则不需要担心同步。

如果它可以帮助你看看我使用的代码。它利用OpenSL的Soundpool库从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() ; 
    } 


} 

工作还在进行中,但能够完成任务。

+1

感谢这个很好的信息和例子。它在openSl页面上说:“SoundPool似乎受到三星Galaxy S2(可能还有其他双核手机)的崩溃困扰。”也许这也可能是原因,因为大多数手机现在都在2.3x版本上。 – Lumis 2012-08-16 10:02:42

+0

你知道OpenSL的延迟低于正常的SoundPool吗?我发现Android乐器应用程序是无用的(触摸播放速度太慢)。 – Lumis 2012-08-16 10:05:35

+0

SoundPool问题比大多数人认为的要广泛得多。我认为所有双核心姜饼手机都受到影响。是的,OpenSL的延迟要好很多倍。 – theJosh 2012-08-16 15:27:46

3

有一点混淆。你不(也不应该)使用带有静态方法和变量的Singleton模式(getInstance()和mInstance变量除外)。这没有道理。

你应该摆脱静态的,完全使用类作为一个单身,以确保没有变量可能因并发性问题为空(我猜你空的问题是并发的结果)

这里我会使用的类:

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

用法现在有点长,但应该删除随机的NPE。你应该在onCreate()的Application类中调用它。

SoundManager.getInstance().initSounds(context); 

然后你需要的地方使用类:

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

更新:

为了回答您的评论:如果您在应用程序创建实例

:在OnCreate( )你会总是有实例,实例也是内部变量。当用户离开该应用程序两种情况可能发生:

  1. 它可以被毁灭,但不是的onCreate将再次只要用户进入应用程序再次
  2. 没有任何反应和实例仍然存在被调用。

因此,在这两种情况下,您都不会丢失实例。

仅仅因为其他人可能以特定的方式做到这一点并不会使这种方式成为正确的方式。

+0

我想我已经从一个网站也许你在Sound Tutorial中选择了这个,但我可以看到现在已经改变了。很多人似乎都使用带有静态变量的声音管理器,我可以在StackOverwlow中看到它。带有静态变量的一点是,当有人离开并回到应用程序时,它们可以保留这些值,所以对于包括实例本身的声音管理器来说,这可能不是一件好事... – Lumis 2012-08-15 10:34:29

+0

更新了我的答案并提供了一些解释 – WarrenFaith 2012-08-15 10:57:56

+0

从什么我读过,留下一个初始化的静态变量可能会导致内存泄漏,因为当您的应用程序被销毁时,进程可能不会被终止。 – Jochem 2012-08-15 11:54:39