Java安卓学习总结(二十五)

第二十一章 音频播放和控制

有关SoundPool

SoundPool是一个能加载一批声音资源到内存中,并能控制同时播放的音频文件的个数的定制版使用工具。因此assets中所有的音频都通过SoundPool来播放。

SoundPool有关方法说明

SoundPool可以使用Builder来构造,但是为了兼容低版本的设备,需要使用旧的构造方法:

public SoundPool(int maxStream, int streamType, int srcQuality) 

maxStream —— 同时播放的流的最大数量
streamType —— 流的类型,一般为STREAM_MUSIC(具体在AudioManager类中列出)
srcQuality —— 采样率转化质量,当前无效果,使用0作为默认值

使用SoundPool播放音频需要预先将所有的资源加载到内存中,并且播放音频需要音频文件的ID:

int load(AssetFileDescriptor afd, int priority) 

final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)

所以要先对每一个sound对象设置ID,ID由load方法的返回值确定。完整的方法实现如下:

private void load(Sound sound) throws IOException
{
AssetFileDescriptor afd=mAssets.openFd(sound.getAssetPath());
int soundID=mSoundPool.load(afd,1);
sound.setSoundID(soundID);
}

public void play(Sound sound)
{
Integer soundID=sound.getSoundID();
if (soundID==null)
return;
mSoundPool.play(soundID,1.0f,1.0f,1,0,mRate);
}

有关单元测试

项目的java文件夹下由三个包

AndroidTest是整合测试类,可以运行在设备和虚拟机上;test是单元测试类,可以在本地开发机上运行。这里用的是单元测试。

需要添加两个测试工具:

testImplementation 'org.jboss.forge.addon:mockito:2.13.0'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'

创建测试类

使用刚刚添加的JUnit单元测试框架来创建测试类,在有关需要测试的类中按下快捷键shift+ctrl+T,可以寻找/创建这个类所关联的测试类。

实现测试类

测试框架下由用@Before注解的setUp方法,整个方法会在所有测试之前运行一次,可以在这个方法中对所需要测试的单元进行有关初始化。可以用虚拟依赖项(Mockito的mock方法)来初始化一个实例:

@Before
public void setUp() throws Exception {
mBeatBox=mock(BeatBox.class);
mSound=new Sound("assetPath");
mSubject=new SoundViewModel(mBeatBox);
mSubject.setSound(mSound);
}

编写测试类方法

所有测试类方法都需要用@Test注解,书上讲了两种测试方法,一是用断言检测两个属性是否相关:

@Test
public void exposesSoundNameAstitle(){
assertThat(mSubject.getTitle(),is(mSound.getName()));
}

一个是测试对象交互,使用vertify(Object)方法,其利用流接口,检测目标object是否用了以xx为参数的xx方法,如下:

@Test
public void callsBeatBoxPlayOnButtonClicked()
{
mSubject.onButtonClicked();

verify(mBeatBox).play(mSound);
}

有关保留fragment

在一个fragment的onCreate方法中添加

setRetainInstance(true);

可以在activity被摧毁时暂时保留fragment,并与立刻生成的新activity绑定。但是这个方法除非使用保存实例状态行不通的情况下才推荐使用。

有关挑战练习 播放进度控制

在视图中加入一个Seekbar和对应的TextView:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
<TextView
android:id="@+id/rate_text"
android:textSize="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="The play rate is 1.00"/>
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

</layout>

在BeatBoxFragment的onCreateView中增加相应的方法:设置默认的进度为33,即原始速度为1.00。当进度条拖动时需要改变整个BeatBox的播放速度。

@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final FragmentBeatBoxBinding binding= DataBindingUtil.inflate(inflater,R.layout.fragment_beat_box,container,false);

binding.recyclerView.setLayoutManager(new GridLayoutManager(getActivity(),3));
binding.recyclerView.setAdapter(new SoundAdapter(mBeatBox.getSounds()));
binding.seekBar.setProgress(33);
binding.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {

binding.rateText.setText("The play rate is "+String.format("%.2f",0.015*i+0.5));
mBeatBox.setRate((float) (0.015*i+0.5));
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {

}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {

}
});
return binding.getRoot();
}

在BeatBox类中,需要增加一个初始值为1.00的,表示播放速率的成员变量mRate。在play方法中mSoundPool.play的最后一个参数设置为mRate;新增setRate方法,用于记录和重新设置所有正在播放的音频的速率。

public void play(Sound sound)
{
Integer soundID=sound.getSoundID();
if (soundID==null)
return;
mSoundPool.play(soundID,1.0f,1.0f,1,0,mRate);
}

public void setRate(float rate)
{
mRate=rate;
for (Sound sound:mSounds)
{
mSoundPool.setRate(sound.getSoundID(),rate);
}
}

Seekbar参考资料

发表评论