Android关于view按键音的修改

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

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

  1. /* The audio stream for phone calls */  
  2.     public static final int STREAM_VOICE_CALL = 0;//通话连接时的音频流(通话声)   
  3.     /* The audio stream for system sounds */  
  4.     public static final int STREAM_SYSTEM = 1;//系统音频流   
  5.     /* The audio stream for the phone ring and message alerts */  
  6.     public static final int STREAM_RING = 2;//来电铃声   
  7.     /* The audio stream for music playback */  
  8.     public static final int STREAM_MUSIC = 3;//媒体音频流   
  9.     /* The audio stream for alarms */  
  10.     public static final int STREAM_ALARM = 4;//闹钟音频流   
  11.     /* The audio stream for notifications */  
  12.     public static final int STREAM_NOTIFICATION = 5;//通知音频流   
  13.     /* @hide The audio stream for phone calls when connected on bluetooth */  
  14.     public static final int STREAM_BLUETOOTH_SCO = 6;//从注释上看时使用蓝牙耳机通话的音频流   
  15.     /* @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */  
  16.     public static final int STREAM_SYSTEM_ENFORCED = 7;//一些国家强制使用的音频流??不太明白   
  17.     /* @hide The audio stream for DTMF tones */  
  18.     public static final int STREAM_DTMF = 8;//DTMF音频流   
  19.     /* @hide The audio stream for text to speech (TTS) */  
  20.     public static final int STREAM_TTS = 9;//TTS: Text to Speech:文件到语言的音频流,即机器说话   
  21.     /* @hide The audio stream for Fm */  
  22.     public static final int STREAM_FM = 10;//FM的音频流   
  23.     /* @hide The audio stream for MATV */  
  24.     public static final int STREAM_MATV = 11;//TV的音频流  
/* 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的音频流

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


  1. /** @hide Maximum volume index values for audio streams */  
  2.  private int[] MAX_STREAM_VOLUME = new int[] {  
  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.  };  
   /** @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()方法中有如下代码:


  1. switch (event.getAction()) {  
  2.                case MotionEvent.ACTION_UP:  
  3.                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  4.                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  5.                        // take focus if we don't have it already and we should in   
  6.                        // touch mode.   
  7.                        boolean focusTaken = false;  
  8.                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  9.                            focusTaken = requestFocus();  
  10.                        }  
  11.   
  12.                        if (!mHasPerformedLongPress) {  
  13.                            // This is a tap, so remove the longpress check   
  14.                            removeLongPressCallback();  
  15.   
  16.                            // Only perform take click actions if we were in the pressed state   
  17.                            if (!focusTaken) {  
  18.                                // Use a Runnable and post this rather than calling   
  19.                                // performClick directly. This lets other visual state   
  20.                                // of the view update before click actions start.   
  21.                                if (mPerformClick == null) {  
  22.                                    mPerformClick = new PerformClick();  
  23.                                }  
  24.                                if (!post(mPerformClick)) {  
  25.                                    performClick();//这里响应click事件   
  26.                                }  
  27.                            }  
  28.                        }  
  29.   
  30.                        if (mUnsetPressedState == null) {  
  31.                            mUnsetPressedState = new UnsetPressedState();  
  32.                        }  
  33.   
  34.                        if (prepressed) {  
  35.                            mPrivateFlags |= PRESSED;  
  36.                            refreshDrawableState();  
  37.                            postDelayed(mUnsetPressedState,  
  38.                                    ViewConfiguration.getPressedStateDuration());  
  39.                        } else if (!post(mUnsetPressedState)) {  
  40.                            // If the post failed, unpress right now   
  41.                            mUnsetPressedState.run();  
  42.                        }  
  43.                        removeTapCallback();  
  44.                    }  
  45.                    break;  
 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()函数当中,继续看该函数具体做了什么:


  1. public boolean performClick() {  
  2.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  3.   
  4.     if (mOnClickListener != null) {  
  5.         playSoundEffect(SoundEffectConstants.CLICK);  
  6.         mOnClickListener.onClick(this);  
  7.         return true;  
  8.     }  
  9.   
  10.     return false;  
  11. }  
    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:


  1. public class SoundEffectConstants  
  2. {  
  3. SoundEffectConstants() { throw new RuntimeException("Stub!"); }  
  4. public static  int getContantForFocusDirection(int direction) { throw new RuntimeException("Stub!"); }  
  5. public static final int CLICK = 0;  
  6. public static final int NAVIGATION_LEFT = 1;  
  7. public static final int NAVIGATION_UP = 2;  
  8. public static final int NAVIGATION_RIGHT = 3;  
  9. public static final int NAVIGATION_DOWN = 4;  
  10. }  
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 ()的具体内容如下:


  1. public void playSoundEffect(int soundConstant) {  
  2.     if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {  
  3.         return;  
  4.     }  
  5.     mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);  
  6. }  
    public void playSoundEffect(int soundConstant) {
        if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
            return;
        }
        mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
    }

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


  1. /** 
  2.     * A set of information given to a view when it is attached to its parent 
  3.     * window. 
  4.     */  
  5.    static class AttachInfo {  
  6.        interface Callbacks {  
  7.            void playSoundEffect(int effectId);  
  8.            boolean performHapticFeedback(int effectId, boolean always);  
  9.        }  
 /**
     * 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的实现该回调接口:


  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {

具体的playSoundEffect()函数内容:


  1. public void playSoundEffect(int effectId) {  
  2.         checkThread();  
  3.   
  4.         try {  
  5.             final AudioManager audioManager = getAudioManager();  
  6.   
  7.             switch (effectId) {  
  8.                 case SoundEffectConstants.CLICK:  
  9.                     audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);  
  10.                     return;  
  11.                 case SoundEffectConstants.NAVIGATION_DOWN:  
  12.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);  
  13.                     return;  
  14.                 case SoundEffectConstants.NAVIGATION_LEFT:  
  15.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);  
  16.                     return;  
  17.                 case SoundEffectConstants.NAVIGATION_RIGHT:  
  18.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);  
  19.                     return;  
  20.                 case SoundEffectConstants.NAVIGATION_UP:  
  21.                     audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);  
  22.                     return;  
  23.                 default:  
  24.                     throw new IllegalArgumentException("unknown effect id " + effectId +  
  25.                             " not defined in " + SoundEffectConstants.class.getCanonicalName());  
  26.             }  
  27.         } catch (IllegalStateException e) {  
  28.             // Exception thrown by getAudioManager() when mView is null   
  29.             Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);  
  30.             e.printStackTrace();  
  31.         }  
  32.     }  
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()方法:


  1. public void  playSoundEffect(int effectType) {  
  2.     if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {  
  3.         return;  
  4.     }  
  5.   
  6.     if (!querySoundEffectsEnabled()) {  
  7.         return;  
  8.     }  
  9.   
  10.     IAudioService service = getService();  
  11.     try {  
  12.         service.playSoundEffect(effectType);  
  13.     } catch (RemoteException e) {  
  14.         Log.e(TAG, "Dead object in playSoundEffect"+e);  
  15.     }  
  16. }  
    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中:


  1. /** @see AudioManager#playSoundEffect(int) */  
  2. public void playSoundEffect(int effectType) {  
  3.     sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,  
  4.             effectType, -1null0);  
  5. }  
    /** @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()方法如下:


  1. private static void sendMsg(Handler handler, int baseMsg, int streamType,  
  2.         int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {  
  3.     int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType);  
  4.   
  5.     if (existingMsgPolicy == SENDMSG_REPLACE) {  
  6.         handler.removeMessages(msg);  
  7.     } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {  
  8.         Log.d(TAG, "sendMsg: Msg " + msg + " existed!");  
  9.         return;  
  10.     }  
  11.   
  12.     handler  
  13.             .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);  
  14. }  
    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 ():


  1.  @Override  
  2.         public void handleMessage(Message msg) {  
  3.             int baseMsgWhat = getMsgBase(msg.what);  
  4.   
  5.             switch (baseMsgWhat) {  
  6.   
  7. ...  
  8.   
  9.                 case MSG_PLAY_SOUND_EFFECT:  
  10.                     playSoundEffect(msg.arg1, msg.arg2);  
  11.                     break;  
  12. ...  
  13.   
  14. }  
 @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:


  1. private void playSoundEffect(int effectType, int volume) {  
  2.     synchronized (mSoundEffectsLock) {  
  3.   
  4.         if (mSoundPool == null) {  
  5.             return;  
  6.         }  
  7.         float volFloat;  
  8.         // use STREAM_MUSIC volume attenuated by 3 dB if volume is not specified by caller   
  9.         if (volume < 0) {  
  10.             //以下计算播放的音量大小:   
  11.                  // Same linear to log conversion as in native AudioSystem::linearToLog() (AudioSystem.cpp)   
  12.             float dBPerStep = (float)((0.5 * 100) / MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);  
  13.             int musicVolIndex = (mStreamStates[AudioSystem.STREAM_MUSIC].mIndex + 5) / 10;  
  14.             float musicVoldB = dBPerStep * (musicVolIndex - MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);  
  15.             volFloat = (float)Math.pow(10, (musicVoldB - 3)/20);  
  16.         } else {  
  17.             volFloat = (float) volume / 1000.0f;  
  18.         }  
  19.         if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {  
  20.   
  21.             mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], volFloat, volFloat, 001.0f);//调用该函数播放按键音   
  22.         } else {  
  23.           
  24.             MediaPlayer mediaPlayer = new MediaPlayer();  
  25.             if (mediaPlayer != null) {  
  26.                 try {  
  27.                     String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];  
  28.                     mediaPlayer.setDataSource(filePath);  
  29.                     mediaPlayer.setAudioStreamType(AudioSystem.STREAM_RING);  
  30.                     mediaPlayer.prepare();  
  31.                     mediaPlayer.setVolume(volFloat, volFloat);  
  32.                     mediaPlayer.setOnCompletionListener(new OnCompletionListener() {  
  33.                         public void onCompletion(MediaPlayer mp) {  
  34.                             cleanupPlayer(mp);  
  35.                         }  
  36.                     });  
  37.                     mediaPlayer.setOnErrorListener(new OnErrorListener() {  
  38.                         public boolean onError(MediaPlayer mp, int what, int extra) {  
  39.                             cleanupPlayer(mp);  
  40.                             return true;  
  41.                         }  
  42.                     });  
  43.                     mediaPlayer.start();  
  44.                 } catch (IOException ex) {  
  45.                     Log.w(TAG, "MediaPlayer IOException: "+ex);  
  46.                 } catch (IllegalArgumentException ex) {  
  47.                     Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);  
  48.                 } catch (IllegalStateException ex) {  
  49.                     Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);  
  50.                 }  
  51.             }  
  52.         }  
  53.     }  
  54. }  
        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()函数:


  1. public AudioService(Context context) {  
  2. ......  
  3.         loadSoundEffects();  
  4. .......  
  5.     }  
public AudioService(Context context) {
......
        loadSoundEffects();
.......
    }


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


  1.    public boolean loadSoundEffects() {  
  2.        synchronized (mSoundEffectsLock) {  
  3.            if (mSoundPool != null) {  
  4.                return true;  
  5.            }  
  6.            mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);  
  7.   
  8.           ......  
  9.   
  10.        return true;  
  11. }  
    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)绑定了起来。




  1. <PRE class=java name="code"><PRE></PRE>  
  2. <PRE></PRE>  
  3. <PRE></PRE>  
  4. <PRE></PRE>  
  5. <PRE></PRE>  
  6. <PRE></PRE>  
  7. <PRE></PRE>  
  8. <PRE></PRE>  
  9. <PRE></PRE>  
  10. <PRE></PRE>  
  11. <PRE></PRE>  
  12. <PRE></PRE>  
  13. <PRE></PRE>  
  14. <PRE></PRE>  
  15. <PRE></PRE>  
  16.   
  17. </PRE>  

  
  
  1. <PRE></PRE>  
  2. <PRE></PRE>  
  3. <PRE></PRE>  
  4. <PRE></PRE>  
  5. <PRE></PRE>  
  6. <PRE></PRE>  
  7. <PRE></PRE>  
  8. <PRE></PRE>  
  9. <PRE></PRE>  
  10. <PRE></PRE>  
  11. <PRE></PRE>  
  12. <PRE></PRE>  
  13. <PRE></PRE>  
  14. <PRE></PRE>  
  15. <PRE></PRE>  
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
android开发秘籍完整版清晰版 第1 章 android 概述   1 1.1 android 演化史  1 1.2 android 的两面性  2 1.3 运行android 的设备   2 1.3.1 htc 系列机型   4 1.3.2 摩托罗拉系列机型   4 1.3.3 三星系列机型   4 1.3.4 平板电脑   5 1.3.5 其他设备   5 1.4 android 设备的硬件差异   5 1.4.1 屏幕  5 1.4.2 用户输入方式   6 1.4.3 传感器   6 1.5 android 的特点   8 1.5.1 多进程和应用程序微件   8 1.5.2 触摸、手势和多点触控   8 1.5.3 硬键盘和软键盘   8 1.6 android 开发   8 1.6.1 如何使用本书中的秘诀   8 .1.6.2 好好设计应用程序   9 1.6.3 保持向前兼容   9 1.6.4 健壮性  10 1.7 软件开发工具包   10 1.7.1 安装与更新   10 1.7.2 软件特性和api 级别   11 1.7.3 利用模拟器或真机调试程序   12 1.7.4 使用android 调试桥   13 1.7.5 签名和发布应用   14 1.8 android market    14 1.8.1 最终用户许可协议   14 1.8.2 提升应用程序的曝光率   15 1.8.3 脱颖而出   15 1.8.4 为应用程序收费   15 1.8.5 管理评论和更新   16 1.8.6 android market 的候补之选   17 第2 章 应用程序基础知识:activity 和intent    18 2.1 android 应用程序预览   18 2.1.1 秘诀1:创建工程并新建activity    19 2.1.2 工程目录结构及自动生成内容   20 2.1.3 android 包和manifest 清单文件   22 2.1.4 重命名应用程序中的部分文件   23 2.2 activity 的生命周期   23 2.2.1 秘诀2:使用其他的生命周期方法  24 2.2.2 秘诀3:强制执行单任务模式   26 2.2.3 秘诀4:强制屏幕方向   26 2.2.4 秘诀5:保存和恢复activity信息   27 2.3 多个activity   28 2.3.1 秘诀6:使用按钮和文本框   28 2.3.2 秘诀7:通过事件启动另外一个activity    29 2.3.3 秘诀8:将语转换成文本并启动activity 显示结果   32 2.3.4 秘诀9:实现选择列表   34 2.3.5 秘诀10:使用隐式intent 创建activity    35 2.3.6 秘诀11:在activity 间传递基本数据类型   37 第3 章 线程、服务、receiver 以及alert 对话框   40 3.1 线程   40 3.1.1 秘诀12:启动一个辅助线程  40 3.1.2 秘诀13:创建实现runnable接口的activity    44 3.1.3 秘诀14:设置线程优先级   45 3.1.4 秘诀15:取消线程   45 3.1.5 秘诀16:在两个应用程序之间共享线程   46 3.2 线程之间的消息机制:handler   46 3.2.1 秘诀17:从主线程调度runnable 任务   46 3.2.2 秘诀18:使用倒数计时器   49 3.2.3 秘诀19:处理耗时的初始化工作  50 3.3 服务  51 3.4 添加broadcast receiver    56 3.5 应用微件   58 3.6 alert 对话框   60 3.6.1 秘诀23:使用toast 在屏幕上显示简短消息   61 3.6.2 秘诀24:使用alert 对话框   61 3.6.3 秘诀25:在状态栏中显示通知   62 第4 章 用户界面布局   65 4.1 资源目录及其基本属性   65 4.2 viewviewgroup   67 4.2.1 秘诀27:利用eclipse 编辑器生成布局   68 4.2.2 秘诀28:控制ui 元素的宽度和高度   71 4.2.3 秘诀29:设置相对布局和布局id   73 4.2.4 秘诀30:通过编程声明布局   74 4.2.5 秘诀31:使用独立线程更新布局  75 4.3 文本操作   78 4.3.1 秘诀32:设置和更改文本属性   79 4.3.2 秘诀33:提供文本输入   81 4.3.3 秘诀34:创建表单  82 4.4 其他控件:从按钮到拖动条   83 4.4.1 秘诀35:在表格布局中使用图像按钮   83 4.4.2 秘诀36:使用复选框和开关按钮   86 4.4.3 秘诀37:使用单选按钮   90 4.4.4 秘诀38:创建下拉菜单   90 4.4.5 秘诀39:使用进度条   92 4.4.6 秘诀40:使用拖动条   94 第5 章 用户界面事件   97 5.1 事件处理器和事件监听器   97 5.1.1 秘诀41:截取物理按键事件  97 5.1.2 秘诀42:创建菜单   100 5.1.3 秘诀43:在xml 文件中定义菜单   104 5.1.4 秘诀44:使用搜索键   105 5.1.5 秘诀45:响应触摸事件   107 5.1.6 秘诀46:监听滑动手势   109 5.1.7 秘诀47:使用多点触控   110 5.2 高级用户界面库  113 5.2.1 秘诀48:使用手势   114 5.2.2 秘诀49:绘制3d 图像   117 第6 章 多媒体技术   122 6.1 图像   123 6.2 频   128 6.2.1 秘诀51:选取和播放频文件   128 6.2.2 秘诀52:录制频文件   131 6.2.3 秘诀53:处理原始频   132 6.2.4 秘诀54:有效使用频资源   136 6.2.5 秘诀55:添加媒体资源并更新路径   137 6.3 视频  138 第7 章 硬件接口   140 7.1 照相机   140 7.2 其他传感器   145 7.2.1 秘诀57:获取设备旋转姿态  146 7.2.2 秘诀58:使用温度传感器和光传感器   149 7.3 电话  150 7.3.1 秘诀59:使用电话管理器  150 7.3.2 秘诀60:监听电话状态   152 7.3.3 秘诀61:拨打电话号码   154 7.4 蓝牙  154 7.4.1 秘诀62:打开蓝牙   155 7.4.2 秘诀63:搜索蓝牙设备   155 7.4.3 秘诀64:与已绑定的蓝牙设备配对   156 7.4.4 秘诀65:打开蓝牙套接字  156 7.4.5 秘诀66:使用设备振动功能   159 7.4.6 秘诀67:访问无线网络   159 第8 章 网络通信   161 8.1 使用短信息   161 8.2 使用web 内容   169 8.2.1 秘诀69:定制web 浏览器   169 8.2.2 秘诀70:使用http get请求  170 8.2.3 秘诀71:使用http post请求  174 8.3 社交网络  174 第9 章 数据存储方法   184 9.1 shared preferences   184 9.1.1 秘诀73:创建和检索sharedpreferences    184 9.1.2 秘诀74:使用preferences框架   185 9.1.3 秘诀75:基于stored data改变用户界面   187 9.1.4 秘诀76:添加最终用户许可协议   190 9.2 sqlite 数据库   194 9.2.1 秘诀77:创建一个独立的数据库包   194 9.2.2 秘诀78:使用独立的数据库包   197 9.2.3 秘诀79:创建个人日记   200 9.3 内容提供器  204 9.4 保存和载入文件   209 第10 章 基于位置的服务   210 10.1 位置服务入门   210 10.1.1 秘诀81:获取最新位置   212 10.1.2 秘诀82:在位置改变时更新信息   212 10.1.3 秘诀83:列出所有可用的提供器   214 10.1.4 秘诀84:将位置解析为地址(反向地理编码)   216 10.1.5 秘诀85:将地址解析为位置(地理编码)    218 10.2 使用谷歌地图   220 10.2.1 秘诀86:在应用程序中添加谷歌地图   222 10.2.2 秘诀87:在地图上添加标记   224 10.2.3 秘诀88:在地图上添加视图   228 10.2.4 秘诀89:在地图上标记设备的当前位置   230 10.2.5 秘诀90:设置临近警告   231 第11 章 android 高级开发技术   232 11.1 android 的自定义视图   232 11.2 android 的原生组件   238 11.3 android 的安全机制   241 11.4 android 的进程间通信   242 11.5 android 的备份管理器   247 11.5.1 秘诀95:备份运行时数据   247 11.5.2 秘诀96:备份文件到云端   248 11.5.3 秘诀97:触发备份与还原操作   249 11.6 android 的动画功能   250 第12 章 调试   255 12.1 eclipse 内置的调试工具   255 12.1.1 秘诀99:设置运行配置   255 12.1.2 秘诀100:使用ddms   256 12.1.3 秘诀101:断点调试   257 12.2 android sdk 中的调试工具   258 12.2.1 秘诀102:使用android debug bridge 工具   258 12.2.2 秘诀103:使用logcat 工具   259 12.2.3 秘诀104:使用hierarchyviewer 工具   261 12.2.4 秘诀105:使用traceview工具   262 12.3 android 系统调试工具   264
0、ANDROID常用类库说明 6 1、ANDROID文件系统与应用程序架构 7 1.1、ANDROID 文件系统 7 1.2、ANDROID应用程序架构 9 2、ANDROID应用程序结构 11 2.1、ACTIVITY 12 2.1.1、概述 12 2.1.2、Activity的生命周期 15 2.1.3、Activity 的创建 16 2.1.4、Activity 的跳转(含Bundle传值) 17 2.1.5.Actvity 堆栈 18 2.1.6、Intent对象调用Activity实例 19 2.1.7、Activity透明 21 2.1.8、一次性关闭所有的Activity 22 2.1.9、PreferenceActivity 用法 22 2.1.10、Dialog风格的Activity 23 2.1.11、横竖屏切换不销毁当前Activity 23 2.2、INTENT RECEIVER 25 2.3、SERVICE 26 2.3.1、什么是Service 26 2.3.2、如何使用Service 27 2.3.3、Service的生命周期 32 2.3.4、判断服务开启状态 33 2.3.5、获取启动的服务 34 2.4、CONTENT PROVIDER 35 3、ANDROID UI LAYOUT 35 3.1、概述 35 3.2、线性布局(LINEAR LAYOUT) 36 3.3、相对布局(RELATIVE LAYOUT) 39 3.4、TABLELAYOUT 40 3.5、ABSOLUTELAYOUT 47 4、ANDROID UI 控件 48 4.1、IMAGEBUTTON 48 4.1.1、图案填充问题 48 4.2、TEXTVIEW 49 4.2.1、动态滚动 49 4.3、EDITTEXT 49 4.3.1、光标选择 49 4.4、TITLEBAR 50 4.4.1、非全屏状态下不显示title标题栏 50 4.4.2、标题栏进度指示器 50 4.4.3、titleBar 高级实现方法(更美观) 51 4.4.4、获取标题栏和状态栏高度 57 4.4.5、标题栏显示简单的进度框 57 4.5、MENU 58 4.5.1、简单的代码 58 4.5.2、menu实现的两种方法 58 4.5.3、自定义MENU背景 62 4.5.4、触发menu 64 4.5.5、Context Menu和Options Menu菜单的区别 64 4.5.6、Context menus for expandable lists 64 4.6、LISTVIEW 66 4.6.1、ListView自定义分割线 66 4.6.2、LIST例一 66 4.6.3、LIST例二 76 4.6.4、LIST例三 80 4.6.5、ListView 被选中item的背景颜色 82 4.6.6、ListView自定义背景颜色 83 4.6.7、List长按与短按消息映射 84 4.6.8、点击ListView改变背景色 87 4.6.9、自动滚动ListView 88 4.6.10、BaseExpandableListAdapter例 88 4.6.11、列表视图(List View) 96 4.6.12、NoteList 99 4.7、TAB与TABHOST 106 4.8、RATINGBAR 110 4.8.1、例一 110 4.8.2、例二 112 4.9、DATE/TIME SET 115 4.9.1、DatePicker/TimePicker 115 4.9.2、DatePickerDialog/TimePickerDialog 119 4.10、WEBVIEW 120 4.10.1、WebView的使用 120 4.11、SCROLLVIEW 121 4.11.1、ScrollView的使用 121 4.12、GRIDVIEW 124 4.12.1、GridView的使用 124 4.13、GAMEVIEW 127 4.13.1、显示到一个布局中 127 4.14、TOASTE 128 4.14.1、短时间显示 128 4.14.2、长时间显示 128 4.15、对话框 128 4.15.1、简单的对话框: 128 4.15.2、包含两个按钮的对话框 128 4.15.3、三个按钮的提示框 129 4.15.4、包含输入的dlg 131 4.15.5、圆形进度框 133 4.15.6、AlertDialog.Builder 133 4.15.7、模式对话框 134 4.16、拖动BUTTON获得位置 135 5、ANDROID UI 美化 137 5.1、简单美化BUTTON、IMAGEBUTTON、TEXTVIEW等控件 137 5.2、BUTTON美化案例☆ 139 5.3、IMAGEBUTTON 按下时的动画效果 142 5.4、滚动条显示与隐藏 143 5.5、LISTVIEW 与 SCROLLVIEW 解决办法 144 方法一:(重写ListView) 144 方法二: 150 5.6、3D魔方 151 6、ANDROID UI 动画 160 6.1、四种2D动画 160 6.1.1、透明度控制动画效果 alpha 160 6.1.2、旋转动画效果 rotate 161 6.1.3、尺寸伸缩动画效果 scale 162 6.1.4、位置转移动画效果 translate 163 6.1.5、四种动画效果的调用 164 7、异步调用 167 开辟一个线程: 167 THREAD: 168 HANDER 170 TIMER 173 ANDROID 界面刷新 174 MESSAGE HANDER 175 用法: 175 线程与子线程调用MessageHander 177 Messagehandler实例 177 8、数据存储与读取 179 1. PREFERENCES 179 2. FILES 180 3. DATABASES 180 4. NETWORK 183 5、CONTENTPROVIDER 183 6、执行SQL语句进行查询 188 用法1 188 其它: 188 详解: 189 查看SQLITE表格内容 192 9、常用功能的实现 193 9.1、获取手机型号以及系统版本号 193 9.2、更改应用程序图标 194 9.3、迎合不同的手机分辨率 194 9.4.ANDROID屏幕适应的四个原则 195 9.5、ANDROID常用单位 196 9.6、取得屏幕信息 197 9.7、横竖屏 197 9.8、程序完全全屏 200 9.8.1锁屏锁键盘 200 9.9、程序的开机启动 201 9.10、动态START页面 208 9.11、彻底退出当前程序 212 9.12、获取应用程序的名称,包名,版本号和图标 212 9.13、调用ANDROID INSTALLER 安装和卸载程序 215 9.14、后台监控应用程序包的安装&卸载 216 9.15、显示应用详细列表 224 9.16、寻找应用 224 9.17、注册一个BROADCASTRECEIVER 225 9.18、打开另一程序 225 9.19、播放默认铃声 225 9.20、设置默认来电铃声 226 9.21、位图旋转 227 9.22、手机震动控制 228 9.23、SENSOR2D感应实例 228 9.24、运用JAVA MAIL包实现发GMAIL邮件 230 9.26、ANDROID键盘响应 236 9.27、后台监听某个按键 238 9.28、VECTOR用法 239 9.29、CURSOR 242 9.30、把一个字符串写进文件 244 9.31、把文件内容读出到一个字符串 245 9.32、扫描WIFI热点演示实例教程 246 9.33、调用GOOGLE搜索 249 9.34、调用浏览器 载入某网址 249 9.35、获取 IP地址 249 9.36、从输入流中获取数据并以字节数组返回 250 9.37、通过ANDROID 客户端上传数据到服务器 251 9.38、文件下载类 255 9.39、下载文件的进度条提示 263 9.40、通过HTTPCLIENT从指定SERVER获取数据 265 9.41、通过FTP传输文件,关闭UI获得返回码 266 9.42、激活JAVASCRIPT打开内部链接 266 9.43、清空手机COOKIES 267 9.44、检查SD卡是否存在并且可以写入 267 9.45、获取SD卡的路径和存储空间 268 9.46、将程序安装到SD卡 268 9.47、创建一个SD映像 269 9.48、查看手机内存存储 269 9.49、在模拟器上调试GOOGLE MAPS 271 9.50、建立GPRS连接 273 9.51、获取手机位置 274 9.5* 获得经纬度,地名标注在地图上 274 9.52、获得两个GPS坐标之间的距离 276 9.53、通过经纬度显示地图 277 9.54、路径规划 277 9.55、将坐标传递到GOOGLE MAP并显示 277 9.56、获取本机电话号码 280 9.57、获得手机联系人 280 9.58、2.0以上版本查询联系人详细信息 282 9.59、2.0以上版本添加联系人 285 9.60、拨打电话 287 9.61、发送SMS、MMS 287 9.62、监听电话被呼叫状态 288 9.63、监听要拨打的电话(可以后台进行修改号码) 290 9.64、后台监听短信内容 291 9.65、删除最近收到的一条短信 292 9.66、调用发短信的程序 293 9.67、后台发送短信 293 9.68、调用发送彩信程序 294 9.69、发送EMAIL 294 9.70、播放多媒体 295 9.71、控制量 296 9.72、定义CONTENTOBSERVER,监听某个数据表 302 9.73、打开照相机 303 9.74、从GALLERY选取图片 303 9.75、打开录机 303 9.76、语朗读 303 9.77、手机获取视频流显示在电脑上 305 9.78、蓝牙的使用 313 9.79、一个很好的加密解密字符串 316 9.80、DRAWABLE、BITMAP、BYTE[]之间的转换 318 9.81、高循环效率的代码 320 9.82、给模拟器打电话发短信 321 9.83、加快模拟器速度 321 9.83.1、模拟器 “尚未注册网络” 322 9.84、EMULATOR命令行参数 322 9.85、如何进行单元测试 323 9.86、ANDROID自动化测试初探 324 9.86.1、捕获Activity上的Element 324 9.86.2、Hierarchyviewer 捕获Element的 328 9.86.3、架构实现 330 9.86.4、模拟键盘鼠标事件(Socket+Instrumentation实现) 332 9.86.5、再述模拟键盘鼠标事件(adb shell 实现) 334 9.87、反编译APK 344 9.88、更换APK图标(签名打包) 348 9.89、利用ANDROID MARKET赚钱 363 9.90、ANDROID-MARKET 使用 365 9.91、传感器 369 9.91.1、获取手机上的传感器 369 9.91.2、 371 9.92、时间类 372 * 获得日期或时间字符串 372 * num天前的日期 373 * num天后的日期 373 * 判断 thingdate 的 dotime 天后是否在今天之后 374 * 判断testDate+testTime是否在两个时间之内 375 附录: 378 附录1、XML布局中的常用属性 378 1.通用属性 378 2.Edit Text部分属性 381 3.layout_alignParentRight android:paddingRight 384 附录2、INTENT ACTION 385 附录3、ANDROID的动作、广播、类别等标志 387 ★★★附带工具包说明 393 1.APK反编译工具.rar 393 2.APK安装工具.rar 393

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值