android系統按鍵音framework流程源碼詳細解析
android 系統按鍵音framework源碼解析(基於android 9.0)
今天來看下android中按鍵音的處理,首先看下按鍵是在那裡開啟的。然後再看看當按下按鍵後一個按鍵音是怎麼播放出來的。
1.首先在setting app裡面 SoundFragment.java
private void setSoundEffectsEnabled(boolean enabled) { mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); //1 if (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
@Override public 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
@Override public 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 complete boolean 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 thread mSoundEffectsLock.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 MediaPlayer if (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 loaded if (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 muted if (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 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) { 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 系統的按鍵音的流程就走完瞭。
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Android音視頻開發Media FrameWork框架源碼解析
- Android開發全局音量調整的實現方式詳解
- Android利用SoundPool實現音樂池
- Android實現監聽音量的變化
- Android系統服務概覽