2012-08-11 83 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