首先简单介绍下预备知识:

1.Android的audio流的类型有以下12种:

[java] view plain copy
  1. /*Theaudiostreamforphonecalls*/
  2. publicstaticfinalintSTREAM_VOICE_CALL=0;//通话连接时的音频流(通话声)
  3. /*Theaudiostreamforsystemsounds*/
  4. publicstaticfinalintSTREAM_SYSTEM=1;//系统音频流
  5. /*Theaudiostreamforthephoneringandmessagealerts*/
  6. publicstaticfinalintSTREAM_RING=2;//来电铃声
  7. /*Theaudiostreamformusicplayback*/
  8. publicstaticfinalintSTREAM_MUSIC=3;//媒体音频流
  9. /*Theaudiostreamforalarms*/
  10. publicstaticfinalintSTREAM_ALARM=4;//闹钟音频流
  11. /*Theaudiostreamfornotifications*/
  12. publicstaticfinalintSTREAM_NOTIFICATION=5;//通知音频流
  13. /*@hideTheaudiostreamforphonecallswhenconnectedonbluetooth*/
  14. publicstaticfinalintSTREAM_BLUETOOTH_SCO=6;//从注释上看时使用蓝牙耳机通话的音频流
  15. /*@hideTheaudiostreamforenforcedsystemsoundsincertaincountries(e.gcamerainJapan)*/
  16. publicstaticfinalintSTREAM_SYSTEM_ENFORCED=7;//一些国家强制使用的音频流??不太明白
  17. /*@hideTheaudiostreamforDTMFtones*/
  18. publicstaticfinalintSTREAM_DTMF=8;//DTMF音频流
  19. /*@hideTheaudiostreamfortexttospeech(TTS)*/
  20. publicstaticfinalintSTREAM_TTS=9;//TTS:TexttoSpeech:文件到语言的音频流,即机器说话
  21. /*@hideTheaudiostreamforFm*/
  22. publicstaticfinalintSTREAM_FM=10;//FM的音频流
  23. /*@hideTheaudiostreamforMATV*/
  24. publicstaticfinalintSTREAM_MATV=11;//TV的音频流

每种音频流所规定的最大值:


[java] view plain copy
  1. /**@hideMaximumvolumeindexvaluesforaudiostreams*/
  2. privateint[]MAX_STREAM_VOLUME=newint[]{
  3. 6,//STREAM_VOICE_CALL
  4. 7,//STREAM_SYSTEM
  5. 7,//STREAM_RING
  6. 12,//STREAM_MUSIC
  7. 7,//STREAM_ALARM
  8. 7,//STREAM_NOTIFICATION
  9. 15,//STREAM_BLUETOOTH_SCO
  10. 7,//STREAM_SYSTEM_ENFORCED
  11. 15,//STREAM_DTMF
  12. 15,//STREAM_TTS
  13. 13,//STREAM_FM
  14. 13//stream_MATV
  15. };

2.所有的按键事件都是touch事件,这部分我会另外开篇博文介绍。


开始本文正文,Anndroid系统中所有View带有按键音,用户可以通过Settings>Sound>勾选Audible Selection即可开启按键音。但是有个奇怪的地方:此按键音是与媒体音量(即STREAM_MUSIC)绑定的,难道按键音的STREAM TYPE就是STREAM_MUSIC吗?我们从代码中寻找一下。


首先所有的View点击的时候都有按键音,我们从View.java的点击事件找起,在view的响应的onTouchEvent()方法中有如下代码:


[java] view plain copy
  1. switch(event.getAction()){
  2. caseMotionEvent.ACTION_UP:
  3. booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;
  4. if((mPrivateFlags&PRESSED)!=0||prepressed){
  5. //takefocusifwedon'thaveitalreadyandweshouldin
  6. //touchmode.
  7. booleanfocusTaken=false;
  8. if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
  9. focusTaken=requestFocus();
  10. }
  11. if(!mHasPerformedLongPress){
  12. //Thisisatap,soremovethelongpresscheck
  13. removeLongPressCallback();
  14. //Onlyperformtakeclickactionsifwewereinthepressedstate
  15. if(!focusTaken){
  16. //UseaRunnableandpostthisratherthancalling
  17. //performClickdirectly.Thisletsothervisualstate
  18. //oftheviewupdatebeforeclickactionsstart.
  19. if(mPerformClick==null){
  20. mPerformClick=newPerformClick();
  21. }
  22. if(!post(mPerformClick)){
  23. performClick();//这里响应click事件
  24. }
  25. }
  26. }
  27. if(mUnsetPressedState==null){
  28. mUnsetPressedState=newUnsetPressedState();
  29. }
  30. if(prepressed){
  31. mPrivateFlags|=PRESSED;
  32. refreshDrawableState();
  33. postDelayed(mUnsetPressedState,
  34. ViewConfiguration.getPressedStateDuration());
  35. }elseif(!post(mUnsetPressedState)){
  36. //Ifthepostfailed,unpressrightnow
  37. mUnsetPressedState.run();
  38. }
  39. removeTapCallback();
  40. }
  41. break;

处理click事件就写在performClick()函数当中,继续看该函数具体做了什么:


[java] view plain copy
  1. publicbooleanperformClick(){
  2. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  3. if(mOnClickListener!=null){
  4. playSoundEffect(SoundEffectConstants.CLICK);
  5. mOnClickListener.onClick(this);
  6. returntrue;
  7. }
  8. returnfalse;
  9. }

从这里可以看到与用户接口onClickListener结合起来了,当用户注册了clickListener,则调用发出按键音函数playSoundEffect ()和响应用户写好的clickListener的onClick()方法。这里playSoundEffect函数传的参数SoundEffectContants.CLICK为多少呢,从SoundEffectConstants.java可知SoundEffectConstants.CLICK = 0:


[java] view plain copy
  1. publicclassSoundEffectConstants
  2. {
  3. SoundEffectConstants(){thrownewRuntimeException("Stub!");}
  4. publicstaticintgetContantForFocusDirection(intdirection){thrownewRuntimeException("Stub!");}
  5. publicstaticfinalintCLICK=0;
  6. publicstaticfinalintNAVIGATION_LEFT=1;
  7. publicstaticfinalintNAVIGATION_UP=2;
  8. publicstaticfinalintNAVIGATION_RIGHT=3;
  9. publicstaticfinalintNAVIGATION_DOWN=4;
  10. }

playSoundEffect ()的具体内容如下:


[java] view plain copy
  1. publicvoidplaySoundEffect(intsoundConstant){
  2. if(mAttachInfo==null||mAttachInfo.mRootCallbacks==null||!isSoundEffectsEnabled()){
  3. return;
  4. }
  5. mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
  6. }

真正调用的是AttachInfo Callbacks接口的playSoundEffect()函数:


[java] view plain copy
  1. /**
  2. *Asetofinformationgiventoaviewwhenitisattachedtoitsparent
  3. *window.
  4. */
  5. staticclassAttachInfo{
  6. interfaceCallbacks{
  7. voidplaySoundEffect(inteffectId);
  8. booleanperformHapticFeedback(inteffectId,booleanalways);
  9. }

看注释可知其真正的方法写在parent window中,那parent window是哪个呢?ViewRoot的实现该回调接口:


[java] view plain copy
  1. publicfinalclassViewRootextendsHandlerimplementsViewParent,
  2. View.AttachInfo.Callbacks{

具体的playSoundEffect()函数内容:


[java] view plain copy
  1. publicvoidplaySoundEffect(inteffectId){
  2. checkThread();
  3. try{
  4. finalAudioManageraudioManager=getAudioManager();
  5. switch(effectId){
  6. caseSoundEffectConstants.CLICK:
  7. audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
  8. return;
  9. caseSoundEffectConstants.NAVIGATION_DOWN:
  10. audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
  11. return;
  12. caseSoundEffectConstants.NAVIGATION_LEFT:
  13. audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
  14. return;
  15. caseSoundEffectConstants.NAVIGATION_RIGHT:
  16. audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
  17. return;
  18. caseSoundEffectConstants.NAVIGATION_UP:
  19. audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
  20. return;
  21. default:
  22. thrownewIllegalArgumentException("unknowneffectid"+effectId+
  23. "notdefinedin"+SoundEffectConstants.class.getCanonicalName());
  24. }
  25. }catch(IllegalStateExceptione){
  26. //ExceptionthrownbygetAudioManager()whenmViewisnull
  27. Log.e(TAG,"FATALEXCEPTIONwhenattemptingtoplaysoundeffect:"+e);
  28. e.printStackTrace();
  29. }
  30. }

我们传入的参数为SoundEffectContants.CLICK,调用AudioManager的playSoundEffect()方法,参数为AudioManger.FX_KEY_CLICK,继续往下看,在AudioManager.java中playSoundEffect()方法:


[java] view plain copy
  1. publicvoidplaySoundEffect(inteffectType){
  2. if(effectType<0||effectType>=NUM_SOUND_EFFECTS){
  3. return;
  4. }
  5. if(!querySoundEffectsEnabled()){
  6. return;
  7. }
  8. IAudioServiceservice=getService();
  9. try{
  10. service.playSoundEffect(effectType);
  11. }catch(RemoteExceptione){
  12. Log.e(TAG,"DeadobjectinplaySoundEffect"+e);
  13. }
  14. }


调用了IAudioService的playSoundEffect() 方法,IAudioService方法是使用aidl生成的接口,aidl源文件:frameworks/base/media/java/android/media/IAudioService.aidl,真正响应的地方在AudioService.java中:


[java] view plain copy
  1. /**@seeAudioManager#playSoundEffect(int)*/
  2. publicvoidplaySoundEffect(inteffectType){
  3. sendMsg(mAudioHandler,MSG_PLAY_SOUND_EFFECT,SHARED_MSG,SENDMSG_NOOP,
  4. effectType,-1,null,0);
  5. }

该方法调用sendMsg()方法,传入一下参数:(mAudioHandler, 7, -1, 1, 0, -1, null, 0),sendMsg()方法如下:


[java] view plain copy
  1. privatestaticvoidsendMsg(Handlerhandler,intbaseMsg,intstreamType,
  2. intexistingMsgPolicy,intarg1,intarg2,Objectobj,intdelay){
  3. intmsg=(streamType==SHARED_MSG)?baseMsg:getMsg(baseMsg,streamType);
  4. if(existingMsgPolicy==SENDMSG_REPLACE){
  5. handler.removeMessages(msg);
  6. }elseif(existingMsgPolicy==SENDMSG_NOOP&&handler.hasMessages(msg)){
  7. Log.d(TAG,"sendMsg:Msg"+msg+"existed!");
  8. return;
  9. }
  10. handler
  11. .sendMessageDelayed(handler.obtainMessage(msg,arg1,arg2,obj),delay);
  12. }

该方法就是将传入的参数经过计算,obtain一个message,message的what = 7 arg1 = 0 arg2 = -1 object = null; 处理该消息的地方handleMessage ():


[java] view plain copy
  1. @Override
  2. publicvoidhandleMessage(Messagemsg){
  3. intbaseMsgWhat=getMsgBase(msg.what);
  4. switch(baseMsgWhat){
  5. ...
  6. caseMSG_PLAY_SOUND_EFFECT:
  7. playSoundEffect(msg.arg1,msg.arg2);
  8. break;
  9. ...
  10. }


调用了带两个参数的playSoundEffect()函数,传入参数 0,-1:


[java] view plain copy
  1. privatevoidplaySoundEffect(inteffectType,intvolume){
  2. synchronized(mSoundEffectsLock){
  3. if(mSoundPool==null){
  4. return;
  5. }
  6. floatvolFloat;
  7. //useSTREAM_MUSICvolumeattenuatedby3dBifvolumeisnotspecifiedbycaller
  8. if(volume<0){
  9. //以下计算播放的音量大小:
  10. //SamelineartologconversionasinnativeAudioSystem::linearToLog()(AudioSystem.cpp)
  11. floatdBPerStep=(float)((0.5*100)/MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
  12. intmusicVolIndex=(mStreamStates[AudioSystem.STREAM_MUSIC].mIndex+5)/10;
  13. floatmusicVoldB=dBPerStep*(musicVolIndex-MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
  14. volFloat=(float)Math.pow(10,(musicVoldB-3)/20);
  15. }else{
  16. volFloat=(float)volume/1000.0f;
  17. }
  18. if(SOUND_EFFECT_FILES_MAP[effectType][1]>0){
  19. mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],volFloat,volFloat,0,0,1.0f);//调用该函数播放按键音
  20. }else{
  21. MediaPlayermediaPlayer=newMediaPlayer();
  22. if(mediaPlayer!=null){
  23. try{
  24. StringfilePath=Environment.getRootDirectory()+SOUND_EFFECTS_PATH+SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
  25. mediaPlayer.setDataSource(filePath);
  26. mediaPlayer.setAudioStreamType(AudioSystem.STREAM_RING);
  27. mediaPlayer.prepare();
  28. mediaPlayer.setVolume(volFloat,volFloat);
  29. mediaPlayer.setOnCompletionListener(newOnCompletionListener(){
  30. publicvoidonCompletion(MediaPlayermp){
  31. cleanupPlayer(mp);
  32. }
  33. });
  34. mediaPlayer.setOnErrorListener(newOnErrorListener(){
  35. publicbooleanonError(MediaPlayermp,intwhat,intextra){
  36. cleanupPlayer(mp);
  37. returntrue;
  38. }
  39. });
  40. mediaPlayer.start();
  41. }catch(IOExceptionex){
  42. Log.w(TAG,"MediaPlayerIOException:"+ex);
  43. }catch(IllegalArgumentExceptionex){
  44. Log.w(TAG,"MediaPlayerIllegalArgumentException:"+ex);
  45. }catch(IllegalStateExceptionex){
  46. Log.w(TAG,"MediaPlayerIllegalStateException:"+ex);
  47. }
  48. }
  49. }
  50. }
  51. }

因为传入的参数volume为-1,按键音大小的值走if(volume < 0)内,函数中此部分计算的是按键音的大小volFloat,可以看出,整个计算过程都跟媒体音量STREAM_MUSIC有关,这里就看出,按键音的音量大小是与STREAM_MUSIC绑定的,那按键音的类型呢?继续看下去,函数mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 0, 0, 1.0f);中使用了SoundPool来播放按键音,我们看看该SoundPool初始化步骤,在AudioService初始化时,调用了loadSoundEffects()函数:


[java] view plain copy
  1. publicAudioService(Contextcontext){
  2. ......
  3. loadSoundEffects();
  4. .......
  5. }


loadSoundEffects()函数的具体实现如下:


[java] view plain copy
  1. publicbooleanloadSoundEffects(){
  2. synchronized(mSoundEffectsLock){
  3. if(mSoundPool!=null){
  4. returntrue;
  5. }
  6. mSoundPool=newSoundPool(NUM_SOUNDPOOL_CHANNELS,AudioSystem.STREAM_SYSTEM,0);
  7. ......
  8. returntrue;
  9. }

在此函数中,初始化了该SoundPool,类型为STREAM_SYSTEM。


到这里,结果出来了,android中,view的按键音类型为系统音频(STREAM_SYSTEM),而音量的大小与媒体音量(STREAM_MUSIC)绑定了起来。

更多相关文章

  1. Android(安卓)使用Visualizer获取播放音频的频率
  2. android 显示系统
  3. Android媒体扫描详细解析之二(MediaScanner & MediaProvider)
  4. Android(安卓)Hook框架Xposed详解:从源代码分析到开发指南
  5. android 背光驱动
  6. android ViewTreeObserver详细讲解
  7. cocos2dx中利用xcode 调用java中的函数
  8. 初学Android,音频管理器之控制音频(六十六)
  9. Android音频系统之AudioFlinger(二)

随机推荐

  1. 解决异常报错org.mybatis.spring.MyBatis
  2. mysql在渗透中的技巧总结
  3. Sql Server网络配置协议不可用
  4. mysql中MAX()函数MIN()函数
  5. Oracle系统表v$session、v$sql字段说明(转
  6. 项目连接MySQL数据库,提示异常
  7. mysql 中 case when then .... else end
  8. linux下安装2个mysql 失败
  9. MySql生日闰月处理
  10. sql*loader问题,我有几万条记录,为什么只