5

我正在按照教程将 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

4 回答 4

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

}
于 2013-02-08T22:52:26.450 回答
1

我想大家在使用SoundPool的时候,要注意两种情况:

  1. 对于 API+21,SoundPool 已弃用,您应该使用SoundPool.Builder
  2. 如果您的 SoundPool 在主线程中运行,请将其带到另一个线程,因为它会阻塞您的 UI
于 2016-07-19T13:20:57.980 回答
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 调用并向声音加载线程发出信号。声音加载线程可以等待带有信号量的信号或等待/通知共享对象。

于 2014-10-28T23:36:03.620 回答
0

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

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

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

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

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

此变量的状态设置为从上述第 4 步开始在线程中加载。

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

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

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

希望这可以帮助。

于 2014-11-26T18:35:52.397 回答