Android低功耗蓝牙的那点事儿

ShaClutterb 8年前
   <p>近期项目使用到了蓝牙技术,菜鸟一枚,网上各种找资料,发现不是不全就是过时,要么就是抄袭转载,真实醉了,现在将这一块的东西整理出来,供大家参考。</p>    <p>基本概念</p>    <p>Android中的蓝牙分两种:经典蓝牙、低功耗蓝牙。</p>    <ol>     <li>二者本质上没有太多区别,可以理解为后者是前者的升级优化版本。对于API上的实现区别还是很大的。</li>     <li>工作流程:发现设备->配对/绑定设备->建立连接->数据通信。<br> 至于底层如何工作,本人不了解,也不是本文关注的重点。</li>     <li>官方文档: <a href="/misc/goto?guid=4959745927891287011" rel="nofollow,noindex">https://developer.android.com/guide/topics/connectivity/bluetooth.html?hl=zh-cn</a></li>    </ol>    <p>重要实例</p>    <ol>     <li>经典蓝牙聊天实例: <a href="/misc/goto?guid=4959745927976441274" rel="nofollow,noindex">https://github.com/googlesamples/android-BluetoothChat</a></li>     <li>低功耗蓝牙: <a href="/misc/goto?guid=4959745928059303347" rel="nofollow,noindex">https://github.com/googlesamples/android-BluetoothLeGatt</a></li>     <li>作为外设(API>=21): <a href="/misc/goto?guid=4959745928154502904" rel="nofollow,noindex">https://github.com/googlesamples/android-BluetoothAdvertisements</a></li>     <li>基础概念讲解: <a href="/misc/goto?guid=4959745928235577911" rel="nofollow,noindex">http://blog.csdn.net/qinxiandiqi/article/details/40741269</a></li>     <li>深入理论: <a href="/misc/goto?guid=4959727008490868790" rel="nofollow,noindex">http://www.race604.com/android-ble-in-action/</a></li>    </ol>    <p>注意事项</p>    <ol>     <li> <p>低功耗蓝牙(BLE)Android 4.3(API 18)以上才支持</p> </li>     <li> <p>使用蓝牙需要权限</p> <pre>  <code class="language-java"><uses-permission android:name="android.permission.BLUETOOTH" />  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />  </code></pre> </li>     <li> <p>Android 5.0(API 21) 扫描蓝牙需要定位权限,否则扫描不到设备,实际使用时候发现 5.0不需要也可以扫描,Android 6.0(API 23)以上必须(不知道什么原因,测试机器:MI 2,知道原因的可告知)</p> <pre>  <code class="language-java"><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 或  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />  </code></pre> <p>官方文档: <a href="/misc/goto?guid=4959745928349516766" rel="nofollow,noindex">https://developer.android.com/guide/topics/connectivity/bluetooth-le.html?hl=zh-cn</a></p> </li>     <li> <p>低功耗蓝牙要声明特征,或者代码判断</p> <pre>  <code class="language-java">// 如果为true表示只能在支持低功耗蓝牙的设备上使用,如果不支持的设备也可以使用,采用代码判断  <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>  // 代码判断  if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {      Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();      finish();  }  </code></pre> </li>     <li> <p>经典蓝牙连接成功后获取一个socket连接得到输入输出流进行通信,低功耗通过特征(具体实现不知道是什么)</p> </li>     <li> <p>Android 5.0(API 21)之前不能当成外设(蓝牙耳机、音响等)来使用,只能作为中心即主机</p> </li>    </ol>    <p>低功耗蓝牙</p>    <p>现在蓝牙开发基本上都是低功耗蓝牙,比如心率设备、耳机设备、手环,所以我们先从重要的开始,讲讲低功耗蓝牙的使用,后面在完善经典蓝牙。</p>    <p>声明权限</p>    <p>创建项目在AndroidManifest.xml中声明权限:</p>    <pre>  <code class="language-java"><manifest xmlns:android="http://schemas.android.com/apk/res/android"            package="com.lowett.android">    // 定位权限第二个包含第一个,所以这里就声明了一个,两个都声明也可以    <!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> -->    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>     <uses-permission android:name="android.permission.BLUETOOTH"/>    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>    // 是否必须支持低功耗蓝牙,此处不必须     <uses-feature        android:name="android.hardware.bluetooth_le"        android:required="false"/>     // 是有gps硬件,这个现在的智能手机没有不支持的吧    <uses-feature android:name="android.hardware.location.gps"/>      </manifest>  </code></pre>    <p>如果manifest中声明的蓝牙特性为false,那么在代码中监测是否支持BLE特性,</p>    <pre>  <code class="language-java">// 使用此检查确定BLE是否支持在设备上,然后你可以有选择性禁用BLE相关的功能  if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {      Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();      finish();  }  </code></pre>    <p>BluetoothAdapter</p>    <p>获取BluetoothManager得到蓝牙适配器BluetoothAdapter,注意这两个类都是系统级别,只有一个,代表了你手机上的蓝牙模块</p>    <pre>  <code class="language-java">final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);  BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();  </code></pre>    <p>如果获取到的Adapter为空说明不支持蓝牙,或者没有蓝牙模块</p>    <p>开启蓝牙</p>    <p>前面获取到了BluetoothAdapter,可以通过调用isEnabled()函数去检测是否开启了蓝牙,false表示没有开启,如果没有开启需要去启用,</p>    <pre>  <code class="language-java">Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);  </code></pre>    <p>代码执行完后,会有弹框提示用户是否启用,我们需要在onActivityResult()中判断返回</p>    <pre>  <code class="language-java">@Override  public void onActivityResult(int requestCode, int resultCode, Intent data) {      super.onActivityResult(requestCode, resultCode, data);      if (requestCode == BluetoothLeManager.REQUEST_ENABLE_BT) {          if (resultCode == Activity.RESULT_OK) {             // something, 去扫描设备              startScan();          } else {              new AlertDialog.Builder(this)                      .setMessage("请开启蓝牙,连接设备")                      .setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {                          @Override                          public void onClick(DialogInterface dialog, int which) {                              dialog.dismiss();                             // something                          }                      })                      .create()                      .show();          }      }  }  </code></pre>    <p>扫描设备</p>    <p>低功耗蓝牙和经典蓝牙的扫描方式不同,如果熟悉经典蓝牙,那就要可能掉进坑了起不来了,哈哈。蓝牙扫描耗电比较严重,所以此处一定要记得在合适的实际停止扫描,比如定时停止、扫描到目标设备就停止(奇怪的是API为何不提供扫描时长的接口呢?)扫描时调用Adapter的startLeScan()方法,然而这个被标记为过时,API>=21被另一个取代。然后通过回掉得到扫描结果(经典蓝牙是广播)</p>    <pre>  <code class="language-java"> public void startScan() {      // 初始化一个handler          initHandler();          if (!mScanning) {              if (scanRunnable == null) {                  scanRunnable = new Runnable() {                      @Override                      public void run() {                          stopScan();                      }                  };              }              // SCAN_PERIOD = 3 * 10 * 1000, 30s后停止扫面              mHandler.postDelayed(scanRunnable, SCAN_PERIOD);  // 新API  //            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  //                mBluetoothAdapter.getBluetoothLeScanner().startScan(this);  //            }else {            // this 实现了BluetoothAdapter.LeScanCallback,即扫描结果回掉                  mBluetoothAdapter.startLeScan(this);  //            }              mScanning = true;              Log.i(TAG, "开始扫描,蓝牙设备");          }      }  </code></pre>    <p>回调函数</p>    <pre>  <code class="language-java">@Override  public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {   // 得到扫描结果      Log.i(TAG, "扫描到的设备, name=" + device.getName() + ",address=" + device.toString());  }  </code></pre>    <p>注意:device:代表外设即目标设备 rssi:设一个强度值,但是时负值,利用这个值通过公式可以算出离你的距离</p>    <p>scanRecord:广播数据,附加的数据,没用到</p>    <p>停止扫描</p>    <p>扫描完成务必停止,因为扫描不仅耗电,还影响连接速度,所以当要连接的时候,先停止扫描时必须的</p>    <pre>  <code class="language-java">public void stopScan() {      initHandler();      Log.i(TAG, "停止扫描,蓝牙设备");      if (mScanning) {          mScanning = false;          // 开始扫描的接口,要一样的不然停止不了          mBluetoothAdapter.stopLeScan(this);      }        if (scanRunnable != null) {          mHandler.removeCallbacks(scanRunnable);          scanRunnable = null;      }  }  </code></pre>    <p>连接设备</p>    <p>通常连接设备速度还是很快的,连接理论上来说也是无状态的,所以也需要一个定式任务来,保证超时停止。</p>    <pre>  <code class="language-java">public boolean connect(Context context, String address) {      if (mConnectionState == STATE_CONNECTED) {          return false;      }      if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) {          return false;      }      initHandler();      if (connectRunnable == null) {          connectRunnable = new Runnable() {              @Override              public void run() {                  mConnectionState = STATE_DISCONNECTING;                  disconnect();              }          };      }    // 30s没反应停止连接      mHandler.postDelayed(connectRunnable, 30 * 1000);      stopScan();  // 获取到远程设备,      final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);      if (device == null) {          return false;      }      // 开始连接,第二个参数表示是否需要自动连接,true设备靠近自动连接,第三个表示连接回调      mBluetoothGatt = device.connectGatt(context, false, mGattCallback);      mBluetoothDeviceAddress = address;      mConnectionState = STATE_CONNECTING;      return true;  }  </code></pre>    <p>监听连接回调</p>    <pre>  <code class="language-java">private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {  // 连接状态变化      @Override      public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {          if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接上              mConnectionState = STATE_CONNECTED;              boolean success = mBluetoothGatt.discoverServices(); // 去发现服务              Log.i(TAG, "Attempting to start service discovery:" +                      success);          } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 连接断开              mConnectionState = STATE_DISCONNECTED;                 }      }    // 发现服务      @Override      public void onServicesDiscovered(BluetoothGatt gatt, int status) {          if (status == BluetoothGatt.GATT_SUCCESS) {              Log.i(TAG,"发现服务");              // 解析服务              discoverService();          }      }    // 特征读取变化      @Override      public void onCharacteristicRead(BluetoothGatt gatt,                                       BluetoothGattCharacteristic characteristic,                                       int status) {          if (status == BluetoothGatt.GATT_SUCCESS) {                      }      }    // 收到数据      @Override      public void onCharacteristicChanged(BluetoothGatt gatt,                                          BluetoothGattCharacteristic characteristic) {          mConnectionState = STATE_CONNECTED;                }  };  </code></pre>    <p>断开连接</p>    <pre>  <code class="language-java">public void disconnect() {      if (mBluetoothAdapter == null || mBluetoothGatt == null) {          mConnectionState = STATE_DISCONNECTED;          return;      }      // 连接成功的GATT      mBluetoothGatt.disconnect();      mBluetoothGatt.close();  }  </code></pre>    <p>多设备连接</p>    <p>蓝牙适配器没有听过连接多个设备的接口,需要我们自己实现,即获取到目标设备的address后调用连接方法,自己维护多个BluetoothGatt即可(代码稍后放出)。</p>    <p>完整代码示例</p>    <p>正在做心率相关的蓝牙设备,此处代码发出来。</p>    <p>代码还在完善中,仅供参考,稳定代码将会发在Github中</p>    <pre>  <code class="language-java">package com.lowett.android.ble;    import android.app.Activity;  import android.bluetooth.BluetoothAdapter;  import android.bluetooth.BluetoothDevice;  import android.bluetooth.BluetoothGatt;  import android.bluetooth.BluetoothGattCallback;  import android.bluetooth.BluetoothGattCharacteristic;  import android.bluetooth.BluetoothGattDescriptor;  import android.bluetooth.BluetoothGattService;  import android.bluetooth.BluetoothManager;  import android.bluetooth.BluetoothProfile;  import android.content.Context;  import android.content.Intent;  import android.content.pm.PackageManager;  import android.os.Handler;  import android.os.Looper;  import android.support.annotation.IntDef;  import android.text.TextUtils;    import com.fit.android.utils.Logger;    import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy;  import java.util.List;  import java.util.Locale;  import java.util.UUID;    /**   * Email: fvaryu@qq.com   */  public class BluetoothLeManager implements BluetoothAdapter.LeScanCallback {      public static final int REQUEST_ENABLE_BT = 1;      private static final int SCAN_PERIOD = 3 * 10 * 1000;        static final int STATE_DISCONNECTED = 1;      public static final int STATE_CONNECTING = 2;      public static final int STATE_DISCONNECTING = 3;      public static final int STATE_CONNECTED = 4;      public static final int STATE_DISCOVER_SERVICES = 5;        @IntDef(value = {STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING, STATE_DISCOVER_SERVICES})      @Retention(RetentionPolicy.SOURCE)      public @interface State {      }        public final static UUID UUID_HEART_RATE_MEASUREMENT =              UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);        private static BluetoothLeManager ourInstance = new BluetoothLeManager();        //    private Context mContext;      private boolean is_inited = false;        private android.bluetooth.BluetoothManager mBluetoothManager;      private BluetoothAdapter mBluetoothAdapter;      private String mBluetoothDeviceAddress;      private int mConnectionState;      private BluetoothGatt mBluetoothGatt;        private boolean mScanning;      private Runnable scanRunnable;      private Handler mHandler;        private Runnable connectRunnable;        private OnDataReceivedListener mOnDataReceivedListener;      // 记得清掉监听 泄漏      private OnLeScanListener mOnLeScanListener;      private OnConnectionStateChangeListener mOnConnectionStateChangeListener;        private int retryCount;        public static BluetoothLeManager getInstance() {          return ourInstance;      }        private BluetoothLeManager() {      }        public void setOnDataReceivedListener(OnDataReceivedListener onDataReceivedListener) {          mOnDataReceivedListener = onDataReceivedListener;      }        public interface OnConnectionStateChangeListener {          void onConnectionStateChange(BluetoothGatt gatt, int status, int newState);            void onConnectTimeout();      }        public void setOnConnectionStateChangeListener(OnConnectionStateChangeListener onConnectionStateChangeListener) {          mOnConnectionStateChangeListener = onConnectionStateChangeListener;      }        public interface OnLeScanListener {          void onLeScan(BluetoothDevice device);      }        public interface OnDataReceivedListener {          void onDataReceived(int heart);      }        private void init() {          if (!is_inited) {              is_inited = true;          }      }        private boolean initialize(Context context) {          init();          if (!is_inited) {              throw new RuntimeException("请先调用init");          }            if (mBluetoothAdapter != null) {              return true;          }          // For API level 18 and above, get a reference to BluetoothAdapter through          // BluetoothLeManager.          if (mBluetoothManager == null) {              mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);              if (mBluetoothManager == null) {                  return false;              }          }            mBluetoothAdapter = mBluetoothManager.getAdapter();          return mBluetoothAdapter != null;      }        public boolean isSupportBluetoothLe(Activity activity) {          return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);      }        public boolean isSupportBluetooth(Context context) {          return initialize(context);      }        public void enableBluetooth(Activity activity) {          if (!initialize(activity)) {              return;          }          if (!mBluetoothAdapter.isEnabled()) {              Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);              activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);          }      }        public boolean isEnabled(Context context) {          return initialize(context) && mBluetoothAdapter.isEnabled();      }        private void initHandler() {          if (mHandler == null) {              mHandler = new Handler(Looper.getMainLooper());          }      }        public void startScan(OnLeScanListener onLeScanListener) {          initHandler();          if (!mScanning) {              if (scanRunnable == null) {                  scanRunnable = new Runnable() {                      @Override                      public void run() {                          stopScan();                      }                  };              }                mHandler.postDelayed(scanRunnable, SCAN_PERIOD);    //            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  //                mBluetoothAdapter.getBluetoothLeScanner().startScan(this);  //            }else {              mBluetoothAdapter.startLeScan(this);  //            }                mScanning = true;                this.mOnLeScanListener = onLeScanListener;                Logger.i("开始扫描,蓝牙设备");          }      }        @Override      public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {          final BluetoothDevice tmp = device;          Logger.i("扫描到的设备, name=" + device.getName() + ",address=" + device.toString());          if (mOnLeScanListener != null) {              mHandler.post(new Runnable() {                  @Override                  public void run() {                      mOnLeScanListener.onLeScan(tmp);                  }              });          }      }        public void stopScan() {          initHandler();          mOnLeScanListener = null;          Logger.i("停止扫描,蓝牙设备");          if (mScanning) {              mScanning = false;              mBluetoothAdapter.stopLeScan(this);          }            if (scanRunnable != null) {              mHandler.removeCallbacks(scanRunnable);              scanRunnable = null;          }      }        private void removeConnectRunnable() {          if (connectRunnable != null) {              mHandler.removeCallbacks(connectRunnable);              connectRunnable = null;          }      }        private void retry() {          if (TextUtils.isEmpty(mBluetoothDeviceAddress)) {              return;          }          mHandler.postDelayed(new Runnable() {              @Override              public void run() {                  if (++retryCount < 11 && mConnectionState < STATE_CONNECTED) {                      reconnect(retryCount);                      mHandler.postDelayed(this, retryCount * 5 * 1000);                      Logger.i("蓝牙重试次数=" + retryCount);                  }                }          }, 2000);      }        private void reconnect(int count) {          if ((mConnectionState >= STATE_CONNECTING)) {              return;          }            if (connectRunnable == null) {              connectRunnable = new Runnable() {                  @Override                  public void run() {                      mConnectionState = STATE_DISCONNECTING;                      disconnect();                  }              };          }          mHandler.postDelayed(connectRunnable, count * 3 * 1000);              if (mBluetoothDeviceAddress != null                  && mBluetoothGatt != null) {              mBluetoothGatt.connect();              mConnectionState = STATE_CONNECTING;          }      }        public boolean connect(Context context, String address) {          if (mConnectionState == STATE_CONNECTED) {              return false;          }          if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) {              return false;          }          initHandler();          if (connectRunnable == null) {              connectRunnable = new Runnable() {                  @Override                  public void run() {                      mConnectionState = STATE_DISCONNECTING;                      disconnect();                        if (mOnConnectionStateChangeListener != null) {                          mOnConnectionStateChangeListener.onConnectTimeout();                      }                  }              };          }          mHandler.postDelayed(connectRunnable, 30 * 1000);            stopScan();            // Previously connected device.  Try to reconnect.          if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)                  && mBluetoothGatt != null) {              if (mBluetoothGatt.connect()) {                  mConnectionState = STATE_CONNECTING;                  return true;              } else {                  return false;              }          }            final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);          if (device == null) {              return false;          }          // We want to directly connect to the device, so we are setting the autoConnect          // parameter to false.          mBluetoothGatt = device.connectGatt(context, false, mGattCallback);          mBluetoothDeviceAddress = address;          mConnectionState = STATE_CONNECTING;          return true;      }        public void disconnect() {          if (mBluetoothAdapter == null || mBluetoothGatt == null) {              Logger.i("BluetoothAdapter not initialized");              mConnectionState = STATE_DISCONNECTED;              return;          }            mBluetoothGatt.disconnect();        }        public void close() {          disconnect();          if (mBluetoothGatt == null) {              return;          }          mBluetoothGatt.close();          mBluetoothGatt = null;      }        public void disconnectNoRetry() {          mBluetoothDeviceAddress = null;          close();      }        private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {            @Override          public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {              if (newState == BluetoothProfile.STATE_CONNECTED) {                  mConnectionState = STATE_CONNECTED;                  boolean success = mBluetoothGatt.discoverServices();                  Logger.i("Attempting to start service discovery:" +                          success);                  removeConnectRunnable();                    Logger.i("链接上");              } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {                  mConnectionState = STATE_DISCONNECTED;                  Logger.i("断开链接");                    retry();              }                if (mOnConnectionStateChangeListener != null) {                  mOnConnectionStateChangeListener.onConnectionStateChange(gatt, status, newState);              }          }            @Override          public void onServicesDiscovered(BluetoothGatt gatt, int status) {              if (status == BluetoothGatt.GATT_SUCCESS) {                  Logger.i("发现服务");                  discoverService();              }            }            @Override          public void onCharacteristicRead(BluetoothGatt gatt,                                           BluetoothGattCharacteristic characteristic,                                           int status) {              if (status == BluetoothGatt.GATT_SUCCESS) {                  broadcastUpdate(characteristic);              }          }            @Override          public void onCharacteristicChanged(BluetoothGatt gatt,                                              BluetoothGattCharacteristic characteristic) {              mConnectionState = STATE_CONNECTED;              broadcastUpdate(characteristic);          }      };        /**       * Retrieves a list of supported GATT services on the connected device. This should be       * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.       *       * @return A {@code List} of supported services.       */      private List<BluetoothGattService> getSupportedGattServices() {          if (mBluetoothGatt == null) return null;          return mBluetoothGatt.getServices();      }        private void discoverService() {          if (mConnectionState == STATE_DISCOVER_SERVICES) {              return;          }          mConnectionState = STATE_DISCOVER_SERVICES;          List<BluetoothGattService> list = getSupportedGattServices();          /**           *  BluetoothGattService = 00001800-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a00-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a01-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a04-0000-1000-8000-00805f9b34fb             BluetoothGattService = 00001801-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a05-0000-1000-8000-00805f9b34fb             心跳服务           BluetoothGattService = 0000180d-0000-1000-8000-00805f9b34fb           心跳特征           BluetoothGattCharacteristic = 00002a37-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a38-0000-1000-8000-00805f9b34fb             BluetoothGattService = 0000180f-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a19-0000-1000-8000-00805f9b34fb             // 设备名字           BluetoothGattService = 0000180a-0000-1000-8000-00805f9b34fb           BluetoothGattCharacteristic = 00002a28-0000-1000-8000-00805f9b34fb           */          for (BluetoothGattService s : list) {              if (!SampleGattAttributes.HEART_RATE_SERVICES.equals(s.getUuid().toString())) {                  continue;              }              final List<BluetoothGattCharacteristic> l = s.getCharacteristics();              for (final BluetoothGattCharacteristic bc : l) {                  if (!SampleGattAttributes.HEART_RATE_MEASUREMENT.equals(bc.getUuid().toString())) {                      continue;                  }                  Logger.i("连接蓝牙 服务成功");                  setCharacteristicNotification(bc, true);                  return;              }          }      }        private void broadcastUpdate(final BluetoothGattCharacteristic characteristic) {              // This is special handling for the Heart Rate Measurement profile.  Data parsing is          // carried out as per profile specifications:          // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml          if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {              int flag = characteristic.getProperties();              int format = -1;              if ((flag & 0x01) != 0) {                  format = BluetoothGattCharacteristic.FORMAT_UINT16;              } else {                  format = BluetoothGattCharacteristic.FORMAT_UINT8;              }              int heartRate = characteristic.getIntValue(format, 1);              Logger.i(String.format(Locale.getDefault(), "Received heart rate: %d", heartRate));              if (mOnDataReceivedListener != null) {                  mOnDataReceivedListener.onDataReceived(heartRate);              }          }  //        else {          // For all other profiles, writes the data formatted in HEX.  //            final byte[] data = characteristic.getValue();  //            if (data != null && data.length > 0) {  //                final StringBuilder stringBuilder = new StringBuilder(data.length);  //                for (byte byteChar : data)  //                    stringBuilder.append(String.format("%02X ", byteChar));  //                intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());  //            }  //        }          /**           * 2、           */  //        sendBroadcast(intent);      }        public boolean isConnected() {          return mConnectionState == STATE_CONNECTED;      }        public String getConnectedAddress() {          if (!isConnected()) {              return null;          }          return mBluetoothDeviceAddress;      }        public void readCharacteristic(BluetoothGattCharacteristic characteristic) {          if (mBluetoothAdapter == null || mBluetoothGatt == null) {              Logger.i("BluetoothAdapter not initialized");              return;          }          mBluetoothGatt.readCharacteristic(characteristic);        }        private void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,                                                 boolean enabled) {          if (mBluetoothAdapter == null || mBluetoothGatt == null) {              Logger.i("BluetoothAdapter not initialized");              return;          }          mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);            // This is specific to Heart Rate Measurement.          if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {              BluetoothGattDescriptor descriptor = characteristic.getDescriptor(                      UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));              descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);              mBluetoothGatt.writeDescriptor(descriptor);          }      }        public void clear() {          mOnLeScanListener = null;          mOnConnectionStateChangeListener = null;      }        public void release() {  //        connectRunnable = null;  //        mHandler = null;  //         ourInstance = null;      }  }  </code></pre>    <p> </p>    <p>来自:http://lowett.com/2017/03/23/android-bluetooth/</p>    <p> </p>