Android关于view按键音的修改
首先简单介绍下预备知识:
1.Android的audio流的类型有以下12种:
[java] view plain copy- /*Theaudiostreamforphonecalls*/
- publicstaticfinalintSTREAM_VOICE_CALL=0;//通话连接时的音频流(通话声)
- /*Theaudiostreamforsystemsounds*/
- publicstaticfinalintSTREAM_SYSTEM=1;//系统音频流
- /*Theaudiostreamforthephoneringandmessagealerts*/
- publicstaticfinalintSTREAM_RING=2;//来电铃声
- /*Theaudiostreamformusicplayback*/
- publicstaticfinalintSTREAM_MUSIC=3;//媒体音频流
- /*Theaudiostreamforalarms*/
- publicstaticfinalintSTREAM_ALARM=4;//闹钟音频流
- /*Theaudiostreamfornotifications*/
- publicstaticfinalintSTREAM_NOTIFICATION=5;//通知音频流
- /*@hideTheaudiostreamforphonecallswhenconnectedonbluetooth*/
- publicstaticfinalintSTREAM_BLUETOOTH_SCO=6;//从注释上看时使用蓝牙耳机通话的音频流
- /*@hideTheaudiostreamforenforcedsystemsoundsincertaincountries(e.gcamerainJapan)*/
- publicstaticfinalintSTREAM_SYSTEM_ENFORCED=7;//一些国家强制使用的音频流??不太明白
- /*@hideTheaudiostreamforDTMFtones*/
- publicstaticfinalintSTREAM_DTMF=8;//DTMF音频流
- /*@hideTheaudiostreamfortexttospeech(TTS)*/
- publicstaticfinalintSTREAM_TTS=9;//TTS:TexttoSpeech:文件到语言的音频流,即机器说话
- /*@hideTheaudiostreamforFm*/
- publicstaticfinalintSTREAM_FM=10;//FM的音频流
- /*@hideTheaudiostreamforMATV*/
- publicstaticfinalintSTREAM_MATV=11;//TV的音频流
每种音频流所规定的最大值:
- /**@hideMaximumvolumeindexvaluesforaudiostreams*/
- privateint[]MAX_STREAM_VOLUME=newint[]{
- 6,//STREAM_VOICE_CALL
- 7,//STREAM_SYSTEM
- 7,//STREAM_RING
- 12,//STREAM_MUSIC
- 7,//STREAM_ALARM
- 7,//STREAM_NOTIFICATION
- 15,//STREAM_BLUETOOTH_SCO
- 7,//STREAM_SYSTEM_ENFORCED
- 15,//STREAM_DTMF
- 15,//STREAM_TTS
- 13,//STREAM_FM
- 13//stream_MATV
- };
2.所有的按键事件都是touch事件,这部分我会另外开篇博文介绍。
开始本文正文,Anndroid系统中所有View带有按键音,用户可以通过Settings>Sound>勾选Audible Selection即可开启按键音。但是有个奇怪的地方:此按键音是与媒体音量(即STREAM_MUSIC)绑定的,难道按键音的STREAM TYPE就是STREAM_MUSIC吗?我们从代码中寻找一下。
首先所有的View点击的时候都有按键音,我们从View.java的点击事件找起,在view的响应的onTouchEvent()方法中有如下代码:
- switch(event.getAction()){
- caseMotionEvent.ACTION_UP:
- booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;
- if((mPrivateFlags&PRESSED)!=0||prepressed){
- //takefocusifwedon'thaveitalreadyandweshouldin
- //touchmode.
- booleanfocusTaken=false;
- if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
- focusTaken=requestFocus();
- }
- if(!mHasPerformedLongPress){
- //Thisisatap,soremovethelongpresscheck
- removeLongPressCallback();
- //Onlyperformtakeclickactionsifwewereinthepressedstate
- if(!focusTaken){
- //UseaRunnableandpostthisratherthancalling
- //performClickdirectly.Thisletsothervisualstate
- //oftheviewupdatebeforeclickactionsstart.
- if(mPerformClick==null){
- mPerformClick=newPerformClick();
- }
- if(!post(mPerformClick)){
- performClick();//这里响应click事件
- }
- }
- }
- if(mUnsetPressedState==null){
- mUnsetPressedState=newUnsetPressedState();
- }
- if(prepressed){
- mPrivateFlags|=PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- }elseif(!post(mUnsetPressedState)){
- //Ifthepostfailed,unpressrightnow
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
处理click事件就写在performClick()函数当中,继续看该函数具体做了什么:
- publicbooleanperformClick(){
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- if(mOnClickListener!=null){
- playSoundEffect(SoundEffectConstants.CLICK);
- mOnClickListener.onClick(this);
- returntrue;
- }
- returnfalse;
- }
从这里可以看到与用户接口onClickListener结合起来了,当用户注册了clickListener,则调用发出按键音函数playSoundEffect ()和响应用户写好的clickListener的onClick()方法。这里playSoundEffect函数传的参数SoundEffectContants.CLICK为多少呢,从SoundEffectConstants.java可知SoundEffectConstants.CLICK = 0:
- publicclassSoundEffectConstants
- {
- SoundEffectConstants(){thrownewRuntimeException("Stub!");}
- publicstaticintgetContantForFocusDirection(intdirection){thrownewRuntimeException("Stub!");}
- publicstaticfinalintCLICK=0;
- publicstaticfinalintNAVIGATION_LEFT=1;
- publicstaticfinalintNAVIGATION_UP=2;
- publicstaticfinalintNAVIGATION_RIGHT=3;
- publicstaticfinalintNAVIGATION_DOWN=4;
- }
playSoundEffect ()的具体内容如下:
- publicvoidplaySoundEffect(intsoundConstant){
- if(mAttachInfo==null||mAttachInfo.mRootCallbacks==null||!isSoundEffectsEnabled()){
- return;
- }
- mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
- }
真正调用的是AttachInfo Callbacks接口的playSoundEffect()函数:
- /**
- *Asetofinformationgiventoaviewwhenitisattachedtoitsparent
- *window.
- */
- staticclassAttachInfo{
- interfaceCallbacks{
- voidplaySoundEffect(inteffectId);
- booleanperformHapticFeedback(inteffectId,booleanalways);
- }
看注释可知其真正的方法写在parent window中,那parent window是哪个呢?ViewRoot的实现该回调接口:
- publicfinalclassViewRootextendsHandlerimplementsViewParent,
- View.AttachInfo.Callbacks{
具体的playSoundEffect()函数内容:
- publicvoidplaySoundEffect(inteffectId){
- checkThread();
- try{
- finalAudioManageraudioManager=getAudioManager();
- switch(effectId){
- caseSoundEffectConstants.CLICK:
- audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
- return;
- caseSoundEffectConstants.NAVIGATION_DOWN:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
- return;
- caseSoundEffectConstants.NAVIGATION_LEFT:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
- return;
- caseSoundEffectConstants.NAVIGATION_RIGHT:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
- return;
- caseSoundEffectConstants.NAVIGATION_UP:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
- return;
- default:
- thrownewIllegalArgumentException("unknowneffectid"+effectId+
- "notdefinedin"+SoundEffectConstants.class.getCanonicalName());
- }
- }catch(IllegalStateExceptione){
- //ExceptionthrownbygetAudioManager()whenmViewisnull
- Log.e(TAG,"FATALEXCEPTIONwhenattemptingtoplaysoundeffect:"+e);
- e.printStackTrace();
- }
- }
我们传入的参数为SoundEffectContants.CLICK,调用AudioManager的playSoundEffect()方法,参数为AudioManger.FX_KEY_CLICK,继续往下看,在AudioManager.java中playSoundEffect()方法:
- publicvoidplaySoundEffect(inteffectType){
- if(effectType<0||effectType>=NUM_SOUND_EFFECTS){
- return;
- }
- if(!querySoundEffectsEnabled()){
- return;
- }
- IAudioServiceservice=getService();
- try{
- service.playSoundEffect(effectType);
- }catch(RemoteExceptione){
- Log.e(TAG,"DeadobjectinplaySoundEffect"+e);
- }
- }
调用了IAudioService的playSoundEffect() 方法,IAudioService方法是使用aidl生成的接口,aidl源文件:frameworks/base/media/java/android/media/IAudioService.aidl,真正响应的地方在AudioService.java中:
- /**@seeAudioManager#playSoundEffect(int)*/
- publicvoidplaySoundEffect(inteffectType){
- sendMsg(mAudioHandler,MSG_PLAY_SOUND_EFFECT,SHARED_MSG,SENDMSG_NOOP,
- effectType,-1,null,0);
- }
该方法调用sendMsg()方法,传入一下参数:(mAudioHandler, 7, -1, 1, 0, -1, null, 0),sendMsg()方法如下:
- privatestaticvoidsendMsg(Handlerhandler,intbaseMsg,intstreamType,
- intexistingMsgPolicy,intarg1,intarg2,Objectobj,intdelay){
- intmsg=(streamType==SHARED_MSG)?baseMsg:getMsg(baseMsg,streamType);
- if(existingMsgPolicy==SENDMSG_REPLACE){
- handler.removeMessages(msg);
- }elseif(existingMsgPolicy==SENDMSG_NOOP&&handler.hasMessages(msg)){
- Log.d(TAG,"sendMsg:Msg"+msg+"existed!");
- return;
- }
- handler
- .sendMessageDelayed(handler.obtainMessage(msg,arg1,arg2,obj),delay);
- }
该方法就是将传入的参数经过计算,obtain一个message,message的what = 7 arg1 = 0 arg2 = -1 object = null; 处理该消息的地方handleMessage ():
- @Override
- publicvoidhandleMessage(Messagemsg){
- intbaseMsgWhat=getMsgBase(msg.what);
- switch(baseMsgWhat){
- ...
- caseMSG_PLAY_SOUND_EFFECT:
- playSoundEffect(msg.arg1,msg.arg2);
- break;
- ...
- }
调用了带两个参数的playSoundEffect()函数,传入参数 0,-1:
- privatevoidplaySoundEffect(inteffectType,intvolume){
- synchronized(mSoundEffectsLock){
- if(mSoundPool==null){
- return;
- }
- floatvolFloat;
- //useSTREAM_MUSICvolumeattenuatedby3dBifvolumeisnotspecifiedbycaller
- if(volume<0){
- //以下计算播放的音量大小:
- //SamelineartologconversionasinnativeAudioSystem::linearToLog()(AudioSystem.cpp)
- floatdBPerStep=(float)((0.5*100)/MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
- intmusicVolIndex=(mStreamStates[AudioSystem.STREAM_MUSIC].mIndex+5)/10;
- floatmusicVoldB=dBPerStep*(musicVolIndex-MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
- volFloat=(float)Math.pow(10,(musicVoldB-3)/20);
- }else{
- volFloat=(float)volume/1000.0f;
- }
- if(SOUND_EFFECT_FILES_MAP[effectType][1]>0){
- mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],volFloat,volFloat,0,0,1.0f);//调用该函数播放按键音
- }else{
- MediaPlayermediaPlayer=newMediaPlayer();
- if(mediaPlayer!=null){
- try{
- StringfilePath=Environment.getRootDirectory()+SOUND_EFFECTS_PATH+SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
- mediaPlayer.setDataSource(filePath);
- mediaPlayer.setAudioStreamType(AudioSystem.STREAM_RING);
- mediaPlayer.prepare();
- mediaPlayer.setVolume(volFloat,volFloat);
- mediaPlayer.setOnCompletionListener(newOnCompletionListener(){
- publicvoidonCompletion(MediaPlayermp){
- cleanupPlayer(mp);
- }
- });
- mediaPlayer.setOnErrorListener(newOnErrorListener(){
- publicbooleanonError(MediaPlayermp,intwhat,intextra){
- cleanupPlayer(mp);
- returntrue;
- }
- });
- mediaPlayer.start();
- }catch(IOExceptionex){
- Log.w(TAG,"MediaPlayerIOException:"+ex);
- }catch(IllegalArgumentExceptionex){
- Log.w(TAG,"MediaPlayerIllegalArgumentException:"+ex);
- }catch(IllegalStateExceptionex){
- Log.w(TAG,"MediaPlayerIllegalStateException:"+ex);
- }
- }
- }
- }
- }
因为传入的参数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()函数:
- publicAudioService(Contextcontext){
- ......
- loadSoundEffects();
- .......
- }
loadSoundEffects()函数的具体实现如下:
- publicbooleanloadSoundEffects(){
- synchronized(mSoundEffectsLock){
- if(mSoundPool!=null){
- returntrue;
- }
- mSoundPool=newSoundPool(NUM_SOUNDPOOL_CHANNELS,AudioSystem.STREAM_SYSTEM,0);
- ......
- returntrue;
- }
在此函数中,初始化了该SoundPool,类型为STREAM_SYSTEM。
到这里,结果出来了,android中,view的按键音类型为系统音频(STREAM_SYSTEM),而音量的大小与媒体音量(STREAM_MUSIC)绑定了起来。
更多相关文章
- Android(安卓)使用Visualizer获取播放音频的频率
- android 显示系统
- Android媒体扫描详细解析之二(MediaScanner & MediaProvider)
- Android(安卓)Hook框架Xposed详解:从源代码分析到开发指南
- android 背光驱动
- android ViewTreeObserver详细讲解
- cocos2dx中利用xcode 调用java中的函数
- 初学Android,音频管理器之控制音频(六十六)
- Android音频系统之AudioFlinger(二)