0
点赞
收藏
分享

微信扫一扫

Android随笔-蓝牙

hoohack 2022-02-22 阅读 70

在这里插入图片描述

概述

定义

蓝牙是一种支持设备短距离通信的无线电技术,具有低功耗、速率快、成本低等特性。

来源

“蓝牙”(Bluetooth)一词是斯堪的纳维亚语中 Blåtand / Blåtann (即古挪威语blátǫnn) 的一个英语化版本,该词蓝牙的名字来源于10世纪丹麦国王Harald Blatand-英译为Harold Bluetooth,他将纷争不断的丹麦部落统一为一个王国,传说中他还引入了基督教。据说,这位哈洛德国王特别喜欢吃蓝莓,有一颗牙齿被染成了永久性的蓝色,因此人们都叫他「哈洛德·蓝牙」(Harald Bluetooth)国王。

在行业协会筹备阶段,需要一个极具有表现力的名字来命名这项高新技术。行业组织人员,在经过一夜关于欧洲历史和未来无限技术发展的讨论后,有些人认为用Blatand国王的名字命名再合适不过了。Blatand国王将现在的挪威,瑞典和丹麦统一起来;就如同这项即将面世的技术,技术将被定义为允许不同工业领域之间的协调工作,例如计算,手机和汽车行业之间的工作。名字于是就这么定下来了。

以此为蓝牙命名的想法最初是Jim Kardach于1997年提出的,Kardach开发了能够允许移动电话与计算机通讯的系统。他的灵感来自于当时他正在阅读的一本由Frans G. Bengtsson 撰写的描写北欧海盗和Harald Bluetooth国王的历史小说The Long Ships,意指蓝牙也将把通讯协议统一为全球标准。

简史

蓝牙的历史实际上要追溯到第二次世界大战。蓝牙的核心是短距离无线电通讯,它的基础来自于跳频扩频(FHSS)技术,由好莱坞女演员 Hedy Lamarr 和钢琴家 George Antheil 在 1942 年 8 月申请的专利上提出。他们从钢琴的按键数量上得到启发,通过使用 88 种不同载波频率的无线电控制鱼雷,由于传输频率是不断跳变的,因此具有一定的保密能力和抗干扰能力。

起初该项技术并没有引起美国军方的重视,直到 20 世纪 80 年代才被军方用于战场上的无线通讯系统,跳频扩频(FHSS)技术后来在解决包括蓝牙、WiFi、3G 移动通讯系统在无线数据收发问题上发挥着关键作用。

蓝牙技术开始于爱立信在 1994 年创制的方案,该方案旨在研究移动电话和其他配件间进行低功耗、低成本无线通信连接的方法。发明者希望为设备间的无线通讯创造一组统一规则(标准化协议),以解决用户间互不兼容的移动电子设备的通信问题,用于替代 RS-232 串口通讯标准。

有不少人认为,蓝牙技术是从1.1版本开始引用到产品上的,其实这并不准确,早在2001年索尼爱立信T39mc成为了第一款内置蓝牙的手机产品,将曾统治手机无线信息交互的“红外传输”技术拉下神坛。而在2002年,蓝牙1.1版才正式推出。

版本

版本时间速度距离(米)
5.2202048Mbit/s300
5.1201948Mbit/s300
5.0201648Mbit/s300
4.2201424Mbit/s50
4.1201324Mbit/s50
4.0201024Mbit/s50
3.0+HS200924Mbit/s10
2.1+EDR20073Mbit/s10
2.0+EDR20042.1Mbit/s10
1.220031Mbit/s10
1.12002810Kbit/s10
1.01998723.1Kbit/s10
  • EDR是蓝牙增强速率(Enhanced Data Rate)的英文缩写,其特色是大大提高了蓝牙技术的数据传输速率,达到了2.1Mbps ,是蓝牙1.2技术传输速率的三倍。
  • HS是highspeed,高速的意思。

与NFC的区别与联系

名称蓝牙NFC
连接一对一,一对多一对一
距离0米<,<300米0<,<20厘米
速率
速度
安全
成本
配对
尺寸
功耗

用途

蓝牙音箱,汽车电子,移动互联,健康医疗,物联网,智能家居等。

开发

蓝牙分为传统蓝牙(Classic Bluetooth)和低功耗蓝牙(Bluetooth LE),本文使用BLE。

申请权限

    <!-- 使用蓝牙的权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <!-- 扫描蓝牙设备或者操作蓝牙设置 -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!-- 精确位置 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!-- 粗略位置 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

蓝牙有范围,所以使用的时候除了申请蓝牙权限外,还需申请位置权限。

获取蓝牙

 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

Android系统已将蓝牙模块封装好,使用BluetoothAdapter.getDefaultAdapter()即可获取当前设备的蓝牙。若

 bluetoothAdapter == null;

则说明该设备不支持蓝牙。

打开蓝牙

打开蓝牙之前需要判断当前设备蓝牙是否处于开启状态,通过

bluetoothAdapter.isEnabled();

判断蓝牙是否开启,若返回true,则已开启,否则处于关闭状态,需要打开蓝牙,打开蓝牙有两种方式:

  1. 直接
bluetoothAdapter.enable();
  1. 间接
     if (!bluetoothAdapter.isEnabled()) {
         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
         startActivityForResult((Activity) context, enableIntent, 1235, null);
    }

第一种方式比较粗暴简单,第二种方式有交互过程,体验会更加好一些。

发现设备

扫描蓝牙设备需要使用广播来发现。

public class BluetoothScanReceiver extends BroadcastReceiver {
    private static final String TAG = "BluetoothReceiver";
    private BluetoothScanCallback callback;


    public BluetoothScanReceiver(BluetoothScanCallback callback) {
        this.callback = callback;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "action: " + action);
        BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        switch (action) {
            case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                Log.d(TAG, "开始扫描。。。");
                callback.onScanStarted();
                break;
            case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                Log.d(TAG, "结束扫描。。。");
                callback.onScanFinished();
                break;
            case BluetoothDevice.ACTION_FOUND:
                Log.d(TAG, "发现设备。。。");
                callback.onScanning(bluetoothDevice);
                break;
            default:
                break;
        }
    }
}

设置了广播接收,接下来需要注册广播

   IntentFilter intentFilter1 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
   context.registerReceiver(bluetoothScanReceiver,intentFilter1);
   IntentFilter intentFilter2 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
   context.registerReceiver(bluetoothScanReceiver,intentFilter2);
   IntentFilter intentFilter3 = new IntentFilter(BluetoothDevice.ACTION_FOUND);
   context.registerReceiver(bluetoothScanReceiver,intentFilter3);
  • BluetoothAdapter.ACTION_DISCOVERY_STARTED,开始扫描。
  • BluetoothAdapter.ACTION_DISCOVERY_FINISHED,结束扫描。
  • BluetoothDevice.ACTION_FOUND,发现设备。

在onReceive中就会发现多个设备,通过 BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE),就能够获取被发现的设备。若存在多个蓝牙设备,则该方法会被多次执行。若将数据缓存下来,则可以查看适合范围内的所有蓝牙设备。

若需要该设备被发现,则需要通过BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE让该设备被发现

 Intent discoveredIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
 discoveredIntent .putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,60);
 startActivityForResult(discoveredIntent);

BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION是设置时间,单位为秒,若大于0或小于3600,则会被设置为默认的120s,不然的话太耗能。扫描也不是一直扫,每一次扫描都会有持续时间,为了确保设备不被遗漏,可以使用Handle或Thread进行多次扫描,注意切换线程。

连接蓝牙

选着需要连接的设备BleDevice进行连接,通常通过bleDevice.getName()获取蓝牙名称进行连接,若知道目标设备的Mac地址,也可通过bleDevice.getMac()进行连接。

  1. 根据UUID连接
public class BluetoothConnectTask extends AsyncTask<BluetoothDevice,Integer, BluetoothSocket> {
    private final String TAG = "BluetoothConnectTask";
    private BluetoothDevice device;
    private BluetoothConnectCallback callback;


    public BluetoothConnectTask(BluetoothConnectCallback callback) {
        this.callback = callback;
    }

    @Override
    protected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) {
        Log.d(TAG, "doInBackground: ");
        device = bluetoothDevices[0];
        BluetoothSocket socket = null;

        try {
            socket = device.createRfcommSocketToServiceRecord(UUID.fromString(""));
            if(socket != null && !socket.isConnected()){
                socket.connect();
                Log.d(TAG, "doInBackground: socket connect");
            }
        } catch (IOException e) {
            e.printStackTrace();
            if(socket != null){
                try {
                    socket.close();
                    Log.d(TAG, "doInBackground: socket close");
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                    Log.e(TAG, "doInBackground: socket close fail");
                }
            }

        }

        return socket;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        Log.d(TAG, "onPreExecute: onConnectStarted");
        if(callback != null){
            callback.onConnectStarted();
        }

    }

    @Override
    protected void onPostExecute(BluetoothSocket socket) {
        super.onPostExecute(socket);
        if(socket != null && socket.isConnected()){
            Log.d(TAG, "onPostExecute: onConnectSuccess");
            callback.onConnectSuccess(device,socket);
        } else {
            Log.d(TAG, "onPostExecute: onConnectFail");
            callback.onConnectFail(device);
        }
    }


}

执行任务

new BluetoothConnectTask(connectCallback).execute(device)

其中 device.createRfcommSocketToServiceRecord(UUID.fromString(""))中需要UUID,蓝牙设备中可有多个UUID,但具体使用哪一个需要根据蓝牙协议来。
2. 根据BluetoothGattCallback连接

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            bluetoothGatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
                    autoConnect, coreGattCallback, TRANSPORT_LE);
        } else {
            bluetoothGatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
                    autoConnect, coreGattCallback);
        }
    private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
        }
        ...
    };

通过返回的bluetoothGatt,获取serviceUuid

            bluetoothGattServices = gatt.getServices();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    uuid:
                    for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {
                        if (bluetoothGattService.getUuid().toString().substring(4, 8).equals("ffff") || bluetoothGattService.getUuid().toString().substring(4, 8).equals("ffe0")) {
                            serviceUUid = bluetoothGattService.getUuid().toString();
                            List<BluetoothGattCharacteristic> characteristics = bluetoothGattService.getCharacteristics();
                            for (BluetoothGattCharacteristic characteristic : characteristics) {
                                if (characteristic.getUuid().toString().substring(4, 8).equals("ff00") || characteristic.getUuid().toString().substring(4, 8).equals("ffe1")) {
                                    charaUUid = characteristic.getUuid().toString();
                                    readNotifyData(bleDevice, serviceUUid, charaUUid);
                                    break uuid;
                                }
                            }
                        }
                    }
                }

因为一台蓝牙设备可以有多个serviceUUid ,而每一个serviceUUid 可以有多个characteristicUuid,需要根据蓝牙协议获取需要正在使用serviceUUid和characteristicUuid,然后用它们来与蓝牙设备进行信息传递。

发送数据

发送数据前将需要发送的数据转为byte[]才能进行通信。
调用封装好的方法writeData(final BleDevice bleDevice, String uuidService, String uuidRead, byte[] data)。

    /**
     * 发送数据
     *
     * @param bleDevice
     * @param uuidService
     * @param uuidRead
     * @param data
     */
    public static void writeData(final BleDevice bleDevice, String uuidService, String uuidRead, byte[] data) {
        BleManager.getInstance().write(bleDevice, uuidService, uuidRead, data, new BleWriteCallback() {
            @Override
            public void onWriteSuccess(int current, int total, byte[] justWrite) {
                Log.d(TAG, "writeData  onWriteSuccess");
            }

            @Override
            public void onWriteFailure(BleException exception) {
                Log.e(TAG, "writeData  onWriteFailure" + exception.getDescription());
            }
        });
    }

BleManager.getInstance().write方法的上一层封装为:

    /**
     * write
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_write
     * @param data
     * @param split
     * @param sendNextWhenLastSuccess
     * @param intervalBetweenTwoPackage
     * @param callback
     */
    public void write(BleDevice bleDevice,
                      String uuid_service,
                      String uuid_write,
                      byte[] data,
                      boolean split,
                      boolean sendNextWhenLastSuccess,
                      long intervalBetweenTwoPackage,
                      BleWriteCallback callback) {

        if (callback == null) {
            throw new IllegalArgumentException("BleWriteCallback can not be Null!");
        }

        if (data == null) {
            BleLog.e("data is Null!");
            callback.onWriteFailure(new OtherException("data is Null!"));
            return;
        }

        if (data.length > 20 && !split) {
            BleLog.w("Be careful: data's length beyond 20! Ensure MTU higher than 23, or use spilt write!");
        }

        BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice);
        if (bleBluetooth == null) {
            callback.onWriteFailure(new OtherException("This device not connect!"));
        } else {
            if (split && data.length > getSplitWriteNum()) {
                new SplitWriter().splitWrite(bleBluetooth, uuid_service, uuid_write, data,
                        sendNextWhenLastSuccess, intervalBetweenTwoPackage, callback);
            } else {
                bleBluetooth.newBleConnector()
                        .withUUIDString(uuid_service, uuid_write)
                        .writeCharacteristic(data, callback, uuid_write);
            }
        }
    }

最终数据的发送是依靠BluetoothGatt的writeCharacteristic(byte[] data, BleWriteCallback bleWriteCallback, String uuid_write)完成的。

接收数据

接收数据同样离不开BluetoothGatt。


    /**
     * Reads the requested characteristic from the associated remote device.
     *
     * <p>This is an asynchronous operation. The result of the read operation
     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
     * callback.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
     *
     * @param characteristic Characteristic to read from the remote device
     * @return true, if the read operation was initiated successfully
     */
    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
            return false;
        }

        if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
        if (mService == null || mClientIf == 0) return false;

        BluetoothGattService service = characteristic.getService();
        if (service == null) return false;

        BluetoothDevice device = service.getDevice();
        if (device == null) return false;

        synchronized (mDeviceBusyLock) {
            if (mDeviceBusy) return false;
            mDeviceBusy = true;
        }

        try {
            mService.readCharacteristic(mClientIf, device.getAddress(),
                    characteristic.getInstanceId(), AUTHENTICATION_NONE);
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
            mDeviceBusy = false;
            return false;
        }

        return true;
    }

同样读取数据需要进一步封装,便于使用。

    /**
     * read
     */
    public void readCharacteristic(BleReadCallback bleReadCallback, String uuid_read) {
        if (mCharacteristic != null
                && (mCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {

            handleCharacteristicReadCallback(bleReadCallback, uuid_read);
            if (!mBluetoothGatt.readCharacteristic(mCharacteristic)) {
                readMsgInit();
                if (bleReadCallback != null)
                    bleReadCallback.onReadFailure(new OtherException("gatt readCharacteristic fail"));
            }
        } else {
            if (bleReadCallback != null)
                bleReadCallback.onReadFailure(new OtherException("this characteristic not support read!"));
        }
    }

为了便于调用,还可以再封装一下。

    /**
     * read
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_read
     * @param callback
     */
    public void read(BleDevice bleDevice,
                     String uuid_service,
                     String uuid_read,
                     BleReadCallback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("BleReadCallback can not be Null!");
        }

        BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice);
        if (bleBluetooth == null) {
            callback.onReadFailure(new OtherException("This device is not connected!"));
        } else {
            bleBluetooth.newBleConnector()
                    .withUUIDString(uuid_service, uuid_read)
                    .readCharacteristic(callback, uuid_read);
        }
    }

这样调用的时候就方便了一些。这是直接读取蓝牙数据,通常我们的做法是通过发送消息的方式取读取数据。
蓝牙连接时已经设置了BluetoothGattCallback,在重写的onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 中获取接受的数据,接收数据后会通过Hander将数据发送。

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);

            Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry) iterator.next();
                Object callback = entry.getValue();
                if (callback instanceof BleReadCallback) {
                    BleReadCallback bleReadCallback = (BleReadCallback) callback;
                    if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
                        Handler handler = bleReadCallback.getHandler();
                        if (handler != null) {
                            Message message = handler.obtainMessage();
                            message.what = BleMsg.MSG_CHA_READ_RESULT;
                            message.obj = bleReadCallback;
                            Bundle bundle = new Bundle();
                            bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
                            bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
                            message.setData(bundle);
                            handler.sendMessage(message);
                        }
                    }
                }
            }
        }

最后在需要的地方通过Handler获取数据即可。

                    case BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE: {
                        BleNotifyCallback notifyCallback = (BleNotifyCallback) msg.obj;
                        Bundle bundle = msg.getData();
                        byte[] value = bundle.getByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE);
                        if (notifyCallback != null) {
                            notifyCallback.onCharacteristicChanged(value);
                        }
                        break;
                    }

至此蓝牙的简单使用流程完成。

举报

相关推荐

0 条评论