Android关于view按键音的修改
2011-12-13 20:07
357 查看
首先简单介绍下预备知识:
1.Android的audio流的类型有以下12种:
每种音频流所规定的最大值:
2.所有的按键事件都是touch事件,这部分我会另外开篇博文介绍。
开始本文正文,Anndroid系统中所有View带有按键音,用户可以通过Settings>Sound>勾选Audible Selection即可开启按键音。但是有个奇怪的地方:此按键音是与媒体音量(即STREAM_MUSIC)绑定的,难道按键音的STREAM TYPE就是STREAM_MUSIC吗?我们从代码中寻找一下。
首先所有的View点击的时候都有按键音,我们从View.java的点击事件找起,在view的响应的onTouchEvent()方法中有如下代码:
处理click事件就写在performClick()函数当中,继续看该函数具体做了什么:
从这里可以看到与用户接口onClickListener结合起来了,当用户注册了clickListener,则调用发出按键音函数playSoundEffect ()和响应用户写好的clickListener的onClick()方法。这里playSoundEffect函数传的参数SoundEffectContants.CLICK为多少呢,从SoundEffectConstants.java可知SoundEffectConstants.CLICK = 0:
playSoundEffect ()的具体内容如下:
真正调用的是AttachInfo Callbacks接口的playSoundEffect()函数:
看注释可知其真正的方法写在parent window中,那parent window是哪个呢?ViewRoot的实现该回调接口:
具体的playSoundEffect()函数内容:
我们传入的参数为SoundEffectContants.CLICK,调用AudioManager的playSoundEffect()方法,参数为AudioManger.FX_KEY_CLICK,继续往下看,在AudioManager.java中playSoundEffect()方法:
调用了IAudioService的playSoundEffect() 方法,IAudioService方法是使用aidl生成的接口,aidl源文件:frameworks/base/media/java/android/media/IAudioService.aidl,真正响应的地方在AudioService.java中:
该方法调用sendMsg()方法,传入一下参数:(mAudioHandler, 7, -1, 1, 0, -1, null, 0),sendMsg()方法如下:
该方法就是将传入的参数经过计算,obtain一个message,message的what = 7 arg1 = 0 arg2 = -1 object = null; 处理该消息的地方handleMessage ():
调用了带两个参数的playSoundEffect()函数,传入参数 0,-1:
因为传入的参数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()函数:
loadSoundEffects()函数的具体实现如下:
在此函数中,初始化了该SoundPool,类型为STREAM_SYSTEM。
到这里,结果出来了,android中,view的按键音类型为系统音频(STREAM_SYSTEM),而音量的大小与媒体音量(STREAM_MUSIC)绑定了起来。
1.Android的audio流的类型有以下12种:
/* The audio stream for phone calls */ public static final int STREAM_VOICE_CALL = 0;//通话连接时的音频流(通话声) /* The audio stream for system sounds */ public static final int STREAM_SYSTEM = 1;//系统音频流 /* The audio stream for the phone ring and message alerts */ public static final int STREAM_RING = 2;//来电铃声 /* The audio stream for music playback */ public static final int STREAM_MUSIC = 3;//媒体音频流 /* The audio stream for alarms */ public static final int STREAM_ALARM = 4;//闹钟音频流 /* The audio stream for notifications */ public static final int STREAM_NOTIFICATION = 5;//通知音频流 /* @hide The audio stream for phone calls when connected on bluetooth */ public static final int STREAM_BLUETOOTH_SCO = 6;//从注释上看时使用蓝牙耳机通话的音频流 /* @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */ public static final int STREAM_SYSTEM_ENFORCED = 7;//一些国家强制使用的音频流??不太明白 /* @hide The audio stream for DTMF tones */ public static final int STREAM_DTMF = 8;//DTMF音频流 /* @hide The audio stream for text to speech (TTS) */ public static final int STREAM_TTS = 9;//TTS: Text to Speech:文件到语言的音频流,即机器说话 /* @hide The audio stream for Fm */ public static final int STREAM_FM = 10;//FM的音频流 /* @hide The audio stream for MATV */ public static final int STREAM_MATV = 11;//TV的音频流
每种音频流所规定的最大值:
/** @hide Maximum volume index values for audio streams */ private int[] MAX_STREAM_VOLUME = new int[] { 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()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick();//这里响应click事件 } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break;
处理click事件就写在performClick()函数当中,继续看该函数具体做了什么:
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
从这里可以看到与用户接口onClickListener结合起来了,当用户注册了clickListener,则调用发出按键音函数playSoundEffect ()和响应用户写好的clickListener的onClick()方法。这里playSoundEffect函数传的参数SoundEffectContants.CLICK为多少呢,从SoundEffectConstants.java可知SoundEffectConstants.CLICK = 0:
public class SoundEffectConstants { SoundEffectConstants() { throw new RuntimeException("Stub!"); } public static int getContantForFocusDirection(int direction) { throw new RuntimeException("Stub!"); } public static final int CLICK = 0; public static final int NAVIGATION_LEFT = 1; public static final int NAVIGATION_UP = 2; public static final int NAVIGATION_RIGHT = 3; public static final int NAVIGATION_DOWN = 4; }
playSoundEffect ()的具体内容如下:
public void playSoundEffect(int soundConstant) { if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) { return; } mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant); }
真正调用的是AttachInfo Callbacks接口的playSoundEffect()函数:
/** * A set of information given to a view when it is attached to its parent * window. */ static class AttachInfo { interface Callbacks { void playSoundEffect(int effectId); boolean performHapticFeedback(int effectId, boolean always); }
看注释可知其真正的方法写在parent window中,那parent window是哪个呢?ViewRoot的实现该回调接口:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks {
具体的playSoundEffect()函数内容:
public void playSoundEffect(int effectId) { checkThread(); try { final AudioManager audioManager = getAudioManager(); switch (effectId) { case SoundEffectConstants.CLICK: audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); return; case SoundEffectConstants.NAVIGATION_DOWN: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN); return; case SoundEffectConstants.NAVIGATION_LEFT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT); return; case SoundEffectConstants.NAVIGATION_RIGHT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT); return; case SoundEffectConstants.NAVIGATION_UP: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP); return; default: throw new IllegalArgumentException("unknown effect id " + effectId + " not defined in " + SoundEffectConstants.class.getCanonicalName()); } } catch (IllegalStateException e) { // Exception thrown by getAudioManager() when mView is null Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e); e.printStackTrace(); } }
我们传入的参数为SoundEffectContants.CLICK,调用AudioManager的playSoundEffect()方法,参数为AudioManger.FX_KEY_CLICK,继续往下看,在AudioManager.java中playSoundEffect()方法:
public void playSoundEffect(int effectType) { if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) { return; } if (!querySoundEffectsEnabled()) { return; } IAudioService service = getService(); try { service.playSoundEffect(effectType); } catch (RemoteException e) { Log.e(TAG, "Dead object in playSoundEffect"+e); } }
调用了IAudioService的playSoundEffect() 方法,IAudioService方法是使用aidl生成的接口,aidl源文件:frameworks/base/media/java/android/media/IAudioService.aidl,真正响应的地方在AudioService.java中:
/** @see AudioManager#playSoundEffect(int) */ public void playSoundEffect(int effectType) { sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP, effectType, -1, null, 0); }
该方法调用sendMsg()方法,传入一下参数:(mAudioHandler, 7, -1, 1, 0, -1, null, 0),sendMsg()方法如下:
private static void sendMsg(Handler handler, int baseMsg, int streamType, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType); if (existingMsgPolicy == SENDMSG_REPLACE) { handler.removeMessages(msg); } else if (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 public void handleMessage(Message msg) { int baseMsgWhat = getMsgBase(msg.what); switch (baseMsgWhat) { ... case MSG_PLAY_SOUND_EFFECT: playSoundEffect(msg.arg1, msg.arg2); break; ... }
调用了带两个参数的playSoundEffect()函数,传入参数 0,-1:
private void playSoundEffect(int effectType, int volume) { synchronized (mSoundEffectsLock) { if (mSoundPool == null) { return; } float volFloat; // use STREAM_MUSIC volume attenuated by 3 dB if volume is not specified by caller if (volume < 0) { //以下计算播放的音量大小: // Same linear to log conversion as in native AudioSystem::linearToLog() (AudioSystem.cpp) float dBPerStep = (float)((0.5 * 100) / MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]); int musicVolIndex = (mStreamStates[AudioSystem.STREAM_MUSIC].mIndex + 5) / 10; float musicVoldB = 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 { MediaPlayer mediaPlayer = new MediaPlayer(); if (mediaPlayer != null) { try { String filePath = 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(new OnCompletionListener() { public void onCompletion(MediaPlayer mp) { cleanupPlayer(mp); } }); mediaPlayer.setOnErrorListener(new OnErrorListener() { public boolean onError(MediaPlayer mp, int what, int extra) { cleanupPlayer(mp); return true; } }); mediaPlayer.start(); } catch (IOException ex) { Log.w(TAG, "MediaPlayer IOException: "+ex); } catch (IllegalArgumentException ex) { Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex); } catch (IllegalStateException ex) { Log.w(TAG, "MediaPlayer IllegalStateException: "+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()函数:
public AudioService(Context context) { ...... loadSoundEffects(); ....... }
loadSoundEffects()函数的具体实现如下:
public boolean loadSoundEffects() { synchronized (mSoundEffectsLock) { if (mSoundPool != null) { return true; } mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0); ...... return true; }
在此函数中,初始化了该SoundPool,类型为STREAM_SYSTEM。
到这里,结果出来了,android中,view的按键音类型为系统音频(STREAM_SYSTEM),而音量的大小与媒体音量(STREAM_MUSIC)绑定了起来。
相关文章推荐
- Android关于view按键音的修改
- Android关于view按键音的修改
- 关于修改android原生键盘上按键大小的方法
- 关于Android自定义view的简单了解
- 关于Android ListView 多ItemView的问题
- Android开发中关于Xwalkview加载https网页出现安全证书ssl问题
- 关于android webview 图片使用同一个src导致只加载第一张的问题
- android中使用线程(比如修改textview的text)
- Android的Listview的ListAdapter关于View的经典写法
- Android -- 自定义View小Demo,关于Path类的使用(一)
- Android Launcher分析和修改8——AllAPP界面拖拽元素(PagedViewWithDraggableItems)
- 关于android开发时,发生Error infalting classa com.baidu.mapapi.map.MapView的解决办法
- 关于Android自定义view所需要知道的基本函数
- Android 关于view的getLayoutParams().width,getWidth(),getMeasuredWidth();
- android-修改TextView中部分文字的颜色
- 【Android游戏开发十九】(必看篇)SurfaceView运行机制详解—剖析Back与Home按键及切入后台等异常处理!
- Android杂谈(15)关于ViewPager里的Fragment的生命周期+懒加载
- 关于Android 尝试在onCreate方法内测量view的宽高的测试
- 关于android.view.WindowLeaked(窗体泄露)的解决方案
- 关于Android中View的分发机制的学习总结(View篇)