蓝牙深层次的也不太懂,主要是使用了 BluetoothAdapter 和 BluetoothDevice 这两类,一个表示本地蓝牙设备,一个表示远程蓝牙设备
相关说明:https://blog.csdn.net/weixin_44128558/article/details/124835947
连接设备和断开设备
名称 | 介绍 |
---|---|
A2dp | 音频 |
Gatt | 低功耗 |
Headset | 蓝牙耳机(通话) |
Health | 健康 |
Hid | 人机接口设备 |
Hfp | 免提 |
Sap | 会话通知 |
HearingAid | 助听器 |
Socket | 面向连接,套接字,基于RFCOMM |
等等一些设备
只贴了出主要的代码
public static final String CONNECT = "connect";
public static final String DISCONNECT = "disconnect";
private String currentStatus = "";
private BluetoothDevice currentDevice = null;
//some code
// 监听回调
private BluetoothProfile.ServiceListener mListener = new BluetoothProfile.ServiceListener() {
@SuppressLint("MissingPermission")
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.d("zwt", "connected profile = " + profile);
if (profile == BluetoothProfile.A2DP) {
BluetoothA2dp mA2dp = (BluetoothA2dp) proxy; //转换
if (mA2dp == null || currentDevice == null){
Log.d("zwt", "a2dp 初始化失败 或 currentDevice = null ");
return;
}
Log.d("zwt", "a2dp 初始化完成 = "+(mA2dp == null)+":::::");
if (DISCONNECT.equals(currentStatus)){ // 连接
//获得所有通过A2DP代理管理连接的蓝牙设备,如果不是A2DP协议代理的设备无法断开连接
@SuppressLint("MissingPermission") List<BluetoothDevice> deviceList=mA2dp.getConnectedDevices();
if (deviceList!=null && deviceList.size()>0 && deviceList.contains(currentDevice)){
disConnectA2dpAndHeadSet(BluetoothA2dp.class, mA2dp, currentDevice);
}
/****取消所有蓝牙设备(音箱)连接****/
// @SuppressLint("MissingPermission") List<BluetoothDevice> deviceList=mA2dp.getConnectedDevices();
// if(deviceList!=null&&deviceList.size()>0){
// Log.d("zwt", "有连接的设备");
// for (BluetoothDevice device : deviceList){
// disConnectA2dpAndHeadSet(BluetoothA2dp.class, mA2dp, device);
// }
// }else {
// Log.d("zwt", "没有连接设备");
// }
/*********************************/
}else if(CONNECT.equals(currentStatus)){ //断开连接
connectA2dpAndHeadSet(BluetoothA2dp.class, mA2dp, currentDevice);
}
currentDevice = null;
}
}
@Override
public void onServiceDisconnected(int profile) {
Log.d(TAG, "close connect profile = " + profile);
if (profile == BluetoothProfile.A2DP) {
mA2dp = null;
}
}
};
// 连接蓝牙 服务
private boolean connectA2dpAndHeadSet(Class<?> btClass, BluetoothProfile bluetoothProfile, BluetoothDevice device) {
Log.d("zwt", "connectA2dpAndHeadSet连接设备");
Boolean isConnect = false;
try {
Method connectMethod = btClass.getMethod("connect", BluetoothDevice.class);
connectMethod.setAccessible(true);
isConnect = (boolean) connectMethod.invoke(bluetoothProfile, device);
Log.d("zwt", "是否连接设备成功:::"+isConnect);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Log.d("zwt", "连接设备异常");
e.printStackTrace();
}
return isConnect;
}
//断开连接蓝牙 服务
private boolean disConnectA2dpAndHeadSet(Class<?> btClass, BluetoothProfile bluetoothProfile, BluetoothDevice device){
Log.d("zwt", "disConnectA2dpAndHeadSet断开连接设备");
Boolean isDisConnect = false;
try {
Method disconnectMethod = btClass.getDeclaredMethod("disconnect", BluetoothDevice.class);
disconnectMethod.setAccessible(true);
isDisConnect = (boolean) disconnectMethod.invoke(bluetoothProfile, device);
Log.d("zwt", "是否断开设备成功:::"+isDisConnect);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
Log.d("zwt", "断开设备异常");
e.printStackTrace();
}
return isDisConnect;
}
/**
* 连接蓝牙设备
* @param device 要连接的设备
* @param bluetoothProfile 代理对象
*/
public void connect(BluetoothDevice device, int bluetoothProfile){
currentStatus = CONNECT;
currentDevice = device;
bluetoothAdapter.getProfileProxy(context, mListener, bluetoothProfile); //会回调到BluetoothProfile.ServiceListener中
}
/**
* 断开连接蓝牙设备
* @param device 要断开连接的设备
* @param bluetoothProfile 代理对象
*/
public void disConnect(BluetoothDevice device, int bluetoothProfile){
currentStatus = DISCONNECT;
currentDevice = device;
bluetoothAdapter.getProfileProxy(context, mListener, bluetoothProfile);
}
注意connectA2dpAndHeadSet 方法,由于 BluetoothA2dp#connect() 和 BluetoothA2dp#disconnect() 方法声明了不能被外部app使用,所以只能用反射的方式调用,这里只实现了 A2DP 协议的连接方式,主要是用于蓝牙音箱,蓝牙耳机等设备的连接,要是想使用蓝牙电话功能,需要使用 BluetoothProfile.HEADSET 协议,实现方法与 A2DP 相同
调用
BTServiceManager.getInstance().disConnect(currentDevice, BluetoothProfile.A2DP);
BTServiceManager.getInstance().connect(currentDevice, BluetoothProfile.A2DP);
回连设备
上面的方法可以去连接,断开蓝牙音箱等设备,但是奇怪的是无法连接或断开连接手机设备,问题在于,设备连接蓝牙音箱的时候为声音的提供端(即主模式 source端),而连接手机的时候为声音的接收端(即从模式 sink端)两种为不一样的模式,而手机厂商可能做了处理,手机不能作为声音的接收端进行连接,所以会连接失败,如果上面的方法连接成功了状态也不对,变成了设备的声音通过手机的喇叭播放出来
最后查api发现了一个A2DP_SINK模式,所以试试用这个模式进行连接:
如上图所示,这种模式只能是系统级的应用才能使用,Android studio中直接使用会报错无法编译,要在系统中编译才能通过
移植项目到系统:https://blog.csdn.net/weixin_44128558/article/details/125278463
// 蓝牙服务回调监听
private BluetoothProfile.ServiceListener mListener = new BluetoothProfile.ServiceListener() {
@SuppressLint("MissingPermission")
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.d(TAG, "connected profile = " + profile);
if (profile == BluetoothProfile.A2DP) { //蓝牙音箱
// some code 与上面相同
}
if (profile == BluetoothProfile.A2DP_SINK){ //作为声音接收端连接手机
BluetoothA2dpSink mA2dpSinkProfile = (BluetoothA2dpSink) proxy;
if (mA2dpSinkProfile == null || currentDevice == null){
Log.d("zwt", "A2dpSink 初始化失败 或 currentDevice = null ");
return;
}
Log.d("zwt", "A2DP_SINK 初始化完成::::"+(mA2dpSinkProfile == null));
if (CONNECT.equals(currentStatus)){ // 连接手机
try{
Thread.sleep(300);
}catch (InterruptedException e) { e.printStackTrace(); }
mA2dpSinkProfile.connect(currentDevice);
Log.d("zwt", "连接手机::"+currentDevice.getName());
// must set PRIORITY_AUTO_CONNECT or auto-connection will not
// occur, however this setting does not appear to be sticky
// across a reboot
mA2dpSinkProfile.setPriority(currentDevice, BluetoothProfile.PRIORITY_AUTO_CONNECT);
}else if (DISCONNECT.equals(currentStatus)){ //断开连接手机
try{
Thread.sleep(300);
}catch (InterruptedException e) { e.printStackTrace(); }
Log.d(TAG, "断开手机连接::"+currentDevice.getName());
mA2dpSinkProfile.disconnect(currentDevice);
}
}
}
// connect方法与disConnect 方法与上面相同无需更改
public void connect(BluetoothDevice device, int bluetoothProfile){
currentStatus = CONNECT;
currentDevice = device;
bluetoothAdapter.getProfileProxy(context, mListener, bluetoothProfile);
}
public void disConnect(BluetoothDevice device, int bluetoothProfile){
currentStatus = DISCONNECT;
currentDevice = device;
bluetoothAdapter.getProfileProxy(context, mListener, bluetoothProfile);
}
调用:主要实现进入app时,断开所有的蓝牙音箱设备,并回连最后一次连接的手机设备,并在退出app时断开与手机的连接
BTServiceManager.getInstance().disConnect(currentDevice, BluetoothProfile.A2DP_SINK);
BTServiceManager.getInstance().connect(currentDevice, BluetoothProfile.A2DP_SINK);
贴出主要代码
@SuppressLint("MissingPermission")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (initPairBLEAndConnectBLE(getConnectedDevice())){
callBackBT();
}
}
/**
* 获得所有已经连接的设备
* @return 已连接设备列表
*/
public Set<BluetoothDevice> getConnectedDevice(){
// bluetoothAdapter 自己通过代码获得
if (bluetoothAdapter != null){
//得到已配对的设备列表
@SuppressLint("MissingPermission") Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
Set<BluetoothDevice> resultConnectedDevice = new HashSet<>();
for (BluetoothDevice bluetoothDevice : devices) {
boolean isConnect = false;
try {
isConnect = (boolean) bluetoothDevice.getClass().getMethod("isConnected").invoke(bluetoothDevice);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
if (isConnect) {
resultConnectedDevice.add(bluetoothDevice);
}
}
return resultConnectedDevice;
}
return null;
}
/**
* 初始话蓝牙状态
* @param devices 已经连接的设备
* @return 是否回连设备,已经连接了设备返回false,否则返回true
*/
@SuppressLint("MissingPermission")
private boolean initPairBLEAndConnectBLE(Set<BluetoothDevice> devices){
if (devices != null && devices.size()>0){
for (BluetoothDevice bluetoothDevice : devices) {
// 经典 1, BLE 2, 双模 3,
Log.d("zwt", "已连接设别:"+bluetoothDevice.getName()+"::类别::"+bluetoothDevice.getType());
int styleMajor = bluetoothDevice.getBluetoothClass().getMajorDeviceClass();
if(styleMajor == BluetoothClass.Device.Major.PHONE){ //手机
// TODO 有连接到的手机设备,做些UI的更新提示
return false;
}else if(styleMajor == BluetoothClass.Device.Major.AUDIO_VIDEO){//音频设备
//这里是要做打开应用先自动断开所有的蓝牙音箱设备,连接手机
//因为在连接音箱设备的同时不能连接手机,跟蓝牙芯片有关(RTL8723DU),即不能同时处在主模式和从模式,一时间只能为一个模式
BTServiceManager.getInstance().disConnect(bluetoothDevice, BluetoothProfile.A2DP);
continue;
} else {//其他设备
// TODO 其他未适配设备,UI提示先手动断开在连接手机蓝牙
return false;
}
}
}
return true;
}
/**
* 回连蓝牙设备
*/
@SuppressLint("MissingPermission")
private void callBackBT(){
Set<BluetoothDevice> ConnctedDevices = BTChatA2dpServiceManager.getInstance().getBondedDevice();
for (BluetoothDevice device : ConnctedDevices){
@SuppressLint("MissingPermission") int styleMajor = device.getBluetoothClass().getMajorDeviceClass();
Log.d("zwt","callBackBT() 已配对设备::"+device.getName()+":类型(512手机)(1024音频设备)::"+styleMajor);
if (styleMajor == BluetoothClass.Device.Major.PHONE){
Log.d("zwt", "手机设备回连中");
BTServiceManager.getInstance().connect(device, BluetoothProfile.A2DP_SINK);
//只尝试连接最近连接过的一个手机设备,无论成功与否都不在向下尝试
break;
}
}
}
// 这里拦截按键,因为发现在destory中调用断开连接的方法无作用,可能是有些资源被释放了
// currentDevice 为当前连接的设备,注意控好状态,不要乱了,本应该是个当前连接设备数组
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
Log.d("zwt", "按下返回按键");
if (currentDevice != null)
BTServiceManager.getInstance().disConnect(currentDevice, BluetoothProfile.A2DP_SINK);
finish();
return true;
}
}
return super.dispatchKeyEvent(event);
}
取消pin码验证
比起上面的回连功能可简单太多了网上资料也很多,需要注册个广播,手动设置以下pin码就行了
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) return;
if (action.equals(BluetoothDevice.ACTION_UUID)){
Log.d("zwt", "ACTION_UUID 唯一码UUID:");
}
// 配对请求广播
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)){
Log.d(TAG, "ACTION_PAIRING_REQUEST 配对请求");
//获得蓝牙设备
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Bundle extras = intent.getExtras();
//"android.bluetooth.device.extra.PAIRING_KEY"
Object pairkey = extras.get(BluetoothDevice.EXTRA_PAIRING_KEY); //配对的pin码
Log.d("zwt", "device-->"+String.valueOf(btDevice )+":::pairkey-->"+String.valueOf(pairkey));
btDevice.setPairingConfirmation(true);
//消费这个广播,不然这个广播传到底层就会又弹出配对界面,一闪而过
abortBroadcast();
//ret为true 表示设置成功,fales表示不成功
boolean ret = btDevice.setPin(pairkey.toString().getBytes());
}
//其他广播监听
}
注册广播
IntentFilter intentFilter = new IntentFilter();
//添加其他需要监听的广播
intentFilter.addAction(BluetoothDevice.ACTION_UUID);
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
/**
Broadcast Action: This intent is used to broadcast PAIRING REQUEST
For apps targeting Build.VERSION_CODES#R or lower, this requires the Manifest.permission#BLUETOOTH_ADMIN permission which can be gained with a simple <uses-permission> manifest tag.
*/
//注册广播
registerReceiver(mBroadcastReceiver, intentFilter);
轻松实现