资讯专栏INFORMATION COLUMN

浅谈Android O Touch声音播放流程

XiNGRZ / 2595人阅读

摘要:前言当我们点击屏幕按键时,就会听到音,那么音是如何播放起来的呢,由于最近项目需求顺便熟悉下了音的逻辑。完成的绘制过程,包括过程。向分发收到的用户发起的事件,如按键,触屏等事件。总结音的流程就简单分析到这里,欢迎大家交流指正。

前言

当我们点击屏幕按键时,就会听到touch音,那么touch音是如何播放起来的呢,由于最近项目需求顺便熟悉下了touch音的逻辑。

正文

谈touch逻辑首先要说下这个类ViewRootImpl.java,位于frameworks/base/core/java/android/view下,ViewRootImpl的主要功能:
A:链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带。
B:完成View的绘制过程,包括measure、layout、draw过程。
C:向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。
关于ViewRootImpl的源码可参照博客ViewRootImpl类源码解析,我们从performFocusNavigation()入手

private boolean performFocusNavigation(KeyEvent event) {
    //略
    if (v.requestFocus(direction, mTempRect)) {
        playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
        return true;
    }
    //略
    return false;
}

当我们点击某个控件时,会先触发performFocusNavigation()这个方法,然后当控件获取到focus后便会调用playSoundEffect()方法,我只截取了performFocusNavigation()中关键代码playSoundEffect()部分,来看下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(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
            e.printStackTrace();
        }
    }

发现调用了audioManager的playSoundEffect()方法,audiomanager就不说了,接触android audio最先接触的可能就是AudioManager了,音量控制,声音焦点申请等。接着看

    public void  playSoundEffect(int effectType) {
        if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
            return;
        }
        //查询是否开启touch音,如果settings中关闭了,则直接返回
        if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) {
            return;
        }

        final IAudioService service = getService();
        try {
            //调用到AudioService的playSoundEffect()
            service.playSoundEffect(effectType);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

查询touch音是否可播放,因为毕竟在android的setting中有个touch音的开关,如果可播放则调用到AudioService的playSoundEffect()

    public void playSoundEffect(int effectType) {
        playSoundEffectVolume(effectType, -1.0f);
    }
    
    public void playSoundEffectVolume(int effectType, float volume) {
        if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {
            Log.w(TAG, "AudioService effectType value " + effectType + " out of range");
            return;
        }

        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
                effectType, (int) (volume * 1000), null, 0);
    }

其实AudioService初始化的时候会创建一个子线HandlerThread,HandlerThread主要处理一些相对耗时的操作,这里将播放touch音的功能放在了这个子线程中去执行,这样避免了主线程的阻塞,其实大家在做mediaplayer播放时也建议放在子线程去播放,接下来看看handler里对消息的处理,关键代码如下

case MSG_PLAY_SOUND_EFFECT:
     if (msg.obj == null) {
         onPlaySoundEffect(msg.arg1, msg.arg2, 0);
    } else {
        onPlaySoundEffect(msg.arg1, msg.arg2, (int) msg.obj);
     }
     break;

直接调用onPlaySoundEffect()的方法

       private void onPlaySoundEffect(int effectType, int volume) {
            synchronized (mSoundEffectsLock) {
                //初始化mSoundPool和要播放的资源文件
                onLoadSoundEffects();

                if (mSoundPool == null) {
                    return;
                }
                float volFloat;
                // use default if volume is not specified by caller
                if (volume < 0) {
                    volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
                } else {
                    volFloat = volume / 1000.0f;
                }

                if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
                    //播放touch音
                    mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
                                        volFloat, volFloat, 0, 0, 1.0f);
                } else {
                    MediaPlayer mediaPlayer = new MediaPlayer();
                    try {
                        String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH +
                                    SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
                        mediaPlayer.setDataSource(filePath);
                        mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                        mediaPlayer.prepare();
                        mediaPlayer.setVolume(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);
                    }
                }
            }
        }

最终通过soundPool来播放指定的资源文件实现了touch音的播放,因此大家在工作中如果有什么需要对应touch音的逻辑,可参照AudioService的onPlaySoundEffect()中的逻辑。
比如指定touch音的AudioAttributes使touch音输出到指定的device上等。

总结

touch音的流程就简单分析到这里,欢迎大家交流指正。
努力学习ing~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72669.html

相关文章

  • 浅谈Android O Notification声音播放流程

    摘要:在线程中,在中创建线程在线程中通过播放饶了一大圈竟然也用来播放就是从传下来的需要注意,通知音是会申请焦点的。总结从创建到播放的流程基本就这样,至于声音的区分是否电话中,如果则使用播放,反之播放。 前言 我们在做Android开发的时候,免不了会使用到Notification,而且在android设备的设置中还可以设置通知音的优先级,以及播放的声音种类。那么通知音是如何播放的呢,今天我们...

    huhud 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<