Android Bluetooth 入门

作者: cff70524f5cf | 来源:发表于2019-04-09 21:36 被阅读48次

    概述

    蓝牙(英语:Bluetooth),一种无线通讯技术标准,用来让固定与移动设备,在短距离间交换数据,以形成个人局域网(PAN)。蓝牙技术当前由蓝牙技术联盟(SIG)来负责维护其技术标准。

    版本

    image.png

    分类

    蓝牙可分为经典蓝牙(Classic Bluetooth)、低功耗蓝牙(Bluetooth Low Energy)、双模蓝牙三大类。2009 年发布的蓝牙 3.0 及之前的蓝牙版本包括 BR、EDR 和 HS(AMP) 三种蓝牙技术,统称为经典蓝牙技术,只支持经典蓝牙技术的蓝牙称为经典蓝牙。2010 年 SIG 联盟合并了 Wibree 联盟,并把 Wibree 联盟提出的低功耗无线技术重新命名为低功耗蓝牙技术(BLE)。2010 年发布的蓝牙 4.0 规格就同时包含经典蓝牙和低功耗蓝牙,只支持低功耗蓝牙技术的蓝牙称为低功率蓝牙,同时支持经典蓝牙和低功率蓝牙技术的蓝牙称为双模蓝牙。低功耗蓝牙与经典蓝牙技术是不兼容的,所以低功耗蓝牙和经典蓝牙两者之间是不能相互通信的。

    优缺点

    经典蓝牙技术持续保持连接,可以传输大数据,适合文件传输、音频播放等,兼容性高,广播信道多,缺点是连接成本高,功耗高;低功耗蓝牙连接速度快,低功耗,广播信道少,缺点是数据传输量有限制。

    Android

    Android 平台包含蓝牙网络堆栈支持,凭借此项支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。针对具有低功耗要求的蓝牙设备,Android 4.3(API 级别 18)中引入了面向低功耗蓝牙的 API 支持。即 SPP 协议(经典蓝牙)和 GATT 协议(低功耗蓝牙)的连接方式。 使用 Android 蓝牙功能需要在 Manifest.xml 文件中声明蓝牙权限,如果使用了蓝牙扫描功能还需要声明位置权限,因为蓝牙是具有定位功能的,目标版本是 Android 23 及以上版本需要添加动态权限申请。

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    // 蓝牙扫描需要位置权限
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    

    SPP 方式

    1. 启用蓝牙

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

    1. 查询配对的设备

    Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    // If there are paired devices
    if (pairedDevices.size() > 0) {
        // Loop through paired devices
        for (BluetoothDevice device : pairedDevices) {
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
    

    1. 发现设备,这里需要注册 ACTION_FOUND Intent BroadcastReceiver, Intent 携带额外字段 EXTRA_DEVICE 和 EXTRA_CLASS,二者分别包含 BluetoothDevice 和 BluetoothClass,如果没申请位置权限,这里发现不了其他设备。

    mBluetoothAdapter.startDiscovery();
    // Create a BroadcastReceiver for ACTION_FOUND
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // When discovery finds a device
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // Add the name and address to an array adapter to show in a ListView
                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        }
    };
    // Register the BroadcastReceiver
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
    

    1. 连接设备,蓝牙连接属于耗时操作,需要放在子线程执行。在蓝牙协议中,UUID 被用来标识蓝牙设备所提供的服务,并非是标识蓝牙设备本身。一个蓝牙设备可以提供多种服务,比如 A2DP(蓝牙音频传输)、HEADFREE(免提)、PBAP(电话本)等,每种服务都对应一个UUID。连接成功后可以获取蓝牙设备的输入输出流,进行数据交互操作。

    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
    
        public ConnectThread(BluetoothDevice device) {
            // Use a temporary object that is later assigned to mmSocket,
            // because mmSocket is final
            BluetoothSocket tmp = null;
            mmDevice = device;
    
            // Get a BluetoothSocket to connect with the given BluetoothDevice
            try {
                // MY_UUID is the app's UUID string, also used by the server code
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) { }
            mmSocket = tmp;
        }
    
        public void run() {
            // Cancel discovery because it will slow down the connection
            mBluetoothAdapter.cancelDiscovery();
    
            try {
                // Connect the device through the socket. This will block
                // until it succeeds or throws an exception
                mmSocket.connect();
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException connectException) {
                // Unable to connect; close the socket and get out
                try {
                    mmSocket.close();
                } catch (IOException closeException) { }
                return;
            }
    
            // Do work to manage the connection (in a separate thread)
            manageConnectedSocket(mmSocket);
        }
    
        /** Will cancel an in-progress connection, and close the socket */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }
    

    GATT 方式

    1. 启用蓝牙和 SPP 方式是一样的
    2. 发现设备,同样需要在子线程执行,不过通过接收回调的方式获取发现的设备,而不是注册广播。不过需要注意的是发现设备是高功耗行为,需要设置一个超时时间停止扫描。
    private ScanCallback mLeScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
        }
    
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }
    
        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };
    
     mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            bluetoothLeScanner.stopScan(mLeScanCallback);
        }
      }, SCAN_PERIOD);
     bluetoothLeScanner.startScan(mLeScanCallback);
    
    1. 连接设备,可以在 BluetoothGattCallback 中实现各种回调方法,如发现服务、监听蓝牙数据等。值得注意的一点是连接 API 在 Android version >= 23 时多了一个参数,如果不加上 BluetoothDevice.TRANSPORT_LE 可能导致连接不上 BLE 设备
    // Various callback methods defined by the BLE API.
        private final BluetoothGattCallback gattCallback =
                new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status,
                    int newState) {
                String intentAction;
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    intentAction = ACTION_GATT_CONNECTED;
                    connectionState = STATE_CONNECTED;
                    Log.i(TAG, "Connected to GATT server.");
                    Log.i(TAG, "Attempting to start service discovery:" +
                            bluetoothGatt.discoverServices());
    
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    intentAction = ACTION_GATT_DISCONNECTED;
                    connectionState = STATE_DISCONNECTED;
                    Log.i(TAG, "Disconnected from GATT server.");
                }
            }
    
            @Override
            // New services discovered
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
    
                } else {
                    Log.w(TAG, "onServicesDiscovered received: " + status);
                }
            }
    
            @Override
            // Result of a characteristic read operation
            public void onCharacteristicRead(BluetoothGatt gatt,
                    BluetoothGattCharacteristic characteristic,
                    int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
                }
            }
         ...
        };
    BluetoothGatt bluetoothGatt;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
         bluetoothGatt = device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
    } else {
       bluetoothGatt = device.connectGatt(context, false, mGattCallback);
    }
    

    总结

    其实 Android 蓝牙相关的 API 使用还是比较简单的,对于初次接触蓝牙开发的小白来说,弄清楚蓝牙设备的类型以及连接方式就成功了一半(经典蓝牙 SPP,低功耗蓝牙 GATT)。另外一半在于蓝牙设备的适配及细节的掌握,适配问题可以多联系蓝牙设备供应商的技术支持,细节问题就靠平时积累和多搜索查阅资料了。 最后安利两个 BLE 开源库:RxAndroidBleflutter_blue

    最后

    点赞+加群免费获取 Android IOC架构设计

    加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

    相关文章

      网友评论

        本文标题:Android Bluetooth 入门

        本文链接:https://www.haomeiwen.com/subject/amdwiqtx.html