一、工作总结
1. 打印出时间的logcat 2012-12-03
Adb logcat -v time
1. 打印出底层log 2012-11-16
Adb shell
Logcat proc/kmsg
1. Audio Focus的应用 2012-11-09
一、AudioFocus的申请与释放
下面看与AudioFocus的相关的类:
获取/放弃AudioFocus的方法都在android.media.AudioManager中,获取AudioFocus用requestAudioFocus();用完之后,放弃AudioFocus,用abandonAudioFocus()。
其中,参数:
· streamType是《Android中的Audio播放:音量和远程播放控制》中说明的AudioStream,其值取决于AudioManager中的STREAM_xxx;
· durationHint是持续性的指示:
o AUDIOFOCUS_GAIN指示申请得到的Audio Focus不知道会持续多久,一般是长期占有;
o AUDIOFOCUS_GAIN_TRANSIENT指示要申请的AudioFocus是暂时性的,会很快用完释放的;
o AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK不但说要申请的AudioFocus是暂时性的,还指示当前正在使用AudioFocus的可以继续播放,只是要“duck”一下(降低音量)。
· AudioManager.OnAudioFocusChangeListener是申请成功之后监听AudioFocus使用情况的Listener,后续如果有别的程序要竞争AudioFocus,都是通过这个Listener的onAudioFocusChange()方法来通知这个Audio Focus的使用者的。
返回值,可能是:
· AUDIOFOCUS_REQUEST_GRANTED:申请成功;
· AUDIOFOCUS_REQUEST_FAILED:申请失败。
二、AudioFocus被抢占与重新获得
由上节中知道,申请/释放AudioFocus时传入了AudioManager.OnAudioFocusChangeListener这个参数,其onAudioFocusChange()方法是Audio Focus被抢占与再次获得通知的地方。所以,每个要使用AudioFocus的程序都要小心实现这个函数,保证AudioFocus实现的一致性。
onAudioFocusChange()方法的focusChange参数指示了该AudioFocus的竞争者对AudioFocus的拥有情况,取值如下:
· AUDIOFOCUS_GAIN:获得了Audio Focus;
· AUDIOFOCUS_LOSS:失去了Audio Focus,并将会持续很长的时间。这里因为可能会停掉很长时间,所以不仅仅要停止Audio的播放,最好直接释放掉Media资源。而因为停止播放Audio的时间会很长,如果程序因为这个原因而失去AudioFocus,最好不要让它再次自动获得AudioFocus而继续播放,不然突然冒出来的声音会让用户感觉莫名其妙,感受很不好。这里直接放弃AudioFocus,当然也不用再侦听远程播放控制【如下面代码的处理】。要再次播放,除非用户再在界面上点击开始播放,才重新初始化Media,进行播放。
· AUDIOFOCUS_LOSS_TRANSIENT:暂时失去Audio Focus,并会很快再次获得。必须停止Audio的播放,但是因为可能会很快再次获得AudioFocus,这里可以不释放Media资源;
· AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时失去AudioFocus,但是可以继续播放,不过要在降低音量。
下面是onAudioFocusChange()方法处理的代码片段:
1 OnAudioFocusChangeListenerafChangeListener =new OnAudioFocusChangeListener() {
2 publicvoid onAudioFocusChange(int focusChange) {
3 if (focusChange ==AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
4 //Pause playback
5 } else if (focusChange ==AudioManager.AUDIOFOCUS_LOSS) {
6 am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
7 am.abandonAudioFocus(afChangeListener);
8 // Stopplayback
9 } else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK){
10 //Lower the volume
11 } else if (focusChange ==AudioManager.AUDIOFOCUS_GAIN) {
12 //Resume playback or Raise it back to normal
13 }
14 }
15 };
三、典型的应用AudioFocus的场景
下面的时序图描述了AudioFocus被抢占与再次获取的典型场景:
Audio Focus被抢占与再次获取的时序图
注意:为了描述简单,此图中除了两个竞争Audio Focus的App之外,只用AudioManager表征了Android的AudioFocus机制中内部参与的对象,实际AudioManager只是外部的表象,内部参与的对象很多,回调函数也并非简单的直接由AudioManager调用,其中还包含了复杂的IPC机制。
图中:
· AudioFocus Client通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio[Step#1 ~ #2];
· 其它程序(Other App)也通过requestAudioFocus()获取同类AudioStream的AudioFocus [Step#3]
· AudioFocus Client失去了Audio Focus,在onAudioFocusChanged()中,根据focusChange的值,做第二节中的处理[Step#4];
· 其它程序(Other App)获取Audio Focus之后,开始播放Audio[Step#5];
· 其它程序(Other App)使用Audio之后,通过abandonAudioFocus()归还AudioFocus [Step#6];
· AudioFocus Client重新获得了Audio Focus,可做进一步的处理 [Step#7]
小结
AudioFocus机制要参与各方充分理解并统一遵照施行,有没有遵照者或者实现有误的程序存在就可能打破这一机制,带来糟糕的用户体验。在保证Built-in程序没问题的前提下,如果进入AndroidMarket之前的程序都严格执行了AudioFocus相关的测试,应该也没问题。
1. AudioStream
Android为不同的应用场合定义了不同的Audio Stream: Voice Call, Ring,Music,Alarm, Notification, DTMF。 这些AudioStream是相互独立的,所以也有各自的音量。AudioStream的定义在android.media.AudioManager中,如下图所示:
2. 硬件音量控制键Vol+/-控制Audio Stream的音量
用户按下音量控制的HardKey,希望能调出音量调整的界面。
缺省情况下,按下音量控制的硬件控制键Vol+/-,调节的是当前被激活的(Active)AudioStream的音量,如果你的程序当前没有正在播放任何声音,按下Vol+/-调节的是来电铃声的音量。【笔者注:基本是翻译的原话,需要明确!By default, pressing the volume controlsmodifythe volume of the active audio stream. If your app isn't currentlyplaying anything, hitting the volume keys adjusts the ringer volume.】
在某一个程序运行时,希望按下Vol+/-调节的是当前所使用的AudioStream的音量,Android在Activity中提供了setVolumeControlStream()方法用来指定你的应用程序使用的Audio Stream类型。所以,如果你的程序用到Audio的播放,你首先要知道你的程序所用的Audio Stream类型,并在onCreate()中调用setVolumeControlStream()来设定Audio Stream的类型。
Q:setVolumeControlStream()之后就起效,还是可见之后才有用?后台播放呢?
要明确这些标红的地方,需要看Android的内部实现!//TODO:明确化;内部实现,另外专题写…
3. 遥控Audio Playback
有些耳机上有诸如音量控制、切换前一首下一首歌、播放/暂停等控制键,Bluetooth的AVRCP Profile也能远程控制的。这些键被按下后,Android是通过broadcastACTION_MEDIA_BUTTON这个Intent发出去的。
所以,要在你的应用中处理这些按键,只要侦听这个广播,并处理即可。
如果知道什么时候开始侦听广播,程序中动态注册/注销侦听都是很好的选择(与,写在AndroidManifest中,apk加载时就注册相比)。而Audio控制在什么时候才处理这些键值是比较明确的,一般获得AudioFocus的情况下,响应ACTION_MEDIA_BUTTON广播;失去Audio Focus的情况下,不响应ACTION_MEDIA_BUTTON广播,这也就是相应registerMediaButtonEventReceiver()/unregisterMediaButtonEventReceiver()的最佳时机。
ACTION_MEDIA_BUTTON广播的处理,只要在override onReceive(),并在其中通过判断是否Intent.ACTION_MEDIA_BUTTON确保是这个广播,从Intent.EXTA_KEY_EVENT中获得KeyEvent,做相应的处理即可。
涉及的几个类的关系图如下:
相应处理的代码片段如下:
1 publicclass RemoteControlReceiverextendsBroadcastReceiver {
2 @Override
3 publicvoid onReceive(Context context, Intent intent){
4 if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())){
5 KeyEvent event =(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
6 if (KeyEvent.KEYCODE_MEDIA_PLAY== event.getKeyCode()) {
7 //Handle key press.
8 }
9 }
10 }
11 }
总结一下要点:
1. Android的Audio控制是按照AudioStream划分的;
2. 各个Audio Stream的音量是独立的。推荐在onCreate()中通过Activity.setVolumeControlStream()方法设置所使用AudioStream的类型,实现按下硬键Vol+/-调整的是相对应AudioStream的音量;
3. 侦听Intent.ACTION_MEDIA_BUTTON广播实现对远程Media控制的处理。
2. Android中MediaButtonReceiver广播监听器的机制分析 2012-11-28
在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 acomponent to be thesole receiverof MEDIA_BUTTON intents
Parameters:
eventReceiver : identifier of a BroadcastReceiver that will receive themedia button intent. This broadcast receiver
must bedeclared in the application manifest.
从注释可知以下两点:
1、在AudioManager对象注册一个MediaoButtonRecevie,使它成为MEDIA_BUTTON的唯一接收器(这很重要,
我们会放在后面讲解) 也就是说只有我能收到,其他的都收不到这个广播了,否则的话大家都收到会照成一定的混乱;
2、 该广播必须在AndroidManifest.xml文件中进行声明,否则就监听不到该MEDIA_BUTTON广播了。
下面我们就简单的写一个MediaButtonReceiver类,并且在AndroidManifest.xml定义
1、 自定义的MediaButtonReceiver广播类
[java]view plaincopyprint?
1 package com.qin.mediabutton;
2
3 importandroid.content.BroadcastReceiver;
4 importandroid.content.Context;
5 importandroid.content.Intent;
6 import android.util.Log;
7 importandroid.view.KeyEvent;
8
9 public class MediaButtonReceiver extendsBroadcastReceiver {
10 private static StringTAG = "MediaButtonReceiver";
11 @Override
12 public voidonReceive(Context context, Intent intent) {
13 // 获得Action
14 String intentAction= intent.getAction();
15 // 获得KeyEvent对象
16 KeyEvent keyEvent =(KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
17
18 Log.i(TAG,"Action ---->" + intentAction + " KeyEvent----->"+keyEvent.toString());
19
20 if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
21 // 获得按键字节码
22 int keyCode =keyEvent.getKeyCode();
23 // 按下 / 松开 按钮
24 int keyAction =keyEvent.getAction();
25 // 获得事件的时间
26 long downtime =keyEvent.getEventTime();
27
28 // 获取按键码keyCode
29 StringBuilder sb= new StringBuilder();
30 // 这些都是可能的按键码 , 打印出来用户按下的键
31 if (KeyEvent.KEYCODE_MEDIA_NEXT ==keyCode) {
32 sb.append("KEYCODE_MEDIA_NEXT");
33 }
34 // 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是
35 //KEYCODE_MEDIA_PLAY_PAUSE
36 if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE== keyCode) {
37 sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
38 }
39 if (KeyEvent.KEYCODE_HEADSETHOOK ==keyCode) {
40 sb.append("KEYCODE_HEADSETHOOK");
41 }
42 if (KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode) {
43 sb.append("KEYCODE_MEDIA_PREVIOUS");
44 }
45 if (KeyEvent.KEYCODE_MEDIA_STOP ==keyCode) {
46 sb.append("KEYCODE_MEDIA_STOP");
47 }
48 // 输出点击的按键码
49 Log.i(TAG,sb.toString());
50 }
51 }
52 }
2、 在AndroidManifest.xml声明我们定义的广播类。
[java]view plaincopyprint?
53 <receiverandroid:name="MediaButtonReceiver">
54 <intent-filter>
55 <actionandroid:name="android.intent.action.MEDIA_BUTTON"></action>
56 </intent-filter>
57 </receiver>
在模拟器上,我们可以手动构造MEDA_BUTTON的广播,并且将它发送出去(后面会介绍)。
如果有真机测试的话,按下MEDIA_BUTTON是可以接受到MEDIA_BUTTON广播的,如果没有接受到,请关闭所有应用
程序,在观察效果。
继续我们的下一步分析:
前面我们说明通过registerMediaButtonEventReceiver(eventReceiver)方法注册时,使它成为MEDIA_BUTTON的
唯一接收器。这个唯一是怎么实现的呢?我们在源码中,一步步追本溯源,相信一定可以找到答案,知道这“唯一“是
怎么来的。
第一步、 为AudioManager注册一个MediaButtonReceiver();
[java]view plaincopyprint?
58 //获得AudioManager对象
59 AudioManager mAudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);
60 //构造一个ComponentName,指向MediaoButtonReceiver类
61 //下面为了叙述方便,我直接使用ComponentName类来替代MediaoButtonReceiver类
62 ComponentName mbCN = newComponentName(getPackageName(),MediaButtonReceiver.class.getName());
63 //注册一个MedioButtonReceiver广播监听
64 mAudioManager.registerMediaButtonEventReceiver(mbCN);
65 //取消注册的方法
66 mAudioManager.unregisterMediaButtonEventReceiver(mbCN);
MediaButtonReceiver就是我们用来接收MEDIA_BUTTON的广播类,下面为了叙述方便和直观上得体验,我直接使用
ComponentName类来替代真正的MediaoButtonReceiver广播类。
说明接下来分析的文件路径全部在 frameworks/base/media/java/android/media/下
第二步、进入AudioManager.java进行查看,发现如下方法:
[java]view plaincopyprint?
67 //注册的方法为:
68 public void registerMediaButtonEventReceiver(ComponentName eventReceiver){
69 //TODO enforce therule about the receiver being declared in the manifest
70 //我们继续查看getService()方法,看看IAudioService类到底是什么?
71 IAudioService service= getService();
72 try {
73 //只是简单的调用了service的方法来完成注册,继续跟踪
74 service.registerMediaButtonEventReceiver(eventReceiver);
75
76 } catch (RemoteException e) {
77 Log.e(TAG,"Dead object in registerMediaButtonEventReceiver"+e);
78 }
79 }
80 //取消注册的方法为
81 public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver){
82 IAudioService service= getService();
83 try {
84 //只是简单的调用了service的方法来取消注册,,继续跟踪
85 service.unregisterMediaButtonEventReceiver(eventReceiver);
86 } catch (RemoteException e) {
87 Log.e(TAG,"Dead object in unregisterMediaButtonEventReceiver"+e);
88 }
89 }
找到getService()方法,其实现为:
[java]view plaincopyprint?
90 //看看它到底是什么
91 private staticIAudioService getService()
92 {
93 // 单例模式,大家懂得
94 if (sService != null){
95 return sService;
96 }
97 //了解Binder机制 以及AIDL文件的使用,就明白了这不过是通过AIDL文件定义的Java层Binder机制
98 //b为IBinder基类接口
99 IBinder b =ServiceManager.getService(Context.AUDIO_SERVICE);
100 //强制转换后,sService不过是一个客户端对象,IAudioService就是aidl文件定义的接口了
101 sService =IAudioService.Stub.asInterface(b);
102 returnsService;
103 }
104 //sService对象的声明
105 privatestatic IAudioService sService; //单例模式,不足为奇了
我们知道了AudiaoManager只不过是一个傀儡,所有的方法都是由IAudioService对象去实现的,通过它的构造方式,
可以知道它应该是有AIDL文件形成的Binder机制, sService只是客户端对象,那么它的服务端对象在什么地方呢?
也就是继承了IAudioService.Stub桩的类。
第三步、接下来我们需要找到该IAudioService.aidl文件和真正的服务端对象
IAudioService.aidl定义如下:
[java]view plaincopyprint?
106 package android.media;
107
108 import android.content.ComponentName;
109 import android.media.IAudioFocusDispatcher;
110 /**
111 * {@hide}
112 */
113 interface IAudioService {
114
115 voidadjustVolume(int direction,int flags);
116 voidadjustSuggestedStreamVolume(intdirection,int suggestedStreamType,int flags);
117 voidadjustStreamVolume(int streamType,int direction,int flags);
118 voidsetStreamVolume(int streamType,int index,int flags);
119 voidsetStreamSolo(int streamType,boolean state, IBinder cb);
120 voidsetStreamMute(int streamType,boolean state, IBinder cb);
121 intgetStreamVolume(intstreamType);
122 intgetStreamMaxVolume(intstreamType);
123 voidsetRingerMode(int ringerMode);
124 intgetRingerMode();
125 voidsetVibrateSetting(int vibrateType,int vibrateSetting);
126 intgetVibrateSetting(intvibrateType);
127 booleanshouldVibrate(int vibrateType);
128 voidsetMode(int mode, IBinder cb);
129 intgetMode();
130 oneway voidplaySoundEffect(inteffectType);
131 oneway voidplaySoundEffectVolume(inteffectType,float volume);
132 booleanloadSoundEffects();
133 oneway voidunloadSoundEffects();
134 oneway voidreloadAudioSettings();
135 voidsetSpeakerphoneOn(boolean on);
136 booleanisSpeakerphoneOn();
137 voidsetBluetoothScoOn(boolean on);
138 booleanisBluetoothScoOn();
139 intrequestAudioFocus(intmainStreamType,int durationHint,IBinder cb, IAudioFocusDispatcher l, String clientId);
140 intabandonAudioFocus(IAudioFocusDispatcher l, String clientId);
141 voidunregisterAudioFocusClient(String clientId);
142 voidregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法是我们需要弄懂的
143 voidunregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法也是是我们需要弄懂的
144 voidstartBluetoothSco(IBinder cb);
145 voidstopBluetoothSco(IBinder cb);
146 }
真正的服务端对象就是继承了 IAudioService.Stub桩的类,AudioService就是该服务端对象,其实AudioManager的
所有操作都是由AudioService来实现的,它才是真正的老大。
第五步、 AudioService.java
[java]view plaincopyprint?
147 //AudioService类
148 public class AudioServiceextends IAudioService.Stub {
149 //.....
150 //仅仅列出我们需要的方法
151 //这儿才是真正的注册MediaButtonReceiver的方法
152 publicvoidregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
153 Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for" + eventReceiver);
154
155 synchronized(mRCStack){
156 //调用它去实现注册ComponentName
157 pushMediaButtonReceiver(eventReceiver);
158 }
159 }
160
161 //在查看pushMediaButtonReceiver()方法 先理解一下两个知识点,很重要的。
162 //RemoteControlStackEntry内部类不过是对ComponentName类的进一步封装(感觉没必要在加一层进行封装了)
163 privatestatic class RemoteControlStackEntry {
164 publicComponentName mReceiverComponent;// 属性
165 //TODO implement registrationexpiration?
166 //public int mRegistrationTime;
167
168 publicRemoteControlStackEntry() {
169 }
170
171 publicRemoteControlStackEntry(ComponentName r) {
172 mReceiverComponent = r;// 构造函数赋值给mReceiverComponent对象
173 }
174 }
175
176 //采用了栈存储结构(先进后出)来保存所有RemoteControlStackEntry对象,也就是保存了ComponentName对象
177 privateStack<RemoteControlStackEntry> mRCStack =new Stack<RemoteControlStackEntry>();
178
179 //回到pushMediaButtonReceiver()查看,这下该拨开云雾了吧,继续学习
180 privatevoidpushMediaButtonReceiver(ComponentName newReceiver) {
181 // already at top of stack?
182 //采用了一个栈(前面我们介绍的知识点)来保存所有注册的ComponentName对象
183 //如果当前栈不为空并且栈顶的对象与新注册的ComponentName对象一样,不做任何事,直接返回
184 if(!mRCStack.empty() &&mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
185 return;
186 }
187 //获得mRCStack栈的迭代器
188 Iterator<RemoteControlStackEntry>stackIterator = mRCStack.iterator();
189 //循环
190 while(stackIterator.hasNext()){
191 RemoteControlStackEntry rcse =(RemoteControlStackEntry)stackIterator.next();
192 //如果当前栈内保存该新注册的ComponentName对象,将它移除,跳出循环
193 if(rcse.mReceiverComponent.equals(newReceiver)) {
194 mRCStack.remove(rcse);
195 break;
196 }
197 }
198 //将新注册的ComponentName对象放入栈顶
199 mRCStack.push(new RemoteControlStackEntry(newReceiver));
200 }
201 }
小结一下:
栈(mRCStack)维护了所有CompoentName对象,对每个CompoentName对象,保证它有且仅有一个,
新注册的CompoentName对象永远处于栈顶
我们看下取消注册的方法:
[java]view plaincopyprint?
202 //我们看下取消注册的方法
203 /** seeAudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver)*/
204 public voidunregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
205 Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for" + eventReceiver);
206
207 synchronized(mRCStack){
208 //调用removeMediaButtonReceiver方法去实现
209 removeMediaButtonReceiver(eventReceiver);
210 }
211 }
212
213 private voidremoveMediaButtonReceiver(ComponentName newReceiver) {
214 Iterator<RemoteControlStackEntry>stackIterator = mRCStack.iterator();
215 while(stackIterator.hasNext()){
216 //获得mRCStack栈的迭代器
217 RemoteControlStackEntry rcse =(RemoteControlStackEntry)stackIterator.next();
218 //如果存在该对象,则移除,跳出循环
219 if(rcse.mReceiverComponent.equals(newReceiver)){
220 mRCStack.remove(rcse);
221 break;
222 }
223 }
224 }
通过对前面的学习,我们知道了AudioManager内部利用一个栈来管理包括加入和移除ComponentName对象,
新的疑问来了?这个MEDIA_BUTTON广播是如何分发的呢?
其实,AudioService.java文件中也存在这么一个MediaoButtonReceiver的广播类,它为系统广播接收器,即用来接收
系统的MEDIA_BUTTON广播,当它接收到了这个MEDIA_BUTTON广播 ,它会对这个广播进行进一步处理,这个处理过程
就是我们需要的弄清楚。
MediaButtonBroadcastReceiver内部类如下:
[java]view plaincopyprint?
225 private classMediaButtonBroadcastReceiver extendsBroadcastReceiver {
226 @Override
227 publicvoid onReceive(Context context,Intent intent) {
228 //获得action ,系统MEDIA_BUTTON广播来了
229 String action = intent.getAction();
230 //action不正确 直接返回
231 if(!Intent.ACTION_MEDIA_BUTTON.equals(action)) {
232 return;
233 }
234 //获得KeyEvent对象
235 KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
236 if(event !=null) {
237 // if in a call or ringing, do notbreak the current phone app behavior
238 // TODO modify this to let thephone app specifically get the RC focus
239 // add modify the phone app to takeadvantage of the new API
240 //来电或通话中,不做处理直接返回
241 if ((getMode() == AudioSystem.MODE_IN_CALL) ||(getMode() ==AudioSystem.MODE_RINGTONE)) {
242 return;
243 }
244 synchronized(mRCStack) {
245 //栈不为空
246 if (!mRCStack.empty()) {
247 // create a new intentspecifically aimed at the current registered listener
248 //构造一个Intent对象 ,并且赋予Action和KeyEvent
249 Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
250 targetedIntent.putExtras(intent.getExtras());
251 //指定该处理Intent的对象为栈顶ComponentName对象的广播类
252 targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
253 // trap the current broadcast
254 // 终止系统广播
255 abortBroadcast();
256 //Log.v(TAG, " Sendingintent" + targetedIntent);
257 //手动发送该广播至目标对象去处理,该广播不再是系统发送的了
258 context.sendBroadcast(targetedIntent,null);
259 }
260 //假设栈为空,那么所有定义在AndroidManifest.xml的监听MEDIA_BUTTON的广播都会处理,
261 //在此过程中如果有任何应用程注册了registerMediaButton 该广播也会立即终止
262 }
263 }
264 }
265 }
总结一下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
[java]view plaincopyprint?
266 package com.qin.mediabutton;
267
268 import android.content.BroadcastReceiver;
269 import android.content.Context;
270 import android.content.Intent;
271 import android.util.Log;
272 import android.view.KeyEvent;
273
274 public class China_MBReceiverextends BroadcastReceiver {
275
276 privatestatic String TAG ="China_MBReceiver" ;
277 @Override
278 publicvoid onReceive(Context context,Intent intent) {
279 //获得Action
280 String intentAction =intent.getAction() ;
281 //获得KeyEvent对象
282 KeyEvent keyEvent =(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
283
284 Log.i(TAG, "Action---->"+intentAction + " KeyEvent----->"+keyEvent.toString());
285
286 if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){
287 //获得按键字节码
288 int keyCode = keyEvent.getKeyCode() ;
289 //按下 / 松开 按钮
290 int keyAction = keyEvent.getAction() ;
291 //获得事件的时间
292 long downtime = keyEvent.getEventTime();
293
294 //获取按键码keyCode
295 StringBuilder sb =new StringBuilder();
296 //这些都是可能的按键码 , 打印出来用户按下的键
297 if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){
298 sb.append("KEYCODE_MEDIA_NEXT");
299 }
300 //说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是KEYCODE_MEDIA_PLAY_PAUSE
301 if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE==keyCode){
302 sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
303 }
304 if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){
305 sb.append("KEYCODE_HEADSETHOOK");
306 }
307 if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){
308 sb.append("KEYCODE_MEDIA_PREVIOUS");
309 }
310 if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){
311 sb.append("KEYCODE_MEDIA_STOP");
312 }
313 //输出点击的按键码
314 Log.i(TAG, sb.toString());
315
316 }
317
318 }
319
320 }
2、England_MBReceiver.java同于China_MBRreceiver,打印Log TAG="England_MBReceiver"
3、在AndroidManifest.xml文件定义:
[java]view plaincopyprint?
321 <strong> <receiverandroid:name=".China_MBReceiver">
322 <intent-filter >
323 <actionandroid:name="android.intent.action.MEDIA_BUTTON"></action>
324 </intent-filter>
325 </receiver>
326
327 <receiverandroid:name=".Enaland_MBReceiver">
328 <intent-filter >
329 <actionandroid:name="android.intent.action.MEDIA_BUTTON"></action>
330 </intent-filter>
331 </receiver></strong>
4、MainActivity .java我们通过手动构造一个MEDIA_BUTTON广播去查看我们的MediaButtonReceiver类的打印信息。
[java]view plaincopyprint?
332 package com.qin.mediabutton;
333
334 import android.app.Activity;
335 import android.content.ComponentName;
336 import android.content.Context;
337 import android.content.Intent;
338 import android.media.AudioManager;
339 import android.os.Bundle;
340 import android.view.KeyEvent;
341
342 public class MainActivity extends Activity {
343 /** Called when the activity is firstcreated. */
344 @Override
345 publicvoid onCreate(BundlesavedInstanceState) {
346 super.onCreate(savedInstanceState);
347 setContentView(R.layout.main);
348
349 //由于在模拟器上测试,我们手动发送一个MEDIA_BUTTON的广播,有真机更好处理了
350 Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
351 //构造一个KeyEvent对象
352 KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ;
353 //作为附加值添加至mbIntent对象中
354 mbIntent.putExtra(Intent.EXTRA_KEY_EVENT,keyEvent);
355
356 //此时China_MBReceiver和England_MBReceiver都会接收到该广播
357 sendBroadcast(mbIntent);
358
359
360 AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE);
361 //AudioManager注册一个MediaButton对象
362 ComponentName chinaCN = newComponentName(getPackageName(),China_MBReceiver.class.getName());
363 //只有China_MBReceiver能够接收到了,它是出于栈顶的。
364 //不过,在模拟上检测不到这个效果,因为这个广播是我们发送的,流程不是我们在上面介绍的。
365 mAudioManager.registerMediaButtonEventReceiver(chinaCN);
366 //sendBroadcast(mbIntent,null);
367 }
368 //当一个Activity/Service死去时,我们需要取消这个MediaoButtonReceiver的注册,如下
369 protectedvoid onDestroy(){
370 super.onDestroy();
371 AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE);
372 ComponentName chinaCN = newComponentName(getPackageName(),China_MBReceiver.class.getName());
373 //取消注册
374 mAudioManager.unregisterMediaButtonEventReceiver(chinaCN);
375 }
376 }
值得注意的一点时,当我们为一个应用程序注册了MediaoButtonReceiver时,在程序离开时,我们需要取消该
MediaoButtonReceiver的注册,在onDestroy()调用unregisterMediaButtonEventReceiver()方法就OK,这样应用程序之间
的交互就更具逻辑性了。