最近对安卓的耳机线控进行了粗略的研究。目的是监听线控耳机上的按钮。
说下,首先我看到在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版本中,线控长按的广播被谷歌保留了,长按后开启的是谷歌自己的语音搜索。不知道后面的版本是怎样的。