Android5.0(Lollipop) 蓝牙BLE(Bluetooth Low Energy)

一、蓝牙BLE(Bluetooth Low Energy)介绍概要

      蓝牙低能耗(BLE)技术是低成本、短距离、可互操作的鲁棒性无线技术,工作在免许可的2.4GHz ISM射频频段。它从一开始就设计为超低功耗(ULP)无线技术。

      蓝牙低能耗技术的三大特性成就了ULP性能,这三大特性分别是最大化的待机时间、快速连接和低峰值的发送/接收功耗。

      无线“开启”的时间只要不是很短就会令电池寿命急剧降低,因此任何必需的发送或接收任务需要很快完成。被蓝牙低能耗技术用来最小化无线开启时间的第一个技巧是仅用3个“广告”信道搜索其它设备,或向寻求建立连接的设备宣告自身存在。相比之下,标准蓝牙技术使用了32个信道。

      蓝牙低能耗技术“完成”一次连接(即扫描其它设备、建立链路、发送数据、认证和适当地结束)只需3ms。而标准蓝牙技术完成相同的连接周期需要数百毫秒。再次提醒,无线开启时间越长,消耗的电池能量就越多。

      具有低功耗蓝牙模块的设备可以扮演2个角色,中心,周边。周边是数据提供者,中心是数据接收/处理者。IOS设备可以很好的扮演这2个角色,利用现成的API就能开发出具有周边和中心功能的应用,我大Android就有点悲催了,自Android 4.3(API 18)的系统就规定了BLE的API,但是仅限于中心,至于周边一直没有API的支持。直到2014.6.26 Android Lollipop(Android 5.0 API 21)的面世,才带来了周边API的支持(BluetoothLeAdvertiser)。

      ble在抓包软件或硬件上又分为Master和Slave。Master可以同时与多个设备连接,通信;Slave只能和一个Master通信。Cnetral主机(常作为client端):如手机,PCPeripheral从机(常作为Service端):如心率计,血糖计

二、关键概念:

(1)Generic Attribute Profile (GATT)

      通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。

(2)Attribute Protocol (ATT)

      GATT是基于ATT Protocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。

(3)Characteristic

      Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。

(4)Descriptor

      对Characteristic的描述,例如范围、计量单位等。

(5)Service

      Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart rate measurement"的Characteristic。

(6)关联

      BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTY。


三、相关权限

<!-- 允许程序进行发现和配对新的蓝牙设备 -->

<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 允许程序进行发现和配对新的蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 
下面这两个权限是ScanBle需要的(二选一即可)
注意:标准蓝牙和低功耗在Android 6.0以上都是需要该权限才能扫描得到的,而Android 6.0以下不用该权限标准蓝牙也能扫描的到。
<!-- 访问CellID或WiFi,只要当前设备可以接收到基站的服务信号,便可获得位置信息 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 更精确的GPS需要该权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 
除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
代码判断是否支持BLE
getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)

四、主要代码介绍
public class BluetoothBleDemo extends Activity {
    private static final int REQUEST_CODE_BLUETOOTH_ENABLE = 1;
    private static final int SCAN_PERIOD = 10000;

    private BluetoothAdapter bluetoothAdapter;
    //import android.bluetooth.le.ScanCallback;
	//Call requires API level 21 (current min is 18): new android.bluetooth.le.ScanCallback
	//java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results
    private ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            //Batch 一批
			for(ScanResult result : results) {
				System.out.println("onBatchScanResults " + result);
			}
        }

        @Override
        public void onScanResult(int callbackType, ScanResult result) {	        
			System.out.println("onScanResult " + result);

			BluetoothDevice device = result.getDevice();
	        System.out.println("Device name: " + device.getName());
	        //Returns the hardware address of this BluetoothDevice. For example, "00:11:22:AA:BB:CC".
	        System.out.println("Device address: " + device.getAddress());
	        System.out.println("Device service UUIDs: " + device.getUuids());
	        //Get the Bluetooth device type of the remote device. 
	        System.out.println("Device type: " + device.getType());
	        //Possible values for the bond state are: BOND_NONE, BOND_BONDING, BOND_BONDED. 
	        System.out.println("Device bondState: " + device.getBondState());
	        
	        ScanRecord record = result.getScanRecord();
	        //Returns the advertising flags indicating the discoverable mode and capability of the device. Returns -1 if the flag field is not set.
	        System.out.println("Record advertise flags: 0x" + Integer.toHexString(record.getAdvertiseFlags()));
	        /*
	         txPowerLevel 发射功率等级  
	         Returns the transmission power level of the packet in dBm. Returns Integer.MIN_VALUE if the field is not set. This value can be used to calculate the path loss of a received packet using the following equation: 
	         pathloss = txPowerLevel - rssi
	         */
	        System.out.println("Record Tx power level: " + record.getTxPowerLevel());
	        System.out.println("Record device name: " + record.getDeviceName());
	        System.out.println("Record service UUIDs: " + record.getServiceUuids());
	        //Returns a map of service UUID and its corresponding service data.
	        System.out.println("Record service data: " + record.getServiceData());
	        //Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific 
	        System.out.println("Record manufacturer specific data: " + record.getManufacturerSpecificData());


	        //RSSI 信号强度,可以用来测算距离  Returns the received signal strength in dBm. The valid range is [-127, 127].
	        System.out.println("result rssi: " + result.getRssi());  
	        //Returns timestamp since boot when the scan record was observed.
	        System.out.println("result timestampNanos: " + result.getTimestampNanos()); 
            
			switch(callbackType) {
			case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
				break;
			case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
				break;
			case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
				break;
			}
        }

        @Override
        public void onScanFailed(int errorCode) {
			System.out.println("onScanFailed errorCode = " + errorCode);
        }
    };
    private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
        }

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

        @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 onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
        }

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

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

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

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

        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            super.onReliableWriteCompleted(gatt, status);
        }
    };
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //判断是否支持BLE特性
        if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            initBluetoothBle();
        } else {
            showToast("this device can not support Bluetooth BLE");
        }
    }

    private void initBluetoothBle() {
        //BluetoothAdapter是Android系统中所有蓝牙操作都需要的,它对应本地Android设备的蓝牙模块,在整个系统中BluetoothAdapter是单例的。当你获取到它的示例之后,就能进行相关的蓝牙操作了。
        //BluetoothManager在Android4.3以上支持(API level 18)
        BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();
    }

    /**
     * 开启蓝牙
     * 打开和关闭蓝牙模块, 都可以通过ACTION_STATE_CHANGED广播来监听
     */
    private void requestEnable() {
        //第一种方法打开蓝牙, 系统会提示应用要打开蓝牙,是否授权;disable不会有任何的提示。
        //boolean result = bluetoothAdapter.enable();
        //第二种方法发送广播, 会弹出一个对话框, 选择是否打开蓝牙, 选择是蓝牙才打开。
        if(bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQUEST_CODE_BLUETOOTH_ENABLE);
        }
    }

    /**
     * 扫描BLE设备.注意该方法无法扫描标准蓝牙,只能扫描BLE设备
     */
    private void scanBleDevice(final boolean enabled) {
        if(bluetoothAdapter == null) {
            return;
        }
        /*
        为什么不能再使用单例的BluetoothAdapter? 原因如下:
        bluetoothAdapter.startLeScan() //deprecated
        http://stackoverflow.com/questions/30223071/startlescan-replacement-to-current-api
        Remember that the method: public BluetoothLeScanner getBluetoothLeScanner () isn't static.
        If you do: BluetoothAdapter.getBluetoothLeScanner()
        you will get an error, since getDefaultAdapter() is a static method, but getBluetoothLeScanner() isn't.
        You need an instance of a BluetoothAdapter.
         */
        final BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
        if(enabled) {
            //scan分为2类,而在android L之前,搜索条件只有uuid
            //(1)直接搜索全部周围peripheral(外围的)设备,搜索结果将通过这个callback返回
            scanner.startScan(scanCallback);
            //(2)根据过滤条件搜索设备
            final List<ScanFilter> scanFilters = new ArrayList<ScanFilter>();
            //uuid格式8-4-4-4-12(32位,128bit)
            //address格式(12位,48bit)
            scanFilters.add(new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("00000000-0000-0000-0000-000000000000")).setDeviceAddress("00:00:00:00:00:00").build());
            ScanSettings scanSettings = new ScanSettings.Builder()
                    //require API 23
                    //.setCallbackType(0).setMatchMode(0).setNumOfMatches(0)
                    .setReportDelay(0).setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE).build();
            scanner.startScan(scanFilters, scanSettings, scanCallback);
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    scanner.stopScan(scanCallback);
                }
            }, SCAN_PERIOD);
        } else {
            scanner.stopScan(scanCallback);
        }
    }

    /**
     * 两个设备通过BLE通信,首先需要建立GATT连接。这里我们讲的是Android设备作为client端,连接GATT Server。数据发送方向总是从server推送到client
     */
    private void connectToGATTServer(BluetoothDevice device) {
        //函数成功,返回BluetoothGatt对象,它是GATT profile的封装。通过这个对象,我们就能进行GATT Client端的相关操作。BluetoothGattCallback用于传递一些连接状态及结果。
        BluetoothGatt bluetoothGatt = device.connectGatt(this, false, gattCallback);

        //连接远程设备
        boolean connectResult = bluetoothGatt.connect();
        //搜索连接设备所支持的service
        boolean discoverResult = bluetoothGatt.discoverServices();
        //断开与远程设备的GATT连接
        bluetoothGatt.disconnect();
        //关闭GATT Client端
        bluetoothGatt.close();
        //读取指定的characteristic。
        //boolean readResult = bluetoothGatt.readCharacteristic(characteristic);
        //设置当指定characteristic值变化时,发出通知
        //boolean setResult = bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
        //获取远程设备所支持的services
        List<BluetoothGattService> gattServices = bluetoothGatt.getServices();
    }

    private void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}
扫描结果如下图



(未完待续,上面讲了Android作为GattClient,剩下通信、Android做为GattServer还有数据分包的情况未讲——先记录一下,免得忘记)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值