在继续使用应用程序之前等待SoundPool加载

时间:2013-02-08 22:46:09

标签: android soundpool

我正在按照教程将SoundPool集成到我的应用程序中,这是教程中给出的代码:

package com.example.soundpoolexample;

import android.app.Activity;
import android.media.AudioManager;
import android.media.SoundPool;
import android.media.SoundPool.OnLoadCompleteListener;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class SoundPoolExample extends Activity implements OnTouchListener {
    private SoundPool soundPool;
    private int soundID;
    boolean loaded = false;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        View view = findViewById(R.id.textView1);
        view.setOnTouchListener(this);
        // Set the hardware buttons to control the music
        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
        // Load the sound
        soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
        soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId,
                    int status) {
                loaded = true;
            }
        });
        soundID = soundPool.load(this, R.raw.sound1, 1);

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // Getting the user sound settings
            AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
            float actualVolume = (float) audioManager
                    .getStreamVolume(AudioManager.STREAM_MUSIC);
            float maxVolume = (float) audioManager
                    .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
            float volume = actualVolume / maxVolume;
            // Is the sound loaded already?
            if (loaded) {
                soundPool.play(soundID, volume, volume, 1, 0, 1f);
                Log.e("Test", "Played sound");
            }
        }
        return false;
    }
}

问题是,我想确保应用程序仅在SoundPool成功加载后才向前移动,而不必在每次播放声音之前检查它是否已加载。我该怎么做?

我确信这是一个非常糟糕的解决方案,但是在我在onCreate中加载声音文件之后会有类似的工作吗?:

...

    soundID = soundPool.load(this, R.raw.sound1, 1);
    while (!loaded) {}

...

我相信有更好的方法。

4 个答案:

答案 0 :(得分:1)

您可以使用Semaphore获得优雅的解决方案,但请尝试避免阻止onCreate(),因为该应用无法响应,此处的代码仅用于说明使用Semaphore

public class SoundPoolExample extends Activity implements OnTouchListener {

public static final Semaphore semaphore = new Semaphore(0);

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
        @Override
        public void onLoadComplete(SoundPool soundPool, int sampleId,
                int status) {

            semaphore.release();
        }
    });
    soundID = soundPool.load(this, R.raw.sound1, 1);

    // Will block since there is not permits (the semaphore is initialized with 0)
    // then will continue once semaphore.release(); is called i.e. after loading is finished
    semaphore.acquire();
} 

// rest of your class

}

答案 1 :(得分:1)

我认为在使用SoundPool时,每个人都应该注意两种情况:

  1. 对于API + 21,不推荐使用SoundPool,而应使用SoundPool.Builder
  2. 如果您的SoundPool在主线程中运行,请将其带到另一个线程,因为它会阻止您的UI

答案 2 :(得分:0)

我尝试过iTech的信号量解决方案,但它没有用。应用程序坐在那里永远等待。

使用主线程调用onCreate。此线程创建声音池,开始加载声音,然后停放自己等待加载完成。好像它应该有效,对吗?

经过一番调查后,我发现SoundPool有一个未记录的行为,即使用Looper / Handler机制,主线程始终运行onLoadComplete。由于主线程已停止,因此无法接收该事件,应用程序将挂起。

我们可以在SoundPool.java中的Android源代码中看到它是如何工作的:

public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
{
    synchronized(mLock) {
        if (listener != null) {
            // setup message handler
            Looper looper;
            if ((looper = Looper.myLooper()) != null) {
                mEventHandler = new EventHandler(mProxy, looper);
            } else if ((looper = Looper.getMainLooper()) != null) {
                mEventHandler = new EventHandler(mProxy, looper);
            } else {
                mEventHandler = null;
            }
        } else {
            mEventHandler = null;
        }
        mOnLoadCompleteListener = listener;
    }
}

这表明它默认使用主线程循环器,除非你有自己设置的循环器。

最简单的解决方案是在新线程中运行整个声音加载过程并立即从主线程返回。然后,您可以根据需要使用主线程显示加载屏幕。加载声音后,主线程可以自由接收onLoadComplete调用并发出声音加载线程的信号。声音加载线程可以使用信号量等待该信号,或者在共享对象上等待/通知。

答案 3 :(得分:0)

正确解决这个问题有点棘手。我通过以下方式处理类似的情况。结构方面,它看起来像这样:

  1. handler在主线程上实例化。
  2. handleMessage已实施,负责处理所有正在加载的声音样本的自定义通知消息。
  3. 在主线程上实例化SoundPool
  4. 启动新线程:

    4.1设置SoundPool.OnLoadCompleteListener,它使用主线程中的实例化处理程序发送自定义通知消息。

    4.2使用实例化的soundPool发出声音样本的异步加载(loadSoundPool方法)

  5. 然后,存在一个保持声音样本加载状态的变量。它可以有三种状态:不加载加载加载

    从上面第4步开始,该变量的状态在线程中设置为 loading

    当自定义通知消息到达时,此变量的状态在handleMessage中设置为已加载

    在播放任何声音之前检查此状态变量。如果尚未加载声音样本,则会显示半全屏对话框,指出正在加载声音样本并在加载完成后自动消失。该对话框无法被用户解雇。如果显示,则通过调用该对话框的handleMessage方法以编程方式从dismiss方法开始。该对话框有一个很好的进度条动画。这是我的应用程序特有的,对于其他应用程序可以采用不同的方式。

    你可能会问为什么这么复杂。问题是整个Android GUI API是一个事件机器,根据定义,在主线程上运行的自己的代码不应该执行任何耗时的过程,因为这会使整个GUI无响应。事实上,当Android检测到这种情况时,它会将它们记录到logcat中。例如,如果我使用了信号量,则不会有进度条动画,因为主GUI线程将被阻止。以这种方式使用信号量只表现出不了解Android GUI架构的事实,如果发布信号量的部分出现问题,它很容易导致应用程序挂起。

    希望这有帮助。