最近可穿戴设备发展的很火,而且蓝牙4.0 以上支持低功耗模式,因此,android4.3(API18)以上支持蓝牙BLE编程。BLE是蓝牙4.0的核心Profile,主打功能是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低,由于BLE的低功耗特点,因此普遍用于穿戴设备。下面介绍android 的BLE开发。

1.   基本概念介绍

  • BluetoothManager :管理蓝牙服务的一个类,主要用于得到一个蓝牙适配器BluetoothAdapter。
  • BluetoothAdapter类:代表了一个本地的蓝牙适配器。它是所有蓝牙交互的的入口点。利用它你可以发现其他蓝牙设备,查询绑定了的设备,使用已知的MAC地址实例化一个蓝牙设备和建立一个BluetoothServerSocket(作为服务器端)来监听来自其他设备的连接。 
  • BluetoothDevice类:代表了一个远端的蓝牙设备,使用它请求远端蓝牙设备连接或者获取远端蓝牙设备的名称、地址、种类和绑定状态。
  • BluetoothGatt类:符合BLE GATT协议,封装了与其他蓝牙设备通信功能的一个类。可以通过BluetoothDevice的connectGatt(Context, boolean, BluetoothGattCallback)得到其实例。
  • GATT(Generic Attribute Profile) 通用属性参数文件,通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。蓝牙 4.0特有。可以理解为一个规范文件。
  • ATT(Attribute Protocol) 属性协议。GATT是基于ATTProtocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics 和services的形式传输。
  • Service 服务。Characteristic的集合。例如一个service叫做“Heart RateMonitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart rate measurement"的Characteristic
  • Characteristic 特性。Characteristic可以理解为一个数据类型,它包括1个value和0至多个Descriptor
  • Descriptor 对Characteristic的描述,例如范围、计量单位等,一个Descriptor包含一个Value

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

 

2.   BLE编程

1.      权限配置

由于android4.3及以上才支持BLE,因此需指定编译版本为targetSdkVersion大于等于18。并且需要在AndroidManifest.xml文件中配置如下所示的权限

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

2.      编程步骤

1.获取BluetoothManager服务及BluetoothAdapter实例

BluetoothManager是系统管理蓝牙的一种服务,获取了BluetoothManager我们就可以通过其getAdapter()获取蓝牙适配器了。

//1.获取蓝牙管理服务及BluetoothAdapter实例
       mBluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
       mBluetoothAdapter = mBluetoothManager.getAdapter();
       if (mBluetoothAdapter == null){
            return;
       }

2.打开蓝牙

         接下来调用BluetoothAdapter的isEnabled()判断蓝牙是否打开,未打开我们可以发送一个action为BluetoothAdapter.ACTION_REQUEST_ENABLE的intent来打开蓝牙。

//2. 判断蓝牙是否打开,没有打开就打开
       if (!mBluetoothAdapter.isEnabled()){
            Intent intent = newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
           startActivityForResult(intent,REQUEST_ENABLE_BT);
       }

         这时界面上回弹出一个对话框提示我们打开蓝牙,打开成功后会回调Activity的onActivityResult(int requestCode, int resultCode, Intent data)方法,我们可以在里面提示一些信息。比如提示蓝牙已经打开等。

@Override
   protected void onActivityResult(int requestCode, int resultCode, Intentdata) {
       super.onActivityResult(requestCode, resultCode, data);
 
       if (resultCode != RESULT_OK){
            return;
       }
       if (requestCode == REQUEST_ENABLE_BT){
            Toast.makeText(this,"蓝牙已开启",Toast.LENGTH_LONG).show();
       }
   }

        

3.扫描周围的BLE蓝牙设备

startLeScan(BluetoothAdapter.LeScanCallbackcallback)即可扫描出BLE设备,在callback中会回调。但是对于5.0以上的系统,android添加了新的API,原有的startLeScan(BluetoothAdapter.LeScanCallback callback)已经被废弃,在5.0以上的系统中是使用BluetoothLeScanner的startScan(ScanCallbackcallback),回调也是ScanCallback了。

/**
     * 扫描Bluetooth LE
     * @param enable
     */
    private void scanBleDevice(boolean enable){
        //android 5.0 以前
        if (Build.VERSION.SDK_INT < 21){
            if (enable){
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mScaning = false;
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);
                    }
                },SCAN_SECOND);
                mScaning = true;
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            } else {
                mScaning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        } else {
            scanner = mBluetoothAdapter.getBluetoothLeScanner();
            scanner.startScan(mScanCallback);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    scanner.stopScan(mScanCallback);
                }
            },SCAN_SECOND);
        }
    }

 扫描的回调如下:

//sacn扫描回调 5.0以上用
   private ScanCallback mScanCallback = new ScanCallback() {
       @Override
       public void onScanResult(int callbackType, ScanResult result) {
            BluetoothDevice device =result.getDevice();
            if (device != null){
                //过滤掉其他设备
                if (device.getName() != null&& device.getName().startsWith("WINPOS")){
                    BLEDevice bleDevice = newBLEDevice(device.getName(),device.getAddress());
                    if(!mBLEDeviceList.contains(bleDevice)){
                       mBLEDeviceList.add(bleDevice);
                       showBluetoothLeDevice(bleDevice);
                    }
                }
            }
       }
 
       @Override
       public void onBatchScanResults(List<ScanResult> results) {
 
           Log.d(TAG,"onBatchScanResults");
       }
 
       @Override
       public void onScanFailed(int errorCode) {
           Log.d(TAG,"onScanFailed");
       }
   };
 
 
   //4.3以上
   private BluetoothAdapter.LeScanCallback mLeScanCallback = newBluetoothAdapter.LeScanCallback() {
       @Override
       public void onLeScan(final BluetoothDevice bluetoothDevice, int i,byte[] bytes) {
 
            if (bluetoothDevice != null){
                //过滤掉其他设备
                if (bluetoothDevice.getName()!= null && bluetoothDevice.getName().startsWith("WINPOS")){
                    BLEDevice bleDevice = newBLEDevice(bluetoothDevice.getName(),bluetoothDevice.getAddress());
                    if(!mBLEDeviceList.contains(bleDevice)){
                       mBLEDeviceList.add(bleDevice);
                       showBluetoothLeDevice(bleDevice);
                    }
                }
            }
       }
   };

         这里我预先定义了BLEDevice类,用来表示BLE设备。定义如下:

public class BLEDevice {
   private String name;
   private String mac;
   public BLEDevice(String name, String mac) {
       this.name = name;
       this.mac = mac;
   }
 
   public String getName() {
       return name;
   }
 
   public void setName(String name) {
       this.name = name;
   }
 
   public String getMac() {
       return mac;
   }
 
   public void setMac(String mac) {
       this.mac = mac;
   }
 
   @Override
   public boolean equals(Object o) {
 
       return !TextUtils.isEmpty(this.mac) &&!TextUtils.isEmpty(((BLEDevice)o).mac)
                &&this.mac.equals(((BLEDevice) o).getMac());
 
   }
}



         扫描时我将扫描到的设备添加到List<BLEDevice> mBLEDeviceList中并显示出来。

/**
    * 显示BLE设备
    * @param device
    */
   private void showBluetoothLeDevice(BLEDevice device){
       if (device == null){
            showToast("没有发现BLE设备");
            return;
       }
       mTextView.append(device.getName() + "   " + device.getMac() + "\n");
   }



4.获取远程设备,连接GATT服务