android 系统按键音framework源码解析(基于android 9.0)
今天来看下android中按键音的处理,首先看下按键是在那里开启的。然后再看看当按下按键后一个按键音是怎么播放出来的。

1.首先在setting app里面 SoundFragment.java

    private void setSoundEffectsEnabled(boolean enabled) {mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); //1if (enabled) {mAudioManager.loadSoundEffects();   // 从这里可以看到调用AudioManager里面的方法打开按键音} else {mAudioManager.unloadSoundEffects();}Settings.System.putInt(getActivity().getContentResolver(),Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);}

大家可能很好奇像AudioManager,WifiManager等,都是通过getSystemService 这个方法得到的。这里花一点时间顺带先说一下1处这个吧。我们先一步一步来看。(其实最终还是回到AudioManager方法里面的,不感兴趣的可以直接跳过)。

2. framework/base/core/java/android/app/Activity.java

  @Overridepublic Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}if (WINDOW_SERVICE.equals(name)) {return mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);    //除了WINDOW_SERVICE和SEARCH_SERVICE外,其他服务都在父类中}

除了WINDOW_SERVICE和SEARCH_SERVICE外,其他服务都在父类中

3.framework/base/core/java/android/view/ContextThemeWrapper.java

    @Overridepublic Object getSystemService(String name) {if (LAYOUT_INFLATER_SERVICE.equals(name)) {if (mInflater == null) {mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);}return mInflater;}return getBaseContext().getSystemService(name);  //还要再往上}

4. framework/base/core/java/android/content/Context.java

  @SuppressWarnings("unchecked")public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) {// Because subclasses may override getSystemService(String) we cannot// perform a lookup by class alone.  We must first map the class to its// service name then invoke the string-based method.String serviceName = getSystemServiceName(serviceClass);return serviceName != null ? (T)getSystemService(serviceName) : null;}
/*** Gets the name of the system-level service that is represented by the specified class.** @param serviceClass The class of the desired service.* @return The service name or null if the class is not a supported system service.*/public abstract @Nullable String getSystemServiceName(@NonNull Class<?> serviceClass);/*** Use with {@link #getSystemService(String)} to retrieve a* {@link android.os.PowerManager} for controlling power management,* including "wake locks," which let you keep the device on while* you're running long tasks.*/public static final String POWER_SERVICE = "power";/*** Use with {@link #getSystemService(String)} to retrieve a* {@link android.view.WindowManager} for accessing the system's window* manager.** @see #getSystemService(String)* @see android.view.WindowManager*/public static final String WINDOW_SERVICE = "window";/*** Use with {@link #getSystemService(String)} to retrieve a {@link* android.net.wifi.WifiManager} for handling management of* Wi-Fi access.** @see #getSystemService(String)* @see android.net.wifi.WifiManager*/public static final String WIFI_SERVICE = "wifi";/*** Use with {@link #getSystemService(String)} to retrieve a* {@link android.media.AudioManager} for handling management of volume,* ringer modes and audio routing.** @see #getSystemService(String)* @see android.media.AudioManager    //在audiomanager 里面*/public static final String AUDIO_SERVICE = "audio";
framework/base/media/java/android/media/AudioManager.java
*** AudioManager provides access to volume and ringer mode control.*/
@SystemService(Context.AUDIO_SERVICE)  //通过注解来讲AUDIO_SERVICE与AudioManager绑定在一块
public class AudioManager {private Context mOriginalContext;private Context mApplicationContext;private long mVolumeKeyUpTime;

这里可以看到,之前那些wifimanager,audiomanager 都是这样来设置得到的。
好了,再继续说按键音的事,就是到AudioManager里面。
5. framework/base/media/java/android/media/AudioManager.java

    /***  Load Sound effects.*  This method must be called when sound effects are enabled.*/public void loadSoundEffects() {final IAudioService service = getService();try {service.loadSoundEffects();} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
private static IAudioService getService()
{if (sService != null) {return sService;}IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);sService = IAudioService.Stub.asInterface(b);return sService;
}

这里不得不再说一下。其实最后还是跑到AudiioService里面了。通过跨进程binder来拿到audioservice的对象。这里再顺带说一下那些service都是在哪里设置的。
6. framework/base/core/java/android/os/ServiceManager.java

    /*** Returns a reference to a service with the given name* @param name the name of the service to get* @return a reference to the service, or <code>null</code> if the service doesn't exist*/public static IBinder getService(String name) {try {IBinder service = sCache.get(name);   //在这个里面拿到if (service != null) {return service;} else {return Binder.allowBlocking(rawGetService(name));}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;}
    /*** Cache for the "well known" services, such as WM and AM.*/private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();/*** This is only intended to be called when the process is first being brought* up and bound by the activity manager. There is only one thread in the process* at that time, so no locking is done.** @param cache the cache of service references* @hide*/public static void initServiceCache(Map<String, IBinder> cache) {if (sCache.size() != 0) {throw new IllegalStateException("setServiceCache may only be called once");}sCache.putAll(cache);}

从上面可以看到 sCache 是一个Map。所以之前拿到的那些管理的对象(wifiManager,AudioManage,WindowManager等等),都是通过get map拿到的。

7. framework/base/services/core/java/com/android/server/audio/AudioService.java

/*** Loads samples into the soundpool.* This method must be called at first when sound effects are enabled*/public boolean loadSoundEffects() {int attempts = 3;LoadSoundEffectReply reply = new LoadSoundEffectReply();synchronized (reply) {sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0); //发送消息while ((reply.mStatus == 1) && (attempts-- > 0)) {try {reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);} catch (InterruptedException e) {Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");}}}return (reply.mStatus == 0);}

当在setting里面打开按键音之后会调这来,从类名就可以看出是加载事件。后面按键声的的播放之前也会调用到这里来。

 case MSG_LOAD_SOUND_EFFECTS://FIXME: onLoadSoundEffects() should be executed in a separate thread as it// can take several dozens of milliseconds to completeboolean loaded = onLoadSoundEffects();   // 调用这个方法if (msg.obj != null) {LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;synchronized (reply) {reply.mStatus = loaded ? 0 : -1;reply.notify();}}break;
 private boolean onLoadSoundEffects() {int status;synchronized (mSoundEffectsLock) {if (!mSystemReady) {Log.w(TAG, "onLoadSoundEffects() called before boot complete");return false;}if (mSoundPool != null) {return true;}loadTouchSoundAssets();     // 记载要播放声音的资源mSoundPool = new SoundPool.Builder().setMaxStreams(NUM_SOUNDPOOL_CHANNELS).setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build()).build();    //链式调用mSoundPoolCallBack = null;mSoundPoolListenerThread = new SoundPoolListenerThread();mSoundPoolListenerThread.start();int attempts = 3;while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {try {// Wait for mSoundPoolCallBack to be set by the other threadmSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);} catch (InterruptedException e) {Log.w(TAG, "Interrupted while waiting sound pool listener thread.");}}if (mSoundPoolCallBack == null) {Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error");if (mSoundPoolLooper != null) {mSoundPoolLooper.quit();mSoundPoolLooper = null;}mSoundPoolListenerThread = null;mSoundPool.release();mSoundPool = null;return false;}/** poolId table: The value -1 in this table indicates that corresponding* file (same index in SOUND_EFFECT_FILES[] has not been loaded.* Once loaded, the value in poolId is the sample ID and the same* sample can be reused for another effect using the same file.*/int[] poolId = new int[SOUND_EFFECT_FILES.size()];for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {poolId[fileIdx] = -1;}/** Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.* If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:* this indicates we have a valid sample loaded for this effect.*/int numSamples = 0;for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {// Do not load sample if this effect uses the MediaPlayerif (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {continue;}if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {String filePath = getSoundEffectFilePath(effect);int sampleId = mSoundPool.load(filePath, 0);if (sampleId <= 0) {Log.w(TAG, "Soundpool could not load file: "+filePath);} else {SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;numSamples++;}} else {SOUND_EFFECT_FILES_MAP[effect][1] =poolId[SOUND_EFFECT_FILES_MAP[effect][0]];}}// wait for all samples to be loadedif (numSamples > 0) {mSoundPoolCallBack.setSamples(poolId);attempts = 3;status = 1;while ((status == 1) && (attempts-- > 0)) {try {mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);status = mSoundPoolCallBack.status();} catch (InterruptedException e) {Log.w(TAG, "Interrupted while waiting sound pool callback.");}}} else {status = -1;}if (mSoundPoolLooper != null) {mSoundPoolLooper.quit();mSoundPoolLooper = null;}mSoundPoolListenerThread = null;if (status != 0) {Log.w(TAG,"onLoadSoundEffects(), Error "+status+ " while loading samples");for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {SOUND_EFFECT_FILES_MAP[effect][1] = -1;}}mSoundPool.release();mSoundPool = null;}}return (status == 0);}

8.接下来看看当按下一个按键后按键音的触发
当按下一个按键或者焦点落到一个view上时,会有很多种情况,如下,

无论如何,最后都会调用到如下的方法中
framework/base/media/java/android/media/AudioManager.java

    public void  playSoundEffect(int effectType) {if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {return;}if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) {return;}final IAudioService service = getService();try {service.playSoundEffect(effectType);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

还是会到AudioSetvice中。
9.framework/base/services/core/java/com/android/server/audio/AudioService.java

    /** @see AudioManager#playSoundEffect(int) */public void playSoundEffect(int effectType) {playSoundEffectVolume(effectType, -1.0f);}/** @see AudioManager#playSoundEffect(int, float) */public void playSoundEffectVolume(int effectType, float volume) {// do not try to play the sound effect if the system stream is mutedif (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) {return;}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);}
 case MSG_PLAY_SOUND_EFFECT:onPlaySoundEffect(msg.arg1, msg.arg2);break;private void onPlaySoundEffect(int effectType, int volume) {synchronized (mSoundEffectsLock) {onLoadSoundEffects();    //上面提到过的的加载if (mSoundPool == null) {return;}float volFloat;// use default if volume is not specified by callerif (volume < 0) {volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);} else {volFloat = 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();try {String filePath = getSoundEffectFilePath(effectType);  //得到播放音频资源的地址。如果要替换资源,可以到此位置替换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);}}}}

到此,android 系统的按键音的流程就走完了。

android 系统按键音framework流程源码解析相关推荐

  1. Android Launcher启动应用程序流程源码解析

    带着问题看源码 点击桌面Launcher图标后做了哪些工作? 应用程序什么时候被创建的? Application和MainActivity的onCreate()方法什么时候被调用的? 概述 在Andr ...

  2. swoole 启动流程_EasySwoole 服务启动过程以及主体设计流程源码解析

    EasySwoole 服务启动过程以及主体设计流程源码解析 本文主要讲解EasySwoole 服务的启动过程,会通过源码片段讲解主体的设计流程 命令启动 当我们通过php easyswoole sta ...

  3. 云原生小课堂|Envoy请求流程源码解析(三):请求解析

    ​ 前言 Envoy 是一款面向 Service Mesh 的高性能网络代理服务.它与应用程序并行运行,通过以平台无关的方式提供通用功能来抽象网络.当基础架构中的所有服务流量都通过 Envoy 网格时 ...

  4. Myth源码解析系列之六- 订单下单流程源码解析(发起者)

    前面一章我们走完了服务启动的源码,这次我们进入下单流程的源码解析~ 订单下单流程源码解析(发起者) 首先保证myth-demo-springcloud-order.myth-demo-springcl ...

  5. BroadcastReceiver的跨进程注册、接收流程源码解析

    根据<Activity跨进程启动流程源码探究>我们可以清楚以下几点: 1)Context的通用实现是在ContextIml这个类中 2)Activity的启动过程需要借助ActivityM ...

  6. SpringBoot启动全流程源码解析(超详细版)

    我们在使用SpringBoot启动项目的时候,可能只需加一个注解,然后启动main,整个项目就运行了起来,但事实真的是所见即所得吗,还是SpringBoot在背后默默做了很多?本文会通过源码解析的方式 ...

  7. Redis运行流程源码解析

    原文作者:@凡趣科技 pesiwang 原文地址:http://blog.nosqlfan.com/html/4007.html 本文分析源码基于 Redis 2.4.7 stable 版本. 概述 ...

  8. Redis运行流程源码解析--转载

    http://blog.nosqlfan.com/html/4007.html http://www.searchdatabase.com.cn/showcontent_62166.htm 导读:本文 ...

  9. NioEventLoop启动流程源码解析

    NioEventLoop的启动时机是在服务端的NioServerSocketChannel中的ServerSocketChannel初始化完成,且注册在NioEventLoop后执行的, 下一步就是去 ...

最新文章

  1. Java高效读取大文件
  2. PHP实现文件安全下载
  3. 7-40 奥运排行榜 (25 分)(详解+思路+map+vector做法)兄弟们冲压呀呀呀呀呀呀呀
  4. tomcat顶层架构
  5. 如何在定制化组件中实现并使用v-model
  6. 计量经济学知识点及案例整理
  7. 三步教你制作拼多多优惠券cms网站系统的返利功能
  8. 有关学术界的治理-来自学术道德通论课程作业
  9. 百度坐标转WGS84
  10. 深度学习车辆检测实现自动驾驶
  11. 基于php732电子杂志投稿审核网站
  12. SSH框架整合3——原生态SessionFactory
  13. SaaS企业如何巧用「组织力」来构建自身持续增长的根基?
  14. 捕获窗口跳入跳出事件
  15. 低能耗配电开关 USB过流保护芯片USB限流保护SY6280
  16. 小波变换中的多贝西小波(DB小波函数)概述
  17. STM8L低功耗设置,深坑。。。
  18. 郝健: github多人协作项目开发实操笔记
  19. C# .CS后台调用JS函数
  20. 麦当劳的AI改造计划

热门文章

  1. 洛谷P1314 聪明的质检员(二分)
  2. calwrf编译linux红帽,WRF模式运行手册
  3. bash与source
  4. 服务器系统2003资源监视器在哪里,系统资源监视器在哪里
  5. Arduino - 最小系统(基于ATMEGA8-16PU)
  6. hive的自定义函数以及自定义加密函数
  7. 界通职称计算机等级考试,计算机等级考试复习方法
  8. 3DMARK05截图泄漏
  9. 10进制和16进制 数字和ASCII码互转
  10. moviepy音视频开发:使用volumex调节音量大小及其花式用法