Android听筒与喇叭切换遇到的坑

场景需求

在聊天场景中,收到对方语音时,用户可以选择外放播放,也可以选择插入耳机收听.更人性化一点当用户把手机靠近耳朵时屏幕关闭自动切换到听筒中播放,播放完毕后拿开手机屏幕自动点亮.比如微信就是如此.

需求分析

从上面场景中我们可以得出我们需要的要点:

播放模式切换:外放<—>耳机 播放模式切换:外放<—>听筒 屏幕操作:亮屏<—>息屏<—>亮屏

解决问题

从需求分析我们可以得出需要代码进行控制的有:

音乐播放控制 外放,耳机,听筒之间的切换 屏幕的息屏与亮屏

外放,耳机,听筒之间的切换

在Android系统中是用AudioManager来管理播放模式的,通过AudioManager.setMode()方法来实现.

在setMode()方法中有以下几种对应不同的播放模式:

  • MODE_NORMAL: 普通模式,既不是铃声模式也不是通话模式
  • MODE_RINGTONE:铃声模式
  • MODE_IN_CALL:通话模式
  • MODE_IN_COMMUNICATION:通信模式,包括音/视频,VoIP通话.(3.0加入的,与通话模式类似)

其中:播放音乐的对应的就是MODE_NORMAL, 如果使用外放播则调用audioManager.setSpeakerphoneOn(true)即可.若使用耳机和听筒,则需要先设置模式为MODE_IN_CALL(3.0以前)或MODE_IN_COMMUNICATION(3.0以后).

注意: 需要权限android.permission.MODIFY_AUDIO_SETTINGS 3.0以后设置模式为MODE_IN_CALL,在华为的某些机型中,根本不起作用.

切换代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
/**
* 切换到外放
*/
public void changeToSpeaker(){
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.setSpeakerphoneOn(true);
}
/**
* 切换到耳机模式
*/
public void changeToHeadset(){
audioManager.setSpeakerphoneOn(false);
}
/**
* 切换到听筒
*/
public void changeToReceiver(){
audioManager.setSpeakerphoneOn(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
} else {
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
}

听筒切换到外放模式,使用上面的changeToSpeaker()即可,但是外放切换到听筒时会丢失大概3秒的语音,解决办法是设置mediaplayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL),该方法需要在player的prepare前调用,所以外放切听筒会导致重新播放

是否插入耳机判断

在插入或者拔出耳机时系统会发出Action为Intent.ACTION_HEADSET_PLUG的广播,并且该广播不能使用静态接收器处理,故写一个广播接收器处理耳机事件即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HeadsetReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action){
//插入和拔出耳机会触发此广播
case Intent.ACTION_HEADSET_PLUG:
int state = intent.getIntExtra("state", 0);
if (state == 1){//切换到耳机
changeToHeadset();
} else if (state == 0){
changeToSpeaker();
}
break;
default:
break;
}
}
}

耳机切换到外放会出现丢失语音 由于耳机切换到外放需要一段时间导致,故解决此问题的方法是先暂停再续播.那么什么时候暂停什么时候续播呢? 耳机拔出时系统还会发出Action为AudioManager.ACTION_AUDIO_BECOMING_NOISY的广播,且此广播比Intent.ACTION_HEADSET_PLUG要早.

屏幕的息屏与亮屏

现在几乎每个手机都有距离感应器,通过举例感应器可获得距离.距离感应器由SensorManager管理:

1
2
3
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);

注册监听的方法的最后一个参数是敏感度,敏感度越高越费电,此处选择一般敏感度即可.此外Activity还需实现SensorEventListener接口,覆写其方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onSensorChanged(SensorEvent event) {
float value = event.values[0];
if (playerManager.isPlaying()){
if (value == sensor.getMaximumRange()) {
changeToSpeaker();
setScreenOn();
} else {
changeToReceiver();
setScreenOff();
}
} else {
if(value == sensor.getMaximumRange()){
changeToSpeaker();
setScreenOn();
}
}
}

在Android系统中硬件的工作状态的控制由PowerManager与WakeLock掌管.PowerManager通过不同的WakeLock来控制CPU,屏幕,键盘等硬件的工作状态.

1
2
powerManager = (PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);

注意:需要权限android.Manifest.permission.DEVICE_POWERandroid.permission.WAKE_LOCK 其中第一个参数代表控制级别,可选值有:

  • PARTIAL_WAKE_LOCK : CPU运行,屏幕和键盘可能关闭
  • SCREEN_DIM_WAKE_LOCK : 屏幕亮,键盘灯可能关闭
  • SCREEN_BRIGHT_WAKE_LOCK : 屏幕全亮,键盘灯可能关闭
  • FULL_WAKE_LOCK : 屏幕和键盘灯全亮
  • PROXIMITY_SCREEN_OFF_WAKE_LOCK : 屏幕关闭,键盘灯关闭,CPU运行
  • DOZE_WAKE_LOCK : 屏幕灰显,CPU延缓工作

此处我们选取PROXIMITY_SCREEN_OFF_WAKE_LOCK.WakeLock通过acquire()release()方法上锁和解锁.

1
2
3
4
5
6
7
8
9
10
11
12
13
private void setScreenOff(){
if (wakeLock == null){
wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
}
wakeLock.acquire();
}
private void setScreenOn(){
if (wakeLock != null){
wakeLock.setReferenceCounted(false);
wakeLock.release();
wakeLock = null;
}
}
三星Galaxy note5在熄灭屏幕是会调用Activity的onPause(),onStop()方法
坚持原创技术分享,您的支持将鼓励我继续创作!