最近对安卓的耳机线控进行了粗略的研究。目的是监听线控耳机上的按钮。
说下,首先我看到在onKeyDown方法里面,可以监听到耳机按钮。但是,这只能是在对应的activity激活在最前面的时候,锁屏同样无法监听到耳机上面的按钮。
所以这种办法具有较大的局限性,不建议采用,往往我们使用耳机的时候,手机放在兜兜里,或者在驾驶。
接下去进入正题。我要讲的是利用广播来实现对耳机线控的监听,在线控按钮摁下去的时候,android就会有自己的广播,我们只要定义一个广播接收者来接收到这个广播就ok了。这个广播的意图是 android.intent.action.MEDIA_BUTTON 。
下面说一下重点。注册普通的广播接收器我们都知道,有两个常见的办法,一种是在代码中动态注册,还有一种的在项目的Manifest里面注册。但是,这个广播特么简直就是个奇葩,我花了一个下午的时间才搞明白。这个广播要注册两遍,Manifest里一遍(常规办法),代码中一遍(借助多媒体服务注册),下面我注册广播的代码贴出来
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); ComponentName name = new ComponentName(context.getPackageName(), HeadSetReceiver.class.getName()); audioManager.registerMediaButtonEventReceiver(name);
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); ComponentName name = new ComponentName(context.getPackageName(), HeadSetReceiver.class.getName()); audioManager.unregisterMediaButtonEventReceiver(name);
HeadSetReceiver是我的广播接受者类,里面延迟1秒处理单双击问题。
package com.example.headset.helper; import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; /** * 耳机线控管理助手类 * 单例 * @author * */ public class HeadSetHelper { private static HeadSetHelper headSetHelper; private OnHeadSetListener headSetListener = null; public static HeadSetHelper getInstance(){ if(headSetHelper == null){ headSetHelper = new HeadSetHelper(); } return headSetHelper; } /** * 设置耳机单击双击监听接口 * 必须在open前设置此接口,否则设置无效 * @param headSetListener */ public void setOnHeadSetListener(OnHeadSetListener headSetListener){ this.headSetListener = headSetListener; } /** * 开启耳机线控监听, * 请务必在设置接口监听之后再调用此方法,否则接口无效 * @param context */ public void open(Context context){ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); ComponentName name = new ComponentName(context.getPackageName(), HeadSetReceiver.class.getName()); audioManager.registerMediaButtonEventReceiver(name); } /** * 关闭耳机线控监听 * @param context */ public void close(Context context){ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); ComponentName name = new ComponentName(context.getPackageName(), HeadSetReceiver.class.getName()); audioManager.unregisterMediaButtonEventReceiver(name); } /** * 删除耳机单机双击监听接口 */ public void delHeadSetListener() { this.headSetListener=null; } /** * 获取耳机单击双击接口 * @return */ protected OnHeadSetListener getOnHeadSetListener() { return headSetListener; } /** * 耳机按钮单双击监听 * @author * */ public interface OnHeadSetListener{ /** * 单击触发,主线程。 * 此接口真正触发是在单击操作1秒后 * 因为需要判断1秒内是否仍监听到点击,有的话那就是双击了。<p> * 如果您有更好的解决办法,请联系我: * qq495389040 */ public void onClick(); /** * 双击触发,此接口在主线程,可以放心使用 */ public void onDoubleClick(); } }
package com.example.headset.helper; import java.util.Timer; import java.util.TimerTask; import com.example.headset.helper.HeadSetHelper.OnHeadSetListener; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.view.KeyEvent; public class HeadSetReceiver extends BroadcastReceiver{ Timer timer = null; OnHeadSetListener headSetListener = null; private static boolean isTimerStart = false; private static MyTimer myTimer = null; //重写构造方法,将接口绑定。因为此类的初始化的特殊性。 public HeadSetReceiver(){ timer = new Timer(true); this.headSetListener = HeadSetHelper.getInstance().getOnHeadSetListener(); } @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub String intentAction = intent.getAction() ; if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){ //获得KeyEvent对象 KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if(headSetListener != null){ try { if(keyEvent.getAction() == KeyEvent.ACTION_UP){ if(isTimerStart){ myTimer.cancel(); isTimerStart = false; headSetListener.onDoubleClick(); }else{ myTimer = new MyTimer(); timer.schedule(myTimer,1000); isTimerStart = true; } } } catch (Exception e) { // TODO: handle exception } } } //终止广播(不让别的程序收到此广播,免受干扰) abortBroadcast(); } /* * 定时器,用于延迟1秒,内若无操作则为单击 */ class MyTimer extends TimerTask{ @Override public void run() { try { myHandle.sendEmptyMessage(0); } catch (Exception e) { // TODO: handle exception } } }; /* * 此handle的目的主要是为了将接口在主线程中触发 * ,为了安全起见把接口放到主线程触发 */ Handler myHandle = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); headSetListener.onClick(); isTimerStart = false; } http:// }; }
为什么要用单例模式,因为我的助手类中的接口真正的触发是在HeadSetReceiver里面,而这个广播的独特注册方式,让我无法获取被注册的这个接受者的实体,也就是说我无法将接口传递给这个接受者。我在HeadSetReceiver的默认构造函数里面去获取这个单例,再把这个单例助手 绑定的那个接口拿过来,在HeadSetReceiver里面去触发。这样才能保证这两个地方的接口就是同一个接口。
具体实例和封装请点下面链接,有不懂的或者建议欢迎提问交流
最后补充一点,痛恨安卓严重的碎片化。或许在很多场合,耳机的长按功能用起来会更方便,比较双击容易出现误操作。
在安卓4.1版本中,线控长按的广播被谷歌保留了,长按后开启的是谷歌自己的语音搜索。不知道后面的版本是怎样的。
点我下载案例项目