13 使用SoundPool播放音频

创建SoundPool

BeatBox.java

public class BeatBox {
    //...
    private static final int MAX_SOUNDS = 5;

    private AssetManager mAssets;
    private List<Sound> mSounds = new ArrayList<>();
    private SoundPool mSoundPool;

    public BeatBox(Context context) {
        mAssets = context.getAssets();

        /**
         * 创建SoundPool
         * @param maxStreams int 同时最大播放音频个数
         * @param streamType int 音频流类型,AudioManager.STREAM_MUSIC表示音乐、游戏音频流
         * @param srcQuality int 采样率转换品质,现已无效,传入0
         */
        mSoundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);

        loadSounds();
    }
    //...
}

加载音频文件

接下来使用 SoundPool 加载音频文件。相比其他音频播放方法,SoundPool 还有个快速响应的优势:指令刚一发出,它就会立即开始播放。但是,它需要在播放前预先加载音频。

SoundPool 加载的音频文件都有自己的 Integer 类型ID。在 Sound 类中添加SoundId 实例变量,并添加相应的获取方法和设置方法管理这些ID。

Sound.java

public class Sound {
    private String mAssetPath;
    private String mName;
    private Integer mSoundId;
    //...
    public Integer getSoundId() {
        return mSoundId;
    }
    public void setSoundId(Integer soundId) {
        mSoundId = soundId;
    }
}

mSoundId 用了 Integer 类型而不是 int 。这样,在 Sound 的 mSoundId 没有值时可以设置其为 null 值。

加载音频

BeatBox.java

//加载音频
private void loadSound(Sound sound) throws IOException {
    AssetFileDescriptor afd = mAssets.openFd(sound.getAssetPath());
    int soundId = mSoundPool.load(afd, 1);
    sound.setSoundId(soundId);
}

调用 mSoundPool.load(AssetFileDescriptor, int) 方法可以把文件载入 SoundPool 待播。为方便管理、重播或卸载音频文件,mSoundPool.load(…) 方法会返回一个 int 型ID。这实际就是存储在 mSoundId 中的ID。调用 openFd(String) 方法有可能抛出 IOException,load(Sound)方法也是如此。

载入全部音频文件

BeatBox.java

//获取asset资源,存入列表,载入全部音频文件
private void loadSounds() {
    String[] soundNames;
    
    //取得assets中的资源清单
    //...
    
    for (String filename : soundNames) {
        try {
            String assetPath = SOUNDS_FOLDER + "/" + filename;
            Sound sound = new Sound(assetPath);
            loadSound(sound);
            mSounds.add(sound);
        } catch (IOException ioe) {
            Log.e(TAG, "无法加载声音资源 " + filename, ioe);
        }
    }
}

播放音频

BeatBox.java

public void playSound(Sound sound) {
    Integer soundId = sound.getSoundId();
    if (soundId == null) return;

    /**
     * 播放指定ID的音频文件
     * @param soundID int load(...)方法加载音频的返回值
     * @param leftVolume float 左音量 (range = 0.0 to 1.0)
     * @param rightVolume float 右音量 (range = 0.0 to 1.0)
     * @param priority int 优先级,已无效 (0 = lowest priority)
     * @param loop int 循环 (0 = no loop, -1 = loop forever)
     * @param rate float 播放速率 (1.0 = normal playback, range 0.5 to 2.0)
     * @return int 成功返回ID,失败返回0
     */
    mSoundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f);
}

添加按钮单击事件

BeatBoxFragment.SoundHolder

private class SoundHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private Button mButton;
    private Sound mSound;

    public SoundHolder(LayoutInflater layoutInflater, ViewGroup container) {
        super(layoutInflater.inflate(R.layout.list_item_sound, container, false));

        mButton = itemView.findViewById(R.id.list_item_sound_button);
        mButton.setOnClickListener(this);
    }

    //绑定sound
    public void bindSound(Sound sound) {
        mSound = sound;
        mButton.setText(mSound.getName());
    }

    @Override
    public void onClick(View v) {
        mBeatBox.playSound(mSound);
    }
}

释放音频

BeatBox.java

public void releasePool() {
    mSoundPool.release();
}

覆盖onDesreoy()方法,在其中释放SoundPool:

BeatBoxFragment.java

@Override
public void onDestroy() {
    super.onDestroy();

    mBeatBox.releasePool();
}

设备旋转和对象保存

在Java世界,要保存对象,要么将其放入 Bundle 中,要么实现 Serializable 接口或者 Parcelable 接口。无论采用哪种方式,对象首先要是可保存对象。

BeatBox 的某一部分可以保存,例如,Sound 类中的一切都可以保存;而 SoundPool 就无法保存了。虽然可以新建包含同样音频文件的 SoundPool ,或者从音频播放中断处继续,然而和电视播放例子一样,播放中断的那段时光是无论如何也找不回了。

保留fragment

savedInstanceState 机制只适用于可保存的对象数据,但 BeatBox 不可保存。在Activity创建和销毁时,我们都需要 BeatBox 实例一直可用。

为应对设备配置变化,fragment有个特殊方法可确保 BeatBox 实例一直存在:retainInstance。覆盖 BeatBoxFragment.onCreate(…) 方法并设置fragment的属性值:

BeatBoxFragment.java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //保留fragment
    //这样,设备旋转时,已保留的fragment不会随activity一起被销毁,它会一直保留,并在需要时原封不动地传递给新的activity。已保留的fragment的全部实例变量也会保持不变。
    setRetainInstance(true);

    mBeatBox = new BeatBox(getActivity());
}

fragment的 retainInstance 属性值默认为 false ,这表明其不会被保留。如属性值为 true ,则该fragment的视图立即被销毁,但fragment本身不会被销毁。虽然保留的fragment没有被销毁,但它已脱离消亡中的activity并处于保留状态。尽管此时的fragment仍然存在,但已没有任何activity托管它。

在这里插入图片描述

在这里插入图片描述

必须同时满足两个条件,fragment才能进入保留状态:

  1. 已调用了fragment的setRetainInstance(true)方法;
  2. 因设备配置改变(通常为设备旋转),托管activity正在被销毁。

ragment只能短暂处于保留状态,即fragment脱离旧activity到重新附加给立即创建的新activity之间的一段时间。


深入学习

是否要保留

除非万不得已,非常不建议使用这种机制。

  1. 首先,相比非保留fragment,保留fragment的显示非常复杂。一旦出现问题,排查起来非常耗时。既然使用它会让程序复杂起来,能不用就不用吧。
  2. 其次,fragment在使用保存实例状态的方式处理设备旋转时,也能够应对所有生命周期场景;但保留的fragment只能处理activity因设备旋转而销毁的情况。如果activity是因操作系统需要回收内存而被销毁,则所有保留的fragment也会随之销毁,数据也就跟着丢失了。

设备旋转处理再探

如果activity或fragment中有需要长久保存的东西,应覆盖 onSaveInstanceState(Bundle) 方法保存其状态。这样,由于和activity记录的生命周期保持了同步,后续就可在需要时恢复。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值