自学安卓编程指南(十七)

这章是用来播放音乐和进行单元测试

对于架构来说,MVVM架构很适合于单元测试,对于播放音乐来说,SoundPool能加载一批音乐资源到内存里面,然后能控制同时播放的音频的文件的个数,如下面的代码

(1)在BeatBox里面创建一个SoundPool对象

private static final int MAX_SOUNDS = 5;

private SoundPool mSoundPool;

//在它的构造函数中添加

mSoundPool = new SoundPool(MAX_SOUNDS,AudioManager.STREAM_MUSIC,0)//第一个参数是指定同时播放多少个音频,第二个参数是确定音频流的类型,Android中有不同的音频流,这也是为什么调低音乐的音量而闹钟的音量却没有受到影响,第三个是采样的转换品质,参考文档说这个不起作用,所以直接传入0就好

 

SoundPool的优势是:指令一发出来,它就能立刻播放,一点都不拖拉,但是它在播放前就必须需要去预加载音频,SoundPool加载的音频文件都有自己的Integer型的Id,所以这边需要在Sound类中添加mSoundId的实例变量,并添加getter和setter方法

private Integer mSoundId;

public Integer getSoundId(){

return mSoundId;

}

public void setSoundId(Integer soundId){

mSoundId = soundId;

}

这边Id使用的是Integer而不是int,这样当mSoundId没有值的时候就可以设置为null了

(2)加载音频,我们在BeatBox中去添加load()方法去载入音频

private void load(Sound sound)throws IOException {

AssetFileDescriptor afd = mAssets.openFd(sound.getAssetPath());

int soundId = mSoundPool.load(afd,1);

sound.setSoundId(soundId);

}

mSoundPool.load()方法可以把文件载入SoundPool待播,为了方便管理,重播,卸载音频文件,那么这个方法就会返回一个int型的Id,这实际上就是放在mSoundId中的Id,

调用openFd()方法有可能抛出IOException

上面的方法写好后就可以在BeatBox中的loadSound()方法中去载入全部的音频

for(String filename : soundName) {

try {

String assetPath = SOUNDS_FOLDER + "/" + filename;

Sound sound = new Sound(assetPath);

load(sound);

mSounds.add(sound);

}catch(IOException e) {

log.e(TAG,"ss");

}

}

(3)播放音频

最后为了播放音频就应该在BeatBox中添加play方法

public void play(Sound sound ) {

Integer soundId = sound.getSoundId();

if(soundId == null) {

return;

}

mSoundPool.Play(sound,1.0f,1.0f,1,0,1,0f);

}

在播放前要确保soundId不是null值,Sound加载失败会出现null的值

 

下面是用来测试所用的

(1)要写测试代码就首先需要为项目添加两个测试工具:Mockito(一个方便创建虚拟对象的Java框架,有了虚拟对象就可以单独测试SoundViewModel,不用担心会因代码关联关系测试到其他对象)和Hamcret(一个规则的匹配器工具库,可以在代码里模拟匹配条件,如果不能按预期匹配条件就不能通过)两个库

添加方法:

右击app模块,选择Open Module Setting 菜单项目,选择弹出界面的Dependencies选择项,然后点击+弹出依赖库窗口,输入需要的库名

如果,我们只想要在测试的时候去让这个库使用,那么我们应该自己手动修改 build.gradle文件,在里面把依赖库的范围从compile改成testCompile

(2)创建测试类,写单元测试的方式是使用测试框架类,使用测试框架类可以集中编写和运行测试案例,并支持在Android studio中里面去看到测试结果

JUnit是最常见的Android单元测试框架,要使用它首先就需要创建一个用作Junit测试的测试类,打开SoundViewModel类,使用

Ctrl + Shift + T就会打开寻找这个类关联的测试类,如果找不到它会提醒新建

我们选择Creare New Test 创建一个新测试的类,测试库选择JUnit4,勾选setUp/@Before。其他的选择不变,

(3)最后我们应该选择创建哪种测试类,或是说在哪一个目录中去存放测试类

androidTest目录下的都是整合测试类,这样的可以运行在设备和虚拟设备上,这样有优点:可以在运行时动态测试应用行为,缺点:需要编译打包在设备上运行,浪费资源

test目录上的是单元测试类,它可以运行在本地开发机上,可以脱离Android运行时环境,速度会挺快的,它的规模最小,测试单个类,不影响手上的工作,可以快速地反复运行,因此我们可以将它放在这了目录上面去

 

一.实现测试类

(1)现在就来实现SoundViewModel测试类,测试框架里面只有一个setUp方法

public class SoundViewModelTest {

@Before 

public void setUp() throws Exception {

 

}

}

(这个测试类在于app模块的test目录下)

和大多数对象,测试类也需要创建对象实例和它依赖的其他对象,为了避免每一个测试类都需要写重复的代码,JUnit提供了@Before这个注解,以@Before注解的包含公共代码的方法会在测试之前运行一次,按照约定每一个单元测试类都需要有@Before注解的setUp()方法

在测试类中使用虚拟对象,虚拟对象会继承BeatBox,同样的方法但是这些方法什么都不做,这样依赖BeatBox的SoundViewModel就不会出问题

要用Mockito创建虚拟对象,需要传入虚拟的类,调用mock()静态方法,创建一个虚拟的BeatBox对象,然后存入mBeatBox变量中

public class SoundViewModelTest {

private BeatBox mBeatBox;

@Before

public void setUp() throws Exception {

  mBeatBox = mock(BeatBox.class);

}

}

mock()方法会导入一个包,mock()方法会自己创建一个虚拟的BeatBox对象

有了虚拟依赖对象就可以来完成测试类了

public class SoundViewModelTest {

private BeatBox mBeatBox;

private Sound sound;

private SoundViewModel mSubject;//这边的变量命名是一个习惯规定,说明这是一个测试类

@Before

public void setUp() throws Exception {

  mBeatBox = mock(BeatBox.class);

mSound = new Sound("assetPath");

mSubject = new SoundViewModel(mBeatBox);

mSubject.setSound(mSound);

}

}

(2)编写测试方法

比如我们可以在测试类判断SoundViewModel的getTitle方法属性和Sound里的getName()属性是有关系的

@Test

public void exposesSoundNameAsTitle(){

assertThat(mSubject.getTitle(),is(mSound.getName()));//判定测试对象获取标题方法和sound的获取文件名返回相同的值,如果不同那么单元测试失败

}

需要导入某些库时,是使用 alt + enter 

(3)测试交互

在这边我们可以来整合SoundViewModel和BeatBox.play()的方法,在实践中,我们在写一个新的方法之前,我们会先写一个测试验证这个方法的预期结果,必须我们需要在SoundViewModel类里写onBunttonClicked()去调用BeatBox.play(Sound)方法,那么就需要在测试类中写一个测试方法调用onButtonClicked();

@Test

public void callsBeatBoxPlayOnButtonClicked(){

mSubject.onButtonClicked();

}

将光标移到它那边然后alt enter,然后就创建这个方法,最后我们需要使用verify()方法。确定onButtonClicked()方法调用了BeatBox.play(Sound)

@Test

public void callsBeatBoxPlayOnButtonClicked(){

mSubject.onButtonClicked();

verify(mBeatBox).play(mSound);//验证以mSound作为参数,调用了mBeatBox对象的play()方法

}

如果此时使用ctrl + R 那么就会出现测试失败,因为此时onButtonClicked方法还没有实现,应该按下面去做

public void onButtonClicked(){

mBeatBox.play(mSound);

}

二.数据绑定回调

按钮要响应事件还查查最后一步:关联按钮对象和onButtonClicked()方法

我们可以在布局文件中添加数据绑定lambda表达式子

android:onClick = "@{ ()->viewModel.onButtonClicked()}"

此时如果运行了会听见自己设置的声音

三.释放音频

对于SoundPool.release()方法来说,它需要

public void release(){

mSoundPool.release();

}

 

最后在onDestroy()方法中,

public void onDestroy(){

super.onDestry();

mBeatBox.release();

}

 

四设备旋转和对象保存

在这里如果我们点击播放然后旋转,那么此时声音会停止,在前面我们曾经用过onSaveInstanceState()方法来解决问题,但在这里不行,因为上面的方法得首先保存数据,然后才能再使用Bundle中的Parcelable恢复数据,但是在这里SoundPool是不可保存的,而不可保存性具有向外传递性,如果一个对象重度依赖另一个对象,那么这个对象也就无法保存,这里BeatBox也就无法保存了,那么要解决这个问题那么就需要下面的解决方法了

(1)保留fragment

为了应对设备配置的变化,fragment有一个特殊的方法可以确保BeatBox实例不被销毁,那就是retainInstance()

覆盖onCreate()方法,并设置fragment的属性值

在onCreate()方法里面写下面的代码

setRetainInstance(true);//它默认是flalse,表明不会保留,因此设备旋转时,fragment会随着设备旋转时fragment会随着activity一起杯毁灭并重建,true的话,那么就可以保留fragment,已经保留的fragment不会随着activity消失,它会一直保留,并在需要时返回给新的activity,此时fragment保存了,那么全部的实例变量(BeatBox)就会保持不变

 

下面是上面实现的内在原因:

fragment之所以可以保留,是因为:可以消灭和重建fragment的视图,但是fragment自身可以不被消灭

(1)设备配置发生编号时,FragmentManager首先会销毁队列中的fragment视图,在设备配置发生变化时总会销毁和重建fragment和activity的视图,这是因为新的配置可能需要新的资源来比配,有更适合的资源时,就应该重新构建视图了

(2)接着FragmentManager就会去检查每一个fragment的retainInstance属性值,如果属性值为false那么就会毁灭该fragment的实例,然后配置新的activity就会创建一个新的fragment和其视图,如果是true那么该视图就会立刻被毁灭,但是fragment本身不会被毁灭,但新的activity创建后,新的Fragment创建后新的Fragment就会找到已经保留的fragment,重新创建它的视图

 

虽然已保留的fragment没有被毁灭,但是它已经脱离activity并处于保留状态了,尽管fragment还在,但是没有activity去托管它了,但是fragment的保留只能保留非常短的时间,就是凑从fragment脱离activity到重新附加给快速的新建activtiy之间的一段时间

 

上面的方法只适合于因为设备旋转而导致的,如果是系统回收的,那么就没用,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值