
概述
定义
蓝牙是一种支持设备短距离通信的无线电技术,具有低功耗、速率快、成本低等特性。
来源
“蓝牙”(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.2 | 2020 | 48Mbit/s | 300 | 
| 5.1 | 2019 | 48Mbit/s | 300 | 
| 5.0 | 2016 | 48Mbit/s | 300 | 
| 4.2 | 2014 | 24Mbit/s | 50 | 
| 4.1 | 2013 | 24Mbit/s | 50 | 
| 4.0 | 2010 | 24Mbit/s | 50 | 
| 3.0+HS | 2009 | 24Mbit/s | 10 | 
| 2.1+EDR | 2007 | 3Mbit/s | 10 | 
| 2.0+EDR | 2004 | 2.1Mbit/s | 10 | 
| 1.2 | 2003 | 1Mbit/s | 10 | 
| 1.1 | 2002 | 810Kbit/s | 10 | 
| 1.0 | 1998 | 723.1Kbit/s | 10 | 
- 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,则已开启,否则处于关闭状态,需要打开蓝牙,打开蓝牙有两种方式:
- 直接
 
bluetoothAdapter.enable();
 
- 间接
 
     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()进行连接。
- 根据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;
                    }
 
至此蓝牙的简单使用流程完成。










