Android 蓝牙调试心得

       废话不多说,因为工作关系,这次接触到蓝牙功能开发,下面是我第一次蓝牙开发的经验总结,对于将要从事蓝牙开发的人,或许有些帮助。

        Android蓝牙分为传统蓝牙开发和ble蓝牙开发。下面就有小伙伴疑问了,我该学习哪个呢,其实不然,只要你学会其中一种开发模式,对于另外一种开发模式无师自通。还有,Android蓝牙开发方式就是固定的模式,跟与什么设备进行连接没有任何关系,切记!代码就是网上你看到的样子,你慢慢调试,能跟任何具有蓝牙模块硬件的设备通信,比如手机,电脑,携带式设备,单片机等等

        先讲讲传统蓝牙,传统蓝牙开发按照网上的文档,一步一步照搬就行。就是读API官方文档一开始比较吃力,理解不深。网上文档大多有个缺点,没有说明demo如何测试。这个我必须说清出,找两个Android手机就行。把demo安装在两个手机上蓝牙互联,互相发送信息,就是这么简单。下面会具体说说怎么做。

         知识点,我就不说了。如何判断手机是否支持蓝牙,如何判断是否开启权限,蓝牙,定位啥的。一直到蓝牙配对这,相信大家阅读起来毫无难度!

         接下来,我只说重点地方,连接以及如何测试:

         1.  是自己手机的蓝牙可被别的手机搜索到 ,mBluetoothAdapter不能为空,也就是你的手机有蓝牙模块。然后,你就可以简单的把下面代码放在onResume()里就行,当然了,每次弹窗提示是很烦人的,但是为了测试方便,我们先这样用。

/**
 * 使蓝牙可被搜索到
 */
private void enableBluetooth() {
    //不在可被搜索的范围
    if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
        Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
        //值为 0 则表示设备始终可检测到
        discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
        startActivity(discoverableIntent);
    }
}

     2. 服务端和客户端,读起来很唬人。下面我就跟新手说清楚这件事。蓝牙,比如我的手机,可以通过蓝牙主动连接别人手机,当然,别人也可以使用自己的手机连接我的手机。发起主动连接的叫做客户端,别动接受别人连接的就叫做服务端。简单的demo测试开发,就可以这么做。一个手机安装只有客户端代码的app(手机A),另一个手机安装只有服务端代码的app(手机B),测试过程:就只能由A向B发起连接,连接成功后,使用BluetoothSocket 的 getOutputStream() 和getInputStream() 发送和接收消息就行,此时手机A与手机B,共同持有一个socket通道,手机A向手机B发,手机B向手机A发,都可以。那大部分情况是,你的手机既可以主动连接别人设备,别人设备也有可能主动连接你。所以啊,每一个app,基本上都要实现客户端和服务端机制的代码。现在大家明白了吧。服务端和客户端机制说白了,就是你的app是否可以连接别的设备,或者被别的设备连接,至于连接成功后,消息收发,那就简单多了。就按照开发API的来。

     3.一定要写在线程内。当mBluetoothAdapter不为空时,当所有条件都满足时,如蓝牙已打开,蓝牙可被检索,蓝牙权限以获取,我们接下来就要写 :

mBluetoothSocket = getBluetoothServerSocket().accept();  //随时做好准备,等待其他设备的蓝牙连接请求-这就是服务端机制

if (BltManager.getInstance().getmBluetoothAdapter() != null) {    
bluetoothServerSocket = BltManager.getInstance().getmBluetoothAdapter().listenUsingRfcommWithServiceRecord(BltConstant.YQYB, BltConstant.SPP_UUID);
}

mBluetoothSocket 不为空,用于收发信息;getBluetoothServerSocket()里面实现的内容就是listenUsingRfcommWithServiceRecord

SPP_UUID,我就不说了吧,两端保持一致就好,可以使那个串口通用的ID

/**
 * 蓝牙UUID
 */
BltConstant.YQYB 是我App的包名,其实你如果没有用到该值,一开始,你随便使用个字符串就行,不影响你测试的。
public static UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

BluetoothDevice curBluetoothDevice = mBluetoothAdapter.getRemoteDevice(myBluBean.getAddress());
connect(device);

}

// 客户端发起连接请求

private void connect(BluetoothDevice bluetoothDevice) {
    try {
        mBluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(BltConstant.SPP_UUID);
        if (mBluetoothSocket != null) {
            MyApplication.bluetoothSocket = mBluetoothSocket;
            if (bluetoothAdapter.isDiscovering()) {
                bluetoothAdapter.cancelDiscovery();
            }
            if (!mBluetoothSocket.isConnected()) {
                mBluetoothSocket.connect();  //咱们发起来接吧,我都等不及了,哈哈哈!
            }
            EventBus.getDefault().post(new BluRxBean(connectsuccess, bluetoothDevice));
        }
    } catch (IOException e) {
        e.printStackTrace();
        try {
            mBluetoothSocket.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

那接下来,我们可以试试看,下面代码,用之前判断mBluetoothSocket是否为空,界面随便写两个textview,一个发送,一个展示接收的数据。就是没有配对,没有建立连接,也不影响我们先写这段定式代码,你们说呢。

InputStream inputStream = mBluetoothSocket.getInputStream();
// 接收数据
BufferedReader bff = new BufferedReader(new InputStreamReader(inputStream));

或者

OutputStream outputStream = mBluetoothSocket.getOutputStream();
outputStream.write(message.getBytes("utf-8"));
outputStream.flush();// 发送数据

4. 展示已连接属性

好多小伙伴,或者网上好多文档,我看了下,写错的不少。在此,我必须给大家说清楚。

if (temp.getLinkState() == 0) {
    switch (temp.getBondState()) {
        case BluetoothDevice.BOND_NONE:
            mHolder.tvState.setText("未配对");
            break;
        case BluetoothDevice.BOND_BONDING:
            mHolder.tvState.setText("配对中");
            break;
        case BluetoothDevice.BOND_BONDED:
            mHolder.tvState.setText("已配对");
            break;
    }
} else {
    mHolder.tvState.setText("已连接");
}

private int linkState = 0;  //连接状态   0表示未连接状态 1表示连接状态

简单的很,我的理解就是蓝牙工作是按一定顺序的,首先你要先配对,BOND_BONDED只表示配对成功意思,绝没有已连接的意思。只有配对成功后,你去连接蓝牙设备,才有“已连接“状态,那大家明白了吧,是的已连接只可能存在于已配对设备中。未连接啊,你像未配对,或者配对中,或者连接失败,你都可以使用未连接状态。所以,当连接成功后,你刷下蓝牙列表呗。

到这里,传统蓝牙开发,已经跟大家讲解清楚了,相信现在好多人,还有各种疑问,反正不管什么,我们先把通道打通,再实现更复杂的功能。

     接下来,我再来说说ble蓝牙,这个就简单多了。如果你看过网上的资料,相信大家到connect这个步骤都不会有什么问题。我就从下面开发不是很顺畅地方开始讲

     1. 先从服务标识说起,经常浏览ble蓝牙资料,我发现很少有人能把这个说清楚的,估计也是我们上大学课程设计的水平,一句话半吊子。有些文章,作者只是给了思路,代码并不准确,好多留言就问,怎么只能发送数据成功,无法接到数据。嘿嘿,兄弟,其实你已经成功建立起了蓝牙通道,只是代码写的有问题,读取数据是没问题,就是有的通道默认只有20个字节,有的可以多一点。

    // 服务标识 这个ID,一般不会错,大家很容易一致,所以connect应该没问题!
    private final UUID SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
    // 特征标识  NOTIFY (从推送通道过来的数据-默认最多20字节),
    private final UUID CHARACTERISTIC_NOTIFY_UUID = UUID.fromString("6E400004-B5A3-F393-E0A9-E50E24DCCA9E");
    // 特征标识  WRITE   (发送数据)
    private final UUID CHARACTERISTIC_WRITE_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
    // 特征标识  READ    (自己主动发起要接收的数据)
    private final UUID CHARACTERISTIC_READ_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");

https://github.com/wandersnail/easyble, 给大家推荐一个app,别问,下载就是。等会告诉大家什么是真理,跟我们对接的开发,如单片机,pc端啥的,他们跟你说的这个UUID是读数据的,这个UUID是发送数据的,这个UUID是通知用的,总之一句话别太当真,否则坑死你。  其实,大家在应用市场也能搜索到这个app ->BLE蓝牙调试助手,必须下载。

         Android 蓝牙调试心得_第1张图片

大家有没有看到,这是一个二维层级结构,是的groupItem的UUID就是我们connet时,使用的UUID。

标注一:private final UUID SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"),一定是我们connect连接的UUID,就是服务标识。是建立通道的关键ID,与上面代码一致。抱歉,我现在没有蓝牙单片机设备,这是一个华为佩戴设备,所以大家将就看。

标注二:我们看其他人的文档,会有这些代码,名字各异

// notify write
private BluetoothGattCharacteristic notifyCharacteristic, writeCharacteristic;
// read
private BluetoothGattCharacteristic readCharacteristic;

接下来就是关键就是关键地方了:

// 搜索GATT服务
mBluetoothGatt.discoverServices();

在回调函数中需要获取这个GATT服务,干什么用的呢,就是我们收发信息,接收通知用的。就是这么简单!

//通过notify通道接收数据,每次只有20字节(默认)
notifyCharacteristic = gattService.getCharacteristic(CHARACTERISTIC_NOTIFY_UUID);
if (notifyCharacteristic == null) {
    LogUtil.e("无法接收数据!");
    mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true);
}

//开启系统监听  UUID是系统的,不需要修改
mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true);
BluetoothGattDescriptor descriptor = notifyCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (descriptor != null) {
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
}

writeCharacteristic = gattService.getCharacteristic(CHARACTERISTIC_WRITE_UUID);
if (writeCharacteristic == null) {
    LogUtil.e("无法发送数据!");
}

readCharacteristic = gattService.getCharacteristic(CHARACTERISTIC_READ_UUID);
if (writeCharacteristic == null) {
    LogUtil.e("无法接收数据!");
}

我再把这部分代码copy过来,为了让大家看清楚:

    // 特征标识  NOTIFY
    private final UUID CHARACTERISTIC_NOTIFY_UUID = UUID.fromString("6E400004-B5A3-F393-E0A9-E50E24DCCA9E");
    // 特征标识  WRITE (发送数据)
    private final UUID CHARACTERISTIC_WRITE_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
    // 特征标识  READ  (接收数据)
    private final UUID CHARACTERISTIC_READ_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");

    childItem标注二,下面有个小属性Properties下面的属性,read,write,notify  一一对应你的CHARACTERISTIC_READ_UUID ,CHARACTERISTIC_WRITE_UUID ,CHARACTERISTIC_NOTIFY_UUID 。

所以,以后不要纠结单片机,佩戴设备,头戴设备给你的UUID是否正确,我们通过这个app,自己看,就一清二楚了。

剩下是范例:

/**
 * 发送数据
 *
 * @param data 数据
 * @return true:发送成功 false:发送失败
 */
public boolean sendData(byte[] data) {
    //value为上位机向下位机发送的指令
    writeCharacteristic.setValue(data);
    return mBluetoothGatt.writeCharacteristic(writeCharacteristic);
}

/**
 * 读取单片机发送的数据
 *
 * @return true:发送成功 false:发送失败
 */
public boolean readData() {
    return mBluetoothGatt.readCharacteristic(readCharacteristic);
}
/**
 * 蓝牙操作回调
 * 蓝牙连接状态才会回调
 */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            // 蓝牙已连接
            mConnectionState = STATE_CONNECTED;
            sendBleBroadcast(ACTION_GATT_CONNECTED);
            // 搜索GATT服务
            mBluetoothGatt.discoverServices();
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            // 蓝牙已断开连接
            mConnectionState = STATE_DISCONNECTED;
            sendBleBroadcast(ACTION_GATT_DISCONNECTED);
        }
        super.onConnectionStateChange(gatt, status, newState);
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        // 发现GATT服务
        if (status == BluetoothGatt.GATT_SUCCESS) {
            LogUtil.e("获取GATT服务成功!");
            setBleNotification();
        } else {
            LogUtil.e("获取GATT服务失败!");
        }
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        sendBleBroadcast(ACTION_DATA_AVAILABLE, characteristic);
        // value为设备发送的数据,根据数据协议进行解析
        byte[] value = characteristic.getValue();
        String result;
        try {
            result = new String(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtil.e("byte转字符串异常:" + e.getMessage());
            result = "";
        }
        LogUtil.e("收到蓝牙数据:" + result);
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorRead(gatt, descriptor, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            LogUtil.e("开启监听成功!");
            sendBleBroadcast(ACTION_OPEN_NOTIFY_SUCCESS);
        } else {
            LogUtil.e("开启监听失败!");
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            LogUtil.e("发送成功!");
            readData();
        } else {
            LogUtil.e("发送失败!");
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
        // App主动读取数据
        if (status == BluetoothGatt.GATT_SUCCESS) {
            byte[] value = characteristic.getValue();
            String result;
            try {
                result = new String(value, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                LogUtil.e("byte转字符串异常:" + e.getMessage());
                result = "";
            }
            LogUtil.e("收到蓝牙数据:" + result);
        }
    }
};

     好了到这里结束了。最后再说一句,其实我对notify属性的理解,就是通知用的,根本不是回传数据,当我的手机向单片机发送一条指令,然后等待结果,这个时候我会先收到通知,告诉我,设备已准备完毕,你可以获取设备上的数据了,这时使用read属性通道,获取单片机发给我的数据。

      以上纯粹个人见解,肯定有不对的地方。希望大家多多指教。qq我就不留了,其实蓝牙使用没有你想象中复杂,我感觉难点还是在数据传输上面,如何解决大数据传输,让我最近很头疼。等我研究完毕了,再给大家上传最新见解!

 

    

 

你可能感兴趣的:(蓝牙,android,单片机)