轉載請注明出處:http://blog.csdn.net/qinjuning
在Android中並沒有定義MediaButtonReceive這個廣播類,MediaButtonReceive只是作為一種通俗的命名方式來響應
插入耳機后,點擊耳機上的按鈕(名稱:MEDIA_BUTTON)接受該廣播事件的類。所有該MEDIA_BUTTON的按下我們就簡稱
為MEDIA_BUTTON廣播吧。
顧名思義:它顯然是一個廣播接收器類(BroadbcastReceiver),那么它就具備了BroadbcastReceiver類的使用方式,
但是,因為它需要通過AudioManager對象注冊,所以它有着自己的獨特之處(否則我也不會單獨拿出來分析,- -),后面我們
會慢慢的講解。
點擊MEDIA_BUTTON發送的Intent Action 為:
ACTION_MEDIA_BUTTON ="android.intent.action.MEDIA_BUTTON"
Intent 附加值為(Extra)點擊MEDIA_BUTTON的按鍵碼 :
//獲得KeyEvent對象
KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
//獲得Action
String intentAction = intent.getAction() ;
AudioManager對象注冊MEDIA_BUTTON廣播的方法原型為:
public voidregisterMediaButtonEventReceiver(ComponentNameeventReceiver)
Register a component to be the sole receiverof MEDIA_BUTTON intents
Parameters:
eventReceiver : identifier of a BroadcastReceiver that will receive the media button intent. This broadcast receiver
must be declared in the application manifest.
從注釋可知以下兩點:
1、 在AudioManager對象注冊一個MediaoButtonRecevie,使它成為MEDIA_BUTTON的唯一接收器(這很重要,
我們會放在后面講解) 也就是說只有我能收到,其他的都收不到這個廣播了,否則的話大家都收到會照成一定的混亂;
2、 該廣播必須在AndroidManifest.xml文件中進行聲明,否則就監聽不到該MEDIA_BUTTON廣播了。
下面我們就簡單的寫一個MediaButtonReceiver類,並且在AndroidManifest.xml定義
1、 自定義的MediaButtonReceiver 廣播類
- package com.qin.mediabutton;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.util.Log;
- import android.view.KeyEvent;
- public class MediaButtonReceiver extends BroadcastReceiver {
- private static String TAG = "MediaButtonReceiver";
- @Override
- public void onReceive(Context context, Intent intent) {
- // 獲得Action
- String intentAction = intent.getAction();
- // 獲得KeyEvent對象
- KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- Log.i(TAG, "Action ---->" + intentAction + " KeyEvent----->"+ keyEvent.toString());
- if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
- // 獲得按鍵字節碼
- int keyCode = keyEvent.getKeyCode();
- // 按下 / 松開 按鈕
- int keyAction = keyEvent.getAction();
- // 獲得事件的時間
- long downtime = keyEvent.getEventTime();
- // 獲取按鍵碼 keyCode
- StringBuilder sb = new StringBuilder();
- // 這些都是可能的按鍵碼 , 打印出來用戶按下的鍵
- if (KeyEvent.KEYCODE_MEDIA_NEXT == keyCode) {
- sb.append("KEYCODE_MEDIA_NEXT");
- }
- // 說明:當我們按下MEDIA_BUTTON中間按鈕時,實際出發的是 KEYCODE_HEADSETHOOK 而不是
- // KEYCODE_MEDIA_PLAY_PAUSE
- if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == keyCode) {
- sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
- }
- if (KeyEvent.KEYCODE_HEADSETHOOK == keyCode) {
- sb.append("KEYCODE_HEADSETHOOK");
- }
- if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == keyCode) {
- sb.append("KEYCODE_MEDIA_PREVIOUS");
- }
- if (KeyEvent.KEYCODE_MEDIA_STOP == keyCode) {
- sb.append("KEYCODE_MEDIA_STOP");
- }
- // 輸出點擊的按鍵碼
- Log.i(TAG, sb.toString());
- }
- }
- }
2、 在AndroidManifest.xml聲明我們定義的廣播類。
- <receiver android:name="MediaButtonReceiver">
- <intent-filter >
- <action android:name="android.intent.action.MEDIA_BUTTON"></action>
- </intent-filter>
- </receiver>
在模擬器上,我們可以手動構造MEDA_BUTTON的廣播,並且將它發送出去(后面會介紹)。
如果有真機測試的話,按下MEDIA_BUTTON是可以接受到MEDIA_BUTTON廣播的,如果沒有接受到,請關閉所有應用
程序,在觀察效果。
繼續我們的下一步分析:
前面我們說明通過registerMediaButtonEventReceiver(eventReceiver)方法注冊時,使它成為MEDIA_BUTTON的
唯一 接收器。這個唯一是怎么實現的呢? 我們在源碼中,一步步追本溯源,相信一定可以找到答案,知道這“唯一“是
怎么來的。
第一步、 為AudioManager注冊一個MediaButtonReceiver() ;
- //獲得AudioManager對象
- AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE);
- //構造一個ComponentName,指向MediaoButtonReceiver類
- //下面為了敘述方便,我直接使用ComponentName類來替代MediaoButtonReceiver類
- ComponentName mbCN = new ComponentName(getPackageName(),MediaButtonReceiver.class.getName());
- //注冊一個MedioButtonReceiver廣播監聽
- mAudioManager.registerMediaButtonEventReceiver(mbCN);
- //取消注冊的方法
- mAudioManager.unregisterMediaButtonEventReceiver(mbCN);
MediaButtonReceiver就是我們用來接收MEDIA_BUTTON的廣播類,下面為了敘述方便和直觀上得體驗,我直接使用
ComponentName類來替代真正的MediaoButtonReceiver廣播類。
說明 接下來分析的文件路徑全部在 frameworks/base/media/java/android/media/ 下
第二步、 進入AudioManager.java進行查看 ,發現如下方法:
- //注冊的方法為:
- public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
- //TODO enforce the rule about the receiver being declared in the manifest
- //我們繼續查看getService()方法,看看IAudioService類到底是什么?
- IAudioService service = getService();
- try {
- //只是簡單的調用了service的方法來完成注冊,繼續跟蹤
- service.registerMediaButtonEventReceiver(eventReceiver);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e);
- }
- }
- //取消注冊的方法為
- public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
- IAudioService service = getService();
- try {
- //只是簡單的調用了service的方法來取消注冊,,繼續跟蹤
- service.unregisterMediaButtonEventReceiver(eventReceiver);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e);
- }
- }
找到getService()方法,其實現為:
- //看看它到底是什么
- private static IAudioService getService()
- {
- // 單例模式,大家懂得
- if (sService != null) {
- return sService;
- }
- //了解Binder機制 以及AIDL文件的使用,就明白了這不過是通過AIDL文件定義的Java層Binder機制
- //b為IBinder基類接口
- IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
- //強制轉換后,sService不過是一個客戶端對象,IAudioService就是aidl文件定義的接口了
- sService = IAudioService.Stub.asInterface(b);
- return sService;
- }
- //sService對象的聲明
- private static IAudioService sService; //單例模式,不足為奇了
我們知道了AudiaoManager只不過是一個傀儡,所有的方法都是由IAudioService 對象去實現的,通過它的構造方式,
可以知道它應該是有AIDL文件形成的Binder機制, sService只是客戶端對象,那么它的服務端對象在什么地方呢?
也就是繼承了IAudioService.Stub樁的類。
第三步、接下來我們需要找到該IAudioService.aidl文件和真正的服務端對象
IAudioService.aidl定義如下:
- package android.media;
- import android.content.ComponentName;
- import android.media.IAudioFocusDispatcher;
- /**
- * {@hide}
- */
- interface IAudioService {
- void adjustVolume(int direction, int flags);
- void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
- void adjustStreamVolume(int streamType, int direction, int flags);
- void setStreamVolume(int streamType, int index, int flags);
- void setStreamSolo(int streamType, boolean state, IBinder cb);
- void setStreamMute(int streamType, boolean state, IBinder cb);
- int getStreamVolume(int streamType);
- int getStreamMaxVolume(int streamType);
- void setRingerMode(int ringerMode);
- int getRingerMode();
- void setVibrateSetting(int vibrateType, int vibrateSetting);
- int getVibrateSetting(int vibrateType);
- boolean shouldVibrate(int vibrateType);
- void setMode(int mode, IBinder cb);
- int getMode();
- oneway void playSoundEffect(int effectType);
- oneway void playSoundEffectVolume(int effectType, float volume);
- boolean loadSoundEffects();
- oneway void unloadSoundEffects();
- oneway void reloadAudioSettings();
- void setSpeakerphoneOn(boolean on);
- boolean isSpeakerphoneOn();
- void setBluetoothScoOn(boolean on);
- boolean isBluetoothScoOn();
- int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId);
- int abandonAudioFocus(IAudioFocusDispatcher l, String clientId);
- void unregisterAudioFocusClient(String clientId);
- void registerMediaButtonEventReceiver(in ComponentName eventReceiver); //這個方法是我們需要弄懂的
- void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //這個方法也是是我們需要弄懂的
- void startBluetoothSco(IBinder cb);
- void stopBluetoothSco(IBinder cb);
- }
真正的服務端對象就是繼承了 IAudioService.Stub 樁的類,AudioService就是該服務端對象,其實AudioManager的
所有操作都是由AudioService來實現的,它才是真正的老大。
第五步、 AudioService.java
- //AudioService類
- public class AudioService extends IAudioService.Stub {
- //.....
- //僅僅列出我們需要的方法
- //這兒才是真正的注冊MediaButtonReceiver的方法
- public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
- Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver);
- synchronized(mRCStack) {
- //調用它去實現注冊ComponentName
- pushMediaButtonReceiver(eventReceiver);
- }
- }
- //在查看pushMediaButtonReceiver()方法 先理解一下兩個知識點,很重要的。
- //RemoteControlStackEntry內部類不過是對ComponentName類的進一步封裝(感覺沒必要在加一層進行封裝了)
- private static class RemoteControlStackEntry {
- public ComponentName mReceiverComponent;// 屬性
- //TODO implement registration expiration?
- //public int mRegistrationTime;
- public RemoteControlStackEntry() {
- }
- public RemoteControlStackEntry(ComponentName r) {
- mReceiverComponent = r;// 構造函數賦值給mReceiverComponent對象
- }
- }
- //采用了棧存儲結構(先進后出)來保存所有RemoteControlStackEntry對象,也就是保存了ComponentName對象
- private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>();
- //回到pushMediaButtonReceiver()查看,這下該撥開雲霧了吧,繼續學習
- private void pushMediaButtonReceiver(ComponentName newReceiver) {
- // already at top of stack?
- //采用了一個棧(前面我們介紹的知識點)來保存所有注冊的ComponentName對象
- //如果當前棧不為空並且棧頂的對象與新注冊的ComponentName對象一樣,不做任何事,直接返回
- if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
- return;
- }
- //獲得mRCStack棧的迭代器
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
- //循環
- while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
- //如果當前棧內保存該新注冊的ComponentName對象,將它移除,跳出循環
- if(rcse.mReceiverComponent.equals(newReceiver)) {
- mRCStack.remove(rcse);
- break;
- }
- }
- //將新注冊的ComponentName對象放入棧頂
- mRCStack.push(new RemoteControlStackEntry(newReceiver));
- }
- }
小結一下:
棧(mRCStack)維護了所有CompoentName對象,對每個CompoentName對象,保證它有且僅有一個,
新注冊的CompoentName對象永遠處於棧頂
我們看下取消注冊的方法:
- //我們看下取消注冊的方法
- /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */
- public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
- Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver);
- synchronized(mRCStack) {
- //調用removeMediaButtonReceiver方法去實現
- removeMediaButtonReceiver(eventReceiver);
- }
- }
- private void removeMediaButtonReceiver(ComponentName newReceiver) {
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
- while(stackIterator.hasNext()) {
- //獲得mRCStack棧的迭代器
- RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
- //如果存在該對象,則移除,跳出循環
- if(rcse.mReceiverComponent.equals(newReceiver)) {
- mRCStack.remove(rcse);
- break;
- }
- }
- }
通過對前面的學習,我們知道了AudioManager內部利用一個棧來管理包括加入和移除ComponentName對象,
新的疑問來了?這個MEDIA_BUTTON廣播是如何分發的呢 ?
其實,AudioService.java文件中也存在這么一個MediaoButtonReceiver的廣播類,它為系統廣播接收器,即用來接收
系統的MEDIA_BUTTON廣播,當它接收到了這個MEDIA_BUTTON廣播 ,它會對這個廣播進行進一步處理,這個處理過程
就是我們需要的弄清楚。
MediaButtonBroadcastReceiver 內部類如下:
- private class MediaButtonBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- //獲得action ,系統MEDIA_BUTTON廣播來了
- String action = intent.getAction();
- //action不正確 直接返回
- if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) {
- return;
- }
- //獲得KeyEvent對象
- KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- if (event != null) {
- // if in a call or ringing, do not break the current phone app behavior
- // TODO modify this to let the phone app specifically get the RC focus
- // add modify the phone app to take advantage of the new API
- //來電或通話中,不做處理直接返回
- if ((getMode() == AudioSystem.MODE_IN_CALL) ||(getMode() == AudioSystem.MODE_RINGTONE)) {
- return;
- }
- synchronized(mRCStack) {
- //棧不為空
- if (!mRCStack.empty()) {
- // create a new intent specifically aimed at the current registered listener
- //構造一個Intent對象 ,並且賦予Action和KeyEvent
- Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- targetedIntent.putExtras(intent.getExtras());
- //指定該處理Intent的對象為棧頂ComponentName對象的廣播類
- targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
- // trap the current broadcast
- // 終止系統廣播
- abortBroadcast();
- //Log.v(TAG, " Sending intent" + targetedIntent);
- //手動發送該廣播至目標對象去處理,該廣播不再是系統發送的了
- context.sendBroadcast(targetedIntent, null);
- }
- //假設棧為空,那么所有定義在AndroidManifest.xml的監聽MEDIA_BUTTON的廣播都會處理,
- //在此過程中如果有任何應用程注冊了registerMediaButton 該廣播也會立即終止
- }
- }
- }
- }
總結一下MEDIA_BUTTON廣播:
AudioManager也就是AudioService服務端對象內部會利用一個棧來管理所有ComponentName對象,所有對象有且僅有一個,
新注冊的ComponentName總是會位於棧頂。
當系統發送MEDIA_BUTTON,系統MediaButtonBroadcastReceiver 監聽到系統廣播,它會做如下處理:
1、 如果棧為空,則所有注冊了該Action的廣播都會接受到,因為它是由系統發送的。
2、 如果棧不為空,那么只有棧頂的那個廣播能接受到MEDIA_BUTTON的廣播,手動發送了MEDIA_BUTTON
廣播,並且指定了目標對象(棧頂對象)去處理該MEDIA_BUTTON 。
下面分析一下KeyEvent對象里的KeyCode按鍵,可能的按鍵碼有:
1、KeyEvent.KEYCODE_MEDIA_NEXT
2、KeyEvent.KEYCODE_HEADSETHOOK
3、KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE(已廢除,等同於KEYCODE_HEADSETHOOK)
4、KeyEvent.KEYCODE_MEDIA_PREVIOUS
5、KeyEvent.KEYCODE_MEDIA_STOP
PS : 在我的真機測試中,按下MEDIA_BUTTON只有KEYCODE_HEADSETHOOK可以打印出來了。
下面給出一個小DEMO檢驗一下我們之前所做的一切,看看MEDIA_BUTTON是如何處理分發廣播的。
編寫兩個MediaButtonReceiver類用來監聽MEDIA_BUTTON廣播:
1 、China_MBReceiver.java
- package com.qin.mediabutton;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.util.Log;
- import android.view.KeyEvent;
- public class China_MBReceiver extends BroadcastReceiver {
- private static String TAG = "China_MBReceiver" ;
- @Override
- public void onReceive(Context context, Intent intent) {
- //獲得Action
- String intentAction = intent.getAction() ;
- //獲得KeyEvent對象
- KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- Log.i(TAG, "Action ---->"+intentAction + " KeyEvent----->"+keyEvent.toString());
- if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){
- //獲得按鍵字節碼
- int keyCode = keyEvent.getKeyCode() ;
- //按下 / 松開 按鈕
- int keyAction = keyEvent.getAction() ;
- //獲得事件的時間
- long downtime = keyEvent.getEventTime();
- //獲取按鍵碼 keyCode
- StringBuilder sb = new StringBuilder();
- //這些都是可能的按鍵碼 , 打印出來用戶按下的鍵
- if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){
- sb.append("KEYCODE_MEDIA_NEXT");
- }
- //說明:當我們按下MEDIA_BUTTON中間按鈕時,實際出發的是 KEYCODE_HEADSETHOOK 而不是 KEYCODE_MEDIA_PLAY_PAUSE
- if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ==keyCode){
- sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
- }
- if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){
- sb.append("KEYCODE_HEADSETHOOK");
- }
- if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){
- sb.append("KEYCODE_MEDIA_PREVIOUS");
- }
- if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){
- sb.append("KEYCODE_MEDIA_STOP");
- }
- //輸出點擊的按鍵碼
- Log.i(TAG, sb.toString());
- }
- }
- }
2 、England_MBReceiver.java同於China_MBRreceiver ,打印Log TAG= "England_MBReceiver"
3、在AndroidManifest.xml文件定義:
- <strong> <receiver android:name=".China_MBReceiver">
- <intent-filter >
- <action android:name="android.intent.action.MEDIA_BUTTON"></action>
- </intent-filter>
- </receiver>
- <receiver android:name=".Enaland_MBReceiver">
- <intent-filter >
- <action android:name="android.intent.action.MEDIA_BUTTON"></action>
- </intent-filter>
- </receiver></strong>
4、MainActivity .java 我們通過手動構造一個MEDIA_BUTTON廣播去查看我們的MediaButtonReceiver類的打印信息。
- package com.qin.mediabutton;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.media.AudioManager;
- import android.os.Bundle;
- import android.view.KeyEvent;
- public class MainActivity extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //由於在模擬器上測試,我們手動發送一個MEDIA_BUTTON的廣播,有真機更好處理了
- Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- //構造一個KeyEvent對象
- KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ;
- //作為附加值添加至mbIntent對象中
- mbIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- //此時China_MBReceiver和England_MBReceiver都會接收到該廣播
- sendBroadcast(mbIntent);
- AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
- //AudioManager注冊一個MediaButton對象
- ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName());
- //只有China_MBReceiver能夠接收到了,它是出於棧頂的。
- //不過,在模擬上檢測不到這個效果,因為這個廣播是我們發送的,流程不是我們在上面介紹的。
- mAudioManager.registerMediaButtonEventReceiver(chinaCN);
- //sendBroadcast(mbIntent,null);
- }
- //當一個Activity/Service死去時,我們需要取消這個MediaoButtonReceiver的注冊,如下
- protected void onDestroy(){
- super.onDestroy() ;
- AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
- ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName());
- //取消注冊
- mAudioManager.unregisterMediaButtonEventReceiver(chinaCN);
- }
- }
值得注意的一點時,當我們為一個應用程序注冊了MediaoButtonReceiver時,在程序離開時,我們需要取消該
MediaoButtonReceiver的注冊,在onDestroy()調用unregisterMediaButtonEventReceiver()方法就OK,這樣應用程序之間
的交互就更具邏輯性了。