? ? 当我们正在“专注学习”的过程中,如果不小心拔掉了耳机线或者断开蓝牙耳机的连接,突然之间我们APP里正在播放的声音就会打扰到周围的人,如果声音还挺大的话,是不是有些尴尬?所以呢,现在需要实现这么一个功能:在有线耳机拔掉或者蓝牙耳机断开连接时,立刻停止播放。
方式一、通过广播接收器监听有线耳机和蓝牙耳机的状态
步骤1:申请权限
? ? 在工程的AndroidManifest.xml中,添加以下代码:
<manifast xxx>
...
<!-- 获取蓝牙相关权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
...
</manifast>
步骤2:注册广播接受器,监听耳机状态变化
? ? 广播注册有两种方式,一种方式是<静态注册>:即在项目的Manifest的文件里注册,经测试,该方式无效。所以可以选择另外一种<动态注册>的方式:在项目的启动页?LanchActivity?或其他合适地方注册广播接收器,笔者采用的方式是单独启动一个后台Service,?在其OnCreate()方法里进行注册。
? ? 不管是在Activity里进行注册,还是在Service里注册,经笔者测试(机型:huawei mate40 pro),在用户完全退出用户界面或运行在后台的情况下,只要不执行 unregisterReceiver 的操作,仍然可以接受到广播。但是笔者建议采用第二种方式,APP 有后台 Service 在运行的情况下,因为被系统回收导致无法接收广播的概率会降低一些,相对来说更加稳定。
public class HeadphoneService extends Service {
private final HeadphoneReceiver receiver = new HeadphoneReceiver();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
IntentFilter intentFilter = new IntentFilter();
//有线耳机
intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
//蓝牙耳机
intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
registerReceiver(receiver, intentFilter);
super.onCreate();
}
@Override
public void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
}
步骤3:创建接收器,处理状态变化
public class HeadphoneReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
int state = intent.getIntExtra("state", -1);
if (state == 0) {
// 耳机拔出
} else if (state == 1) {
// 耳机插入
}
} else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
/* 申请权限
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
*/
//如果应用运行的Android系统版本大于等于12,获取蓝牙状态需要动态申请权限
int state = adapter.getProfileConnectionState(BluetoothProfile.HEADSET);
if (state == BluetoothAdapter.STATE_DISCONNECTED) {
//Bluetooth headset is now disconnected
}
}
}
}
步骤4:?启动Service
// 建议在LaunchActivity的OnCreate()里或其他合适的地方启动服务
Intent intent = new Intent(this, HeadphoneService.class);
startService(intent);
第一种方式虽然可以实现我们的需求,但有两个缺点:
缺点1:如果应用运行的Android系统版本大于等于12,获取蓝牙状态需要动态申请权限;
缺点2:拔出有线耳机或断开蓝牙耳机,会延迟大概一秒钟,才会收到Android的系统广播;
方式二:通过广播接收器监听噪音 - NOISY
/**
* Broadcast intent, a hint for applications that audio is about to become
* 'noisy' due to a change in audio outputs. For example, this intent may
* be sent when a wired headset is unplugged, or when an A2DP audio
* sink is disconnected, and the audio system is about to automatically
* switch audio route to the speaker. Applications that are controlling
* audio streams may consider pausing, reducing volume or some other action
* on receipt of this intent so as not to surprise the user with audio
* from the speaker.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
? ? ?这段话的大概意思就是,如果耳机线被拔掉或是支持A2DP(蓝牙协议)的音频连接断开后,系统会认为该应用因为声音输出方式改变,导致产生噪音(NOISY),系统会广播该Intent?Action:(ACTION_AUDIO_BECOMING_NOISY),让我们的广播接收器可以做出相应的处理,比如:暂停播放、降低音量等。
步骤1:注册广播接收器
public class HeadphoneService extends Service {
private final HeadphoneReceiver receiver = new HeadphoneReceiver();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(receiver, intentFilter);
super.onCreate();
}
@Override
public void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
}
步骤2:创建接收器,处理状态变化
public class HeadphoneReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {
//暂停播放
pauseAudio();
}
}
}
步骤3:?启动Service
// 建议在LaunchActivity的OnCreate()里或其他合适的地方启动服务
Intent intent = new Intent(this, HeadphoneService.class);
startService(intent);
? ? 综上,第二种方式是不是更加简洁呢?既不需要在 Manifest 文件里申明权限,也不需要在运行时动态申请蓝牙权限;而且相对第一种方式没有任何延迟。