Android蓝牙连接问题总结

本文详细介绍了Android蓝牙开发的流程,包括蓝牙检测、扫描蓝牙设备、蓝牙设备连接、发现服务及特征码注册、蓝牙设备断开等内容,并针对蓝牙开发中常见问题提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近开始接触Android的蓝牙设备问题,严格意义上来说,也算是第二次接触蓝牙机制了,之前对于蓝牙设备的整个过程,也不是太了解,只是接触了一些自己需要的部分。而这次应该算是比较深入的了解了蓝牙机制的部分吧,所以就总结一下这段时间对蓝牙的个人认识吧


(此图片转自http://blog.youkuaiyun.com/l331258747/article/details/55261386)

基本上关于蓝牙的流程,这张图片就说明的差不多了,检测用户是否开启了蓝牙——开启蓝牙——扫描蓝牙设备——选择设备连接——发现服务——注册特征码——断开连接,我们就参考这张图,按照响应的顺序来说一下整个蓝牙设备流程的流程方法吧

一:蓝牙检测

我们在启用蓝牙模块的时候,还要先声明一下相关的权限问题

  <!-- 步骤(1)设置Google Map API v2 Key,其中com.wei.android.beginning为package的名字 -->
    <permission
        android:name="com.example.administrator.bluetoothdevice.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />
    <uses-permission android:name="com.example.administrator.bluetoothdevice.permission.MAPS_RECEIVE" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="no.nordicsemi.android.LOG" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 获取位置权限才可以搜索到设备-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- 蓝牙分享所需的权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

蓝牙通信之前,需要先确定设备是否支持蓝牙功能,先初始化一个BluetoothAdapter的实例,

BluetoothAdapter提供了一个静态方法getDefaultAdapter()来获得BluetoothAdapter的实例

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

然后检测蓝牙是否开启并打开蓝牙:

第一种方法:

if(mBluetoothAdapter!=null){

if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

}

第二种方法:

if(mBluetoothAdapter!=null){

if (!mBluetoothAdapter.isEnabled()) {

mBluetoothAdapter.enable();

}

}

而两种方法,都会提示用户开启蓝牙权限。

二:扫描蓝牙设备

在扫描这里,要多添加一个点,就是关于蓝牙连接状态的广播监听,方便在蓝牙是否连接,断开的时候去做相关的逻辑处理,所以还是有必要了解一下的。

if (mCommonBroadcastReceiver != null) {
    LocalBroadcastManager.getInstance(this).registerReceiver(
            mCommonBroadcastReceiver, makeIntentFilter());
    //注册蓝牙监听状态改变
    BleProfileServiceReadyActivity.this.registerReceiver(mCommonBroadcastReceiver, makeIntentFilter2());
}
private static IntentFilter makeIntentFilter2() {
    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
    return intentFilter;
}
注册相关的蓝牙广播监听之后,在监听中做对应的逻辑处理
private BroadcastReceiver mCommonBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(final Context context, final Intent intent) {
        final String action = intent.getAction();
        if (BleProfileService.BROADCAST_CONNECTION_STATE.equals(action)) {

            final int state = intent.getIntExtra(
                    BleProfileService.EXTRA_CONNECTION_STATE,
                    BleProfileService.STATE_DISCONNECTED);
            switch (state) {
                case BleProfileService.STATE_CONNECTED:
                 
                    break;
                case BleProfileService.STATE_DISCONNECTED:
                  
                    break;

                case BleProfileService.STATE_LINK_LOSS:

                   
                    break;

                case BleProfileService.STATE_CONNECTING:

                    break;
                case BleProfileService.STATE_DISCONNECTING:

                   
                    break;
                default:
                    // there should be no other actions
                    break;
            }
        }
这里解释一下相关的状态含义:

STATE_DISCONNECTED 状态为断开
STATE_CONNECTING 状态为连接"
STATE_DISCONNECTING 状态为断中
STATE_CONNECTED 状态为连接中

简单的说一下监听方法后,我们在回到主题,关于扫描蓝牙设备的方法:

  1. //搜索附近所有的外围设备  
  2. mBluetoothAdapter.startLeScan(mLeScanCallback);  
  3. //搜索某些uuid的外围设备。  
  4. mBluetoothAdapter.startLeScan(uuid[] ,mLeScanCallback);  
  1. 停止扫描  
  2. mBluetoothAdapter.stopLeScan(mLeScanCallback);  


/*
 * 扫描结果回调
 */
private LeScanCallback mLeScanCallback= new LeScanCallback() {

    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi,
                         final byte[] scanRecord) {
        if (mLeScanCallback== null) {
            return;
        }
        try {
           //这里写逻辑代码
        } catch (Exception e) {
            Log.e("", e.toString());
        }
        return;
    }
};

解释一下其中的几个参数,当你扫描回调后,基本就可以搜索到周围的蓝牙设备了(前提是蓝牙开启的状态下),在回调的结果中,分别以后三个参数

BluetoothDevice 表示你搜索的设备对象,一般获取设备的mac地址就是从这个对象获取,方法是
getAddress()
rssi 表示的则是设备距离的远近,信号强弱值,有需要这个值可以用数组缓存下来显示
scanRecord 表示的则是蓝牙设备的一些属性值,比如
serviceUUIDs;serviceData;localName等  
在这里要补充说明一点的是,手机扫描设备回调,可能会无法调用,需要手机将位置权限也打开给应用,如果禁掉了应用的位置权限,也会导致扫描设备回调方法无法执行。

三、蓝牙设备连接

关于蓝牙的连接问题,先要说明几个相关类的含义

BluetoothGatt:中央使用和处理数据;
BluetoothGattCallback:中央的回调。

BluetoothGattServer:周边提供数据;

BluetoothGattServerCallback:周边的回调

BluetoothGattService:Gatt服务

BluetoothGattCharacteristic:Gatt特性

BluetoothGattDescriptor:Gatt描述

(转自http://blog.youkuaiyun.com/vnanyesheshou/article/details/51943870)

我们要做的连接操作,就是用其中一个对象来处理的:

BluetoothDevicedevice=mBluetoothAdapter.getRemoteDevice(new_mac);

蓝牙设备对象,由BluetoothAdapter根据设备的mac地址来获取

这里需要注意的一个坑是,mac地址必须是FF:FF:FF:FF这样的形式,这点不同于IOS的适配,如果使用其他的设备地址,会出现异常IllegalArgumentException,所以在连接的时候建议处理好此类异常问题:

if (BluetoothManager.BluetoothState()) {
    try {
        BluetoothDevice device = getBluetoothAdapter().getRemoteDevice(DeviceAddress);
        Logger.i(TAG, "connectionHandler() " + DeviceAddress);
        if (null != device) {
            BluetoothGatt gatt = device.connectGatt(getContext(), false, this);
            if (gatt.connect()) {
                createControl(gatt);
                return true;
            }
        }
    } catch (IllegalArgumentException e) {
        Toast.makeText(getContext(), getContext().getString(R.string.error_device), Toast.LENGTH_SHORT).show();
        return false;
    } catch (Exception e) {
        Toast.makeText(getContext(), getContext().getString(R.string.error_device), Toast.LENGTH_SHORT).show();
        return false;
    }
}

BluetoothGatt mBluetoothGatt =device.connectGatt(mContext, false,mGattCallback);

这里的三个参数,分别对应的是<1>上下文对象<2>是否自动连接<3>连接回调和连接结果返回。这里重点解释一下第二个参数,个人建议最好是设置为false,如果设置自动连接,则安卓底层会不停的跟对应Mac地址的设备反复连接,连接效率会变得很慢,而且容易发送阻塞,导致后边的设备一直在等前一个设备连接成功的回调,蓝牙设备的连接一定要分开逐个连接,尽量不要形成线程阻碍。

private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
    @Override
    //获取连接状态方法,BLE设备连接上或断开时,会调用到此方
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (DEBUG) Log.d(TAG, "onConnectionStateChange");
        if (status == BluetoothGatt.GATT_SUCCESS) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                showMessage("Bluetooth LE connected");
            }
            else if (status == BluetoothProfile.STATE_DISCONNECTED) {
                showMessage("Bluetooth LE disconnected");
            }
        }
    }

    //成功发现设备的services时,调用此方法
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if ((status == BluetoothGatt.GATT_SUCCESS) &&
                (mBluetoothGatt.getService(SERVICE_UUID) != null)) {
            showMessage("Discover service Successful !!!");
        }
    }

    //读写characteristic时会调用到以下方法
    @Override
    public void onCharacteristicRead(BluetoothGatt gattBluetoothGattCharacteristic characteristic, int status) {
        if ((status == BluetoothGatt.GATT_SUCCESS) &&
                (characteristic.getUuid().equals(CHARACTERISTIC_UUID))) {
            showMessage(characteristic.getStringValue(0));
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        BluetoothGattCharacteristic mCharacteristic =
                getCharacteristic(CHARACTERISTIC_UUID);
        if ((status == BluetoothGatt.GATT_SUCCESS) &&
                (characteristic.getStringValue(0).equals(mCharacteristic.getStringValue(0)))) {
            showMessage("CharacteristicWrite Successful !!!");
        }
    }
};
(代码内容转自http://blog.youkuaiyun.com/l331258747/article/details/55261386,也可以参考文章最后留的Demo中的处理方法)
连接是否成功,以及通知的处理,是声明在OnConnectionStateChange,看名字也应该可以看的出来,当设备的蓝牙连接状态发生改变的时候调用的方法。这里也是整个蓝牙最容易出现问题的地方,就是超过了手机连接设备的上限,导致反复连接无法连接上的133状态码问题。(133状态码一般出现在连接设备的数量超过了最大上限值)所以我们要针对这种问题,做相应的处理,尽可能在不使用的蓝牙连接的时候就及时回收掉BlueToothGatt。
     @Override
    publicvoid onConnectionStateChange(BluetoothGatt gatt, intstatus, intnewState) {
        String intentAction;
        if(status == BluetoothGatt.GATT_SUCCESS) {
            if(newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = BluetoothConstants.ACTION_GATT_CONNECTED;
                mBLEConnectionState = BluetoothConstants.BLE_STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                Log.i(TAG, "Attempting to start service discovery:"  + mBluetoothGatt.discoverServices());
            elseif (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = BluetoothConstants.ACTION_GATT_DISCONNECTED;
                mBLEConnectionState = BluetoothConstants.BLE_STATE_DISCONNECTED;
                close(); // 防止出现status 133
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        else{
            Log.d(TAG, "onConnectionStateChange received: "  + status);
            intentAction = BluetoothConstants.GATT_STATUS_133;
            mBLEConnectionState = BluetoothConstants.BLE_STATE_DISCONNECTED;
            close(); // 防止出现status 133
            broadcastUpdate(intentAction);
            connect(reConnectAddress);
        }
    }
 

(转自http://blog.youkuaiyun.com/baidu_26352053/article/details/54571688)

四、发现服务,注册特征码

onServiceAdded需要注意我们的特征码一定不能在别的地方注册,因为蓝牙连接是一个耗时操作,如果你提前注册了特征码,很可能注册不成功,一定放在onServiceAdded方法下去注册,否则你会发现根本无法操作蓝牙设备
   private final BluetoothGattServerCallback mCallbacks = new BluetoothGattServerCallback() {
        @Override
        //获取连接状态方法,BLE设备连接上或断开时,会调用到此方
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            if (DEBUG) Log.d(TAG, "onConnectionStateChange: newState=" + newState);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    mDevice = device;
                    String devicename = device.getName();
                    String address = device.getAddress();
                    notifyConnected(devicename);
                    beginNotification();
                } else if (status == BluetoothProfile.STATE_DISCONNECTED) {
                    stopNotification();
                    notifyDisconnected();
                    mDevice = null;
                }
            }
        }

        //service添加成功会调用此方
        @Override
        public void onServiceAdded(int status, BluetoothGattService service) {
            if (DEBUG) Log.d(TAG, "onServiceAdded()");
            if (status == BluetoothGatt.GATT_SUCCESS) notifyServiceAdded();
        }

        //读写Characteristic,在此获得客户端发来的消息
        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,BluetoothGattCharacteristic characteristic,
                                                 boolean preparedWrite, boolean responseNeeded,int offset, byte[] value) {
            if (DEBUG) Log.d(TAG, "onCharacteristicWriteRequest: preparedWrite=" + preparedWrite);
            try{
                mCharacteristicString = new String(value); //客户端发来的消息
            }catch(Exception e){
            }
            notifyCharacteristicWriteRequest(mCharacteristicString);
        }
    }

    @Override
    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
                                            int offset, BluetoothGattCharacteristic characteristic) {
        if (DEBUG) Log.d(TAG, "onCharacteristicReadRequest()");
        notifyCharacteristicReadRequest();
    }
};
关于其他的蓝牙设备回调,接收发送数据包给蓝牙设备等操作,就要放在回调的其他几个方法了,具体都有相关的说明和标注,就不一一列出了。

五:蓝牙设备断开问题

在这里一定要说明的是,close一定要放在disconnect后调用,如果看过安卓蓝牙的底层源码的,一定有看到close中其实是有调用到disconnect方法的。很多时候设备断开后无法再重新连接,都是因为已经达到了手机蓝牙可连接设备的上限值,基本上手机可缓存的蓝牙设备都是在6个或者6个以上的,但是超过这个数量之后,就会发现始终连接不上设备的问题,类似蓝牙连接中常见的133问题,大多数时候我们在断开设备或者不需要蓝牙连接的时候,最好是调用close来回收掉BluetoothGatt,下次在重新创建一个连接对象。

mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;

五:关于蓝牙设备中出现的133问题

1、就是建立及时回收蓝牙连接断开方法

// 断开连接
public boolean disConnect() {
    if (mBluetoothGatt != null) {
        System.out.println("断开连接");
        Log.d("TAG", "连接失败,已断开连接" + mBluetoothGatt);
        mBluetoothGatt.disconnect();
        mBluetoothGatt.close();
        mBluetoothGatt = null;
        connect_flag = false;
        setConnectionState(BluetoothProfile.STATE_DISCONNECTED);
        cleanControl();
        return true;
    }
    return false;
}

2、安卓底层还有一个回收刷新蓝牙缓存的方法,不过是无法调用的。在android.bluetooth.BluetoothGatt

android.bluetooth.BluetoothGatt类中有一个refresh方法是隐藏性质的方法,所以要通过java反射机制来调用此方法。


清理方法如下,一般建议放在最后清理缓存的时候调用,否则你会发现由于清理了缓存,反复都连接不上设备

/**
 * 清理蓝牙缓存
 */
public boolean refreshDeviceCache() {
    if (mBluetoothGatt != null) {
        try {
            Method localMethod = mBluetoothGatt.getClass().getMethod(
                    "refresh", new Class[0]);
            if (localMethod != null) {
                boolean bool = ((Boolean) localMethod.invoke(
                        mBluetoothGatt, new Object[0])).booleanValue();
                return bool;
            }
        } catch (Exception localException) {
            L.i(TAG, "An exception occured while refreshing device");
        }
    }
    return false;
}

大致流程就说完了,其中还有很多坑,比如特征码注册问题了,无法正常断开设备问题了,重启蓝牙后无法正常连接设备等,以后再找时间对此单独在写一篇文章吧,另外附上gothub上发布的蓝牙设备连接搜索整的Demo地址

https://github.com/wangyetongsss/BluetoothDevice

<think>好的,我现在需要解决用户在Android 13上遇到的蓝牙音箱音量控制失效的问题。首先,我得理清可能导致这个问题的原因。根据用户提供的引用资料,特别是引用[1]和[2]提到了AVRCP协议在Android中的实现,涉及MediaSession和蓝牙按键事件的处理。这可能与音量控制有关,因为AVRCP不仅处理元数据,还负责传输控制命令,比如音量调节。 首先,用户的问题是无法通过设备的音量条控制蓝牙音箱的音量。可能的故障点包括蓝牙协议兼容性问题、MediaSession配置错误、系统服务问题或者蓝牙设备本身的限制。我需要逐步排查这些可能性。 根据引用[1],AVRCP在Android中通过MediaSession来处理按键事件,所以检查应用的MediaSession配置是否正确是关键。如果应用没有正确设置MediaSession,系统可能无法将音量控制命令传递给蓝牙设备。另外,系统设置中的绝对音量功能(Absolute Volume)是否启用也可能影响,因为如果设备不支持或该功能被禁用,音量调节可能无法同步。 然后,考虑用户提到的引用[3],虽然主要是关于硬件噪声的问题,但其中提到的串电阻方法可能不适用于软件问题,不过可以提醒用户检查硬件连接是否正常,比如蓝牙音箱是否稳定连接,或者尝试其他设备排除硬件问题。 引用[4]提到通知管理可能影响系统行为,但不确定是否直接相关。不过,可以建议用户检查是否有第三方应用干扰了媒体控制,比如AcDisplay或其他通知管理工具,可能会拦截或修改系统事件。 引用[5]涉及安全攻击,但用户的问题更可能是配置问题而非恶意攻击,不过可以建议用户检查系统更新,确保没有安全漏洞影响蓝牙功能。 接下来,整理解决方案步骤: 1. 检查蓝牙绝对音量设置:Android 13可能在开发者选项中提供了“禁用绝对音量”选项,启用或禁用它可能解决问题,具体取决于设备兼容性。 2. 验证MediaSession配置:应用需要正确设置MediaSession,并处理媒体按钮事件,确保音量控制命令被正确传递到蓝牙设备。 3. 重新配对蓝牙设备:有时重新配对可以解决协议协商问题。 4. 检查系统更新和厂商补丁:系统bug可能需要更新修复。 5. 使用第三方应用测试:如蓝牙调试工具,确认是否是系统级问题。 6. 检查蓝牙设备兼容性:确保音箱支持AVRCP版本与手机兼容。 可能还需要建议用户清除蓝牙相关应用的缓存或数据,或者重启设备。此外,查看系统日志(通过ADB)可能会有帮助,但这对普通用户来说可能较复杂。 最后,生成相关问题需要考虑用户可能遇到的类似问题或进一步疑问,比如AVRCP版本差异、其他蓝牙控制问题等。</think>### 分步解决方案 **Android 13蓝牙音箱音量控制失效的解决方案:** 1. **启用/禁用「绝对音量」功能** - 进入「开发者选项」:在系统设置中连续点击「版本号」7次激活开发者模式 - 找到「停用绝对音量」或「Disable Absolute Volume」选项 - 切换该选项状态(若当前禁用则启用,反之亦然)后重新连接蓝牙设备测试 *原理:部分蓝牙设备依赖AVRCP协议的绝对音量同步功能[^1]* 2. **验证应用MediaSession配置** - 在代码中检查是否正确定义了`MediaSession`并关联`MediaSession.Callback`: ```kotlin val mediaSession = MediaSession(context, "AudioSession") mediaSession.setCallback(object : MediaSession.Callback() { override fun onSetVolumeTo(volume: Int) { // 处理音量控制逻辑 } }) mediaSession.isActive = true ``` - 确保在`AndroidManifest.xml`中声明了`MEDIA_CONTENT_CONTROL`权限(系统应用需要) 3. **蓝牙设备重新配对** - 进入「设置 > 已连接设备」 - 长按蓝牙音箱条目选择「取消配对」 - 重启手机后重新完成配对流程 *注意:部分设备需在配对时同步音量控制能力* 4. **检查系统更新与厂商补丁** - 进入「设置 > 系统 > 系统更新」检查安全补丁版本 - 访问设备制造商官网查询蓝牙相关问题的修复公告 *示例:三星2023年4月更新修复了AVRCP 1.6兼容性问题* 5. **硬件兼容性验证** - 使用其他Android设备测试同一蓝牙音箱 - 通过蓝牙协议分析工具(如nRF Connect)查看AVRCP支持版本 $$ \text{AVRCP版本要求} \geq 1.4 \text{(Android 13最低支持版本)} $$ --- ###
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值