一、引言

Android蓝牙中涉及通用数据传输协议的有两种:

  • SPP协议
  • BLE(Bluetooth low energy)协议

SPP协议是Android 2.0引入的API,是通过Socket的形式来实现数据传输及交互,有分客户端和服务端,手机一般以客户端的角色主动连接SPP协议设备。
BLE协议是Android 4.3引入的API,但手机厂商大部份在Android 4.4上才支持BLE,即低功耗蓝牙,一般我们开发的话是使用中央(BluetoothGatt)或者外围(BluetoothGattServer)来进行开发的,手机正常情况下当作中央设备来接收信息,而蓝牙模块当作是外围设备发送数据。

二、SPP协议

SPP协议的连接流程:

  • 使用registerReceiver注册BroadcastReceiver来获取蓝牙状态、搜索设备等消息
  • 在BroadcastReceiver的onReceive()里取得搜索所得的蓝牙设备信息(如名称,MAC,RSSI)
  • 通过设备的MAC地址来建立一个BluetoothDevice对象
  • 由BluetoothDevice衍生出BluetoothSocket,准备SOCKET来读写设备
  • 通过BluetoothSocket的createRfcommSocketToServiceRecord()方法来选择连接的协议/服务,默认是SPP(UUID:00001101-0000-1000-8000-00805F9B34FB)
  • Connect之后(如果还没配对则系统自动提示),使用BluetoothSocket的getInputStream()和getOutputStream()来读写蓝牙设备
  • 关闭数据接口

SPP协议涉及4个类:

  • BluetoothAdapter:代表本地蓝牙适配器,是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信
  • BluetoothDevice:代表一个远程蓝牙设备,使用这个来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息
  • BluetoothSocket:代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据
  • BluetoothServerSocket:代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接

获取本地蓝牙适配器:

    public static BluetoothAdapter getBtAdapter() {
        Context mContext = CSmartApplication.getInstance().getApplicationContext();
        BluetoothManager bluetoothManager = (BluetoothManager) mContext
                .getSystemService(Context.BLUETOOTH_SERVICE);
        return bluetoothManager.getAdapter();
    }

注册监听:

    private void requestProfileConnectionState() {
        //检测连接状态:
        int a2dp = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
        int gatt = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.GATT);
        int sap = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.SAP);
        //据是否有连接获取已连接的设备
        int flag = -1;
        if (a2dp == BluetoothProfile.STATE_CONNECTED) {
            flag = a2dp;
        } else if (gatt == BluetoothProfile.STATE_CONNECTED) {
            flag = gatt;
        } else if (sap == BluetoothProfile.STATE_CONNECTED) {
            flag = sap;
        }
        if (flag != -1) {
            ProxyListener mProxyListener = new ProxyListener();
            mBluetoothAdapter.getProfileProxy(this, mProxyListener, flag);
        }
    }

    private void listenBlueState() {
        //检查当前是否存在蓝牙连接
        requestProfileConnectionState();
        if (mFondDevieReceiver == null) {
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
            intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            mFondDevieReceiver = new FoundDeviceReceiver();
            registerReceiver(mFondDevieReceiver, intentFilter);
        }
    }


获取远程蓝牙设备:

 private class ProxyListener implements BluetoothProfile.ServiceListener {

        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {

            if (proxy != null) {
                List<BluetoothDevice> mDevices = proxy.getConnectedDevices();
                if (mDevices.size() > 0) {
                    for (int i = 0; i < mDevices.size(); i++) {
                        //获取BluetoothDevice
                        mDevice = mDevices.get(0);
                        logShow("mDevice: " + mDevices.get(0).getAddress());
                        //调用创建Socket连接
                        connectDevice();
                    }
                }
                mBluetoothAdapter.closeProfileProxy(profile, proxy);
            }
        }

        @Override
        public void onServiceDisconnected(int profile) {

        }
    }


    private class FoundDeviceReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)
                    || BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {

                int a2dpState = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
                int adapterState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR);
                if (BluetoothA2dp.STATE_CONNECTED == a2dpState || BluetoothAdapter.STATE_CONNECTED == adapterState) {//连接成功
                    //获取BluetoothDevice
                    mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    logShow("STATE_CONNECTED: " + mDevice.getAddress());
                    //调用创建Socket连接
                    connectDevice();
                } else if (BluetoothA2dp.STATE_CONNECTING == a2dpState) {//正在连接
                } else if (BluetoothA2dp.STATE_DISCONNECTED == a2dpState) {//取消连接
                    logShow("STATE_DISCONNECTED");
                    ......
                }
            }
            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                        BluetoothAdapter.ERROR);
                switch (state) {
                    case BluetoothAdapter.STATE_OFF:
                        break;
                    case BluetoothAdapter.STATE_TURNING_OFF:
                        break;
                    case BluetoothAdapter.STATE_ON:
                        break;
                    case BluetoothAdapter.STATE_TURNING_ON:
                        break;
                }
            }
        }
    }

创建Socket:

创建RFCOMM的UUID需要与设备端约定,一般使用默认的就可以,但有时因项目特殊,也会另有定义。

 private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");//默认UUID
    /**
     * Create the RFCOMM bluetooth socket.
     *
     * @param uuid UUID to create the socket with.
     * @return BluetoothSocket object.
     */
    @TargetApi(10)
    private BluetoothSocket createSocket(UUID uuid) {
        BluetoothSocket socket = null;
        try {
            if (VERSION.SDK_INT >= 10) {
                socket = mBTDevice.createInsecureRfcommSocketToServiceRecord(uuid);
            } else {
                socket = mBTDevice.createRfcommSocketToServiceRecord(uuid);
            }
        } catch (IOException e) {
            if (mDebug)
                Log.e(TAG, "createSocket: " + e.toString());
            try {
                Method method = mBTDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
                socket = (BluetoothSocket) method.invoke(mBTDevice, Integer.valueOf(1));
                return socket;
            } catch (Exception e1) {
                if (mDebug)
                    handleException("createSocket", TypeException.CONNECTION_FAILED, e1);
            }
        }

        return socket;
    }

数据读写:

注:mInputStream.read(buffer)是I/O口堵塞式的

    mBTSocket.connect();
    mInputStream = mBTSocket.getInputStream();

    //启动新线程去处理
    while (this.going) {
        if (mInputStream != null) {
            try {
                int bytes = mInputStream.read(buffer);
                if (bytes < 0) {
                    this.going = false;
                } else {
                    Log.e(TAG, "buffer: " + Arrays.toString(buffer));
                    //数据解析
                    mHandler.obtainMessage(Message.MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
                }
            } catch (Exception e) {
                Log.e(TAG, "mInputStream read is null");
                this.going = false;
            }
        }
    }
    public void sendData(byte[] data) {
        if (!this.mDfuBusy) {//判断连接是否有效
            if (mBTSocket == null) {
                Log.e(TAG, "sendCommandData: not connected.");
                return;
            }
            try {
                OutputStream outputStream = mBTSocket.getOutputStream();
                if (outputStream != null) {
                    try {
                        outputStream.write(data);
                    } catch (Exception e) {
                        Log.e(TAG, "outputStream.write null");
                    }
                } else {
                    Log.e(TAG, "outputStream is null");
                }
            } catch (IOException e) {
                Log.e(TAG, "sendData: " + e);
            }
        }
    }

接口关闭:

    private void disconnectBluetooth() {
        if (mDebug) {
            Log.i(TAG, "disconnect BT");
        }
        if (mBTSocket != null) {
            try {
                if (mInputStream != null) {
                    mInputStream.close();
                    mInputStream = null;
                }
                if (mBTSocket != null) {
                    if (mBTSocket.getInputStream() != null) {
                        mBTSocket.getInputStream().close();
                    }
                }
                if (mBTSocket != null) {
                    if (mBTSocket.getOutputStream() != null) {
                        mBTSocket.getOutputStream().close();
                    }
                }
                if (mBTSocket != null) {
                    mBTSocket.close();
                }
                mBTSocket = null;
                mBTDevice = null;
            } catch (IOException e) {
                if (mDebug) {
                    Log.e(TAG, e.toString());
                }
            }
        }
    }

服务端的Socket建立与客户端差不多,需注意以下两个步骤:

  • 创建监听listen(蓝牙没有此监听,但需要通过whlie(true)死循环来一直监听的)
  • 通过accept(),如果有客户端连接,会创建一个新的Socket,体现出并发性,可以同时与多个socket通讯)

总之:SPP的连接和操作相对比较简单,考虑的事情也少。需要注意的是频繁数据发送(间隔时间短)的情况下,APP收到的数据并不是按原先约定的TLV一包一包数据,可能会被拆包,应对的办法就是启动线程接收完数据,然后去一包一包取。


参考

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐