一、关键概念:


Generic Attribute Profile (GATT)


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


 


Attribute Protocol (ATT)


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


 


Characteristic


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


 


Descriptor


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


 


Service


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


 


二、角色和职责:


Android设备与BLE设备交互有两组角色:


 


中心设备和外围设备(Central vs. peripheral);


GATT server vs. GATT client.


 


Central vs. peripheral:


中心设备和外围设备的概念针对的是BLE连接本身。Central角色负责scan advertisement。而peripheral角色负责make advertisement。


 


GATT server vs. GATT client:


这两种角色取决于BLE连接成功后,两个设备间通信的方式。


 


举例说明:


现有一个活动追踪的BLE设备和一个支持BLE的Android设备。Android设备支持Central角色,而BLE设备支持peripheral角色。创建一个BLE连接需要这两个角色都存在,都仅支持Central角色或者都仅支持peripheral角色则无法建立连接。


 


当连接建立后,它们之间就需要传输GATT数据。谁做server,谁做client,则取决于具体数据传输的情况。例如,如果活动追踪的BLE设备需要向Android设备传输sensor数据,则活动追踪器自然成为了server端;而如果活动追踪器需要从Android设备获取更新信息,则Android设备作为server端可能更合适。


 


 


三、权限及feature:


和经典蓝牙一样,应用使用蓝牙,需要声明BLUETOOTH权限,如果需要扫描设备或者操作蓝牙设置,则还需要BLUETOOTH_ADMIN权限:


<uses-permission android:name="android.permission.BLUETOOTH"/> 
  
 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>


 


除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:


<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>


 


按时required为true时,则应用只能在支持BLE的Android设备上安装运行;required为false时,Android设备均可正常安装运行,需要在代码运行时判断设备是否支持BLE feature:


 


// Use this check to determine whether BLE is supported on the device. Then 
  
 // you can selectively disable BLE-related features. 
  
 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 
  
     Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); 
  
     finish(); 
  
 }

 


 


四、启动蓝牙:


在使用蓝牙BLE之前,需要确认Android设备是否支持BLE feature(required为false时),另外要需要确认蓝牙是否打开。 


如果发现不支持BLE,则不能使用BLE相关的功能。如果支持BLE,但是蓝牙没打开,则需要打开蓝牙。


 


打开蓝牙的步骤:


 


1、获取BluetoothAdapter


 


BluetoothAdapter是Android系统中所有蓝牙操作都需要的,它对应本地Android设备的蓝牙模块,在整个系统中BluetoothAdapter是单例的。当你获取到它的示例之后,就能进行相关的蓝牙操作了。


 


获取BluetoothAdapter代码示例如下:


// Initializes Bluetooth adapter. 
 
 final BluetoothManager bluetoothManager = 
 
         (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 
 
 mBluetoothAdapter = bluetoothManager.getAdapter();



 


注:这里通过getSystemService获取BluetoothManager,再通过BluetoothManager获取BluetoothAdapter。BluetoothManager在Android4.3以上支持(API level 18)。


 


2、判断是否支持蓝牙,并打开蓝牙


 


获取到BluetoothAdapter之后,还需要判断是否支持蓝牙,以及蓝牙是否打开。


如果没打开,需要让用户打开蓝牙:


private BluetoothAdapter mBluetoothAdapter; 
  
 ... 
  
 // Ensures Bluetooth is available on the device and it is enabled. If not, 
  
 // displays a dialog requesting user permission to enable Bluetooth. 
  
 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { 
  
     Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 
  
     startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); 
  
 }


 


 


五、搜索BLE设备:


通过调用BluetoothAdapter的 startLeScan()搜索BLE设备。调用此方法时需要传入  BluetoothAdapter.LeScanCallback参数。


因此你需要实现  BluetoothAdapter.LeScanCallback接口,BLE设备的搜索结果将通过这个callback返回。


 


由于搜索需要尽量减少功耗,因此在实际使用时需要注意:


 


1、当找到对应的设备后,立即停止扫描;


2、不要循环搜索设备,为每次搜索设置适合的时间限制。避免设备不在可用范围的时候持续不停扫描,消耗电量。


 


搜索的示例代码如下:


/** 
  
  * Activity for scanning and displaying available BLE devices. 
  
  */ 
  
 public class DeviceScanActivity extends ListActivity { 
  

     private BluetoothAdapter mBluetoothAdapter; 
  
     private boolean mScanning; 
  
     private Handler mHandler; 
  

     // Stops scanning after 10 seconds. 
  
     private static final long SCAN_PERIOD = 10000; 
  
     ... 
  
     private void scanLeDevice(final boolean enable) { 
  
         if (enable) { 
  
             // Stops scanning after a pre-defined scan period. 
  
             mHandler.postDelayed(new Runnable() { 
  
                 @Override 
  
                 public void run() { 
  
                     mScanning = false; 
  
                     mBluetoothAdapter.stopLeScan(mLeScanCallback); 
  
                 } 
  
             }, SCAN_PERIOD); 
  

             mScanning = true; 
  
             mBluetoothAdapter.startLeScan(mLeScanCallback); 
  
         } else { 
  
             mScanning = false; 
  
             mBluetoothAdapter.stopLeScan(mLeScanCallback); 
  
         } 
  
         ... 
  
     } 
  
 ... 
  
 }


 


如果你只需要搜索指定UUID的外设,你可以调用  startLeScan(UUID[], BluetoothAdapter.LeScanCallback)方法。


其中UUID数组指定你的应用程序所支持的GATT Services的UUID。


 


 


BluetoothAdapter.LeScanCallback的实现示例如下:


private LeDeviceListAdapter mLeDeviceListAdapter; 
  
 ... 
  
 // Device scan callback. 
  
 private BluetoothAdapter.LeScanCallback mLeScanCallback = 
  
         new BluetoothAdapter.LeScanCallback() { 
  
     @Override 
  
     public void onLeScan(final BluetoothDevice device, int rssi, 
  
             byte[] scanRecord) { 
  
         runOnUiThread(new Runnable() { 
  
            @Override 
  
            public void run() { 
  
                mLeDeviceListAdapter.addDevice(device); 
  
                mLeDeviceListAdapter.notifyDataSetChanged(); 
  
            } 
  
        }); 
  
    } 
  
 };

 


注意:搜索时,你只能搜索传统蓝牙设备或者BLE设备,两者完全独立,不可同时被搜索。


 


 


六、连接GATT Server:


两个设备通过BLE通信,首先需要建立GATT连接。这里我们讲的是Android设备作为client端,连接GATT Server。


连接GATT Server,你需要调用BluetoothDevice的 connectGatt()方法。此函数带三个参数:Context、autoConnect(boolean)和 BluetoothGattCallback对象。调用示例:


 


mBluetoothGatt = device.connectGatt(this, false, mGattCallback);


 


函数成功,返回 BluetoothGatt对象,它是GATT profile的封装。通过这个对象,我们就能进行GATT Client端的相关操作。 BluetoothGattCallback用于传递一些连接状态及结果。


 


BluetoothGatt常规用到的几个操作示例:


 


connect() :连接远程设备。


discoverServices() : 搜索连接设备所支持的service。


disconnect():断开与远程设备的GATT连接。


close():关闭GATT Client端。


readCharacteristic(characteristic) :读取指定的characteristic。


setCharacteristicNotification(characteristic, enabled) :设置当指定characteristic值变化时,发出通知。


getServices() :获取远程设备所支持的services。


 


等等。


 


注:


1、某些函数调用之间存在先后关系。例如首先需要connect上才能discoverServices。


2、一些函数调用是异步的,需要得到的值不会立即返回,而会在BluetoothGattCallback的回调函数中返回。例如discoverServices与onServicesDiscovered回调,readCharacteristic与onCharacteristicRead回调,setCharacteristicNotification与onCharacteristicChanged回调等。