Android BLE 总结-源码篇(BluetoothLeAdvertiser)

Stella981
• 阅读 900

在做Android BLE的应用程序时,我们发出广播数据是调用BluetoothLeAdvertiser的startAdvertising方法,如下所示:

[java] view plain copy

  1. mBluetoothLeAdvertiser.startAdvertising(advertiseSettings,  
  2.                 advertiseData, myAdvertiseCallback);

  

那么我打算写的BLE总结之源码篇就以此为线索来分析Android BLE FrameWork方面的东西。

[java] view plain copy

  1.  public void startAdvertising(AdvertiseSettings settings,

  2.             AdvertiseData advertiseData, final AdvertiseCallback callback) {

  3.         startAdvertising(settings, advertiseData, null, callback);

  4.     }  

  5. public void startAdvertising(AdvertiseSettings settings,

  6.             AdvertiseData advertiseData, AdvertiseData scanResponse,  

  7.             final AdvertiseCallback callback) {

  8.         synchronized (mLeAdvertisers) {

  9. //该check只是检查mBluetoothAdater是否为null和其状态是否为State_ON

  10.             BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);  

  11.             if (callback == null) {

  12.                 throw new IllegalArgumentException("callback cannot be null");

  13.             }  

  14.             if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&

  15.                     !mBluetoothAdapter.isPeripheralModeSupported()) {//是否支持广播和作为外围设备

  16.                 postStartFailure(callback,  

  17.                         AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);  

  18.                 return;

  19.             }  

  20.             boolean isConnectable = settings.isConnectable();

  21.             if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||

  22.                     totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {

  23.                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);  

  24.                 return;

  25.             }  

  26.             if (mLeAdvertisers.containsKey(callback)) {

  27.                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);  

  28.                 return;

  29.             }  

  30.             IBluetoothGatt gatt;  

  31.             try {

  32.                 gatt = mBluetoothManager.getBluetoothGatt();  

  33.             } catch (RemoteException e) {

  34.                 Log.e(TAG, "Failed to get Bluetooth gatt - ", e);

  35.                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);  

  36.                 return;

  37.             }  

  38.             AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,

  39.                     scanResponse, settings, gatt);  

  40.             wrapper.startRegisteration();  

  41.         }  

  42.     }

  

大家可以看到在startAdvertising内部,首先经过了一系列的判断,然后包装了一个叫作AdvertiseCallbackWrapper的类来做发广播数据的行为。

我们先看一下startAdvertising内部都是做了哪些判断:
1.判断蓝牙是否已经打开,否则抛出异常。

2.判断回调callback是否为空

3.判断当前设备是否支持广播数据和作为外围设备

4.判断广播数据包的长度是否超过了31字节

5.判断广播是否已经开始

经过了这5步初步的判断,下面来到了最重要的地方,mBluetoothManager.getBluetoothGatt();获取一个引用,最终的发送广播和停止广播都是通过这个引用来进行实现的。这里不进行展开,因为本文主要是对BluetoothLeAdvertiser的解读。

下面我们就来看看刚才提到的AdvertiseCallbackWrapper,代码如下:

[java] view plain copy

  1. /**

  2. * Bluetooth GATT interface callbacks for advertising.

  3. */

  4.     private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {

  5.         private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;

  6.         private final AdvertiseCallback mAdvertiseCallback;

  7.         private final AdvertiseData mAdvertisement;

  8.         private final AdvertiseData mScanResponse;

  9.         private final AdvertiseSettings mSettings;

  10.         private final IBluetoothGatt mBluetoothGatt;

  11.         // mClientIf 0: not registered

  12.         // -1: advertise stopped or registration timeout

  13.         // >0: registered and advertising started

  14.         private int mClientIf;

  15.         private boolean mIsAdvertising = false;

  16.         public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,

  17.                 AdvertiseData advertiseData, AdvertiseData scanResponse,  

  18.                 AdvertiseSettings settings,  

  19.                 IBluetoothGatt bluetoothGatt) {  

  20.             mAdvertiseCallback = advertiseCallback;  

  21.             mAdvertisement = advertiseData;  

  22.             mScanResponse = scanResponse;  

  23.             mSettings = settings;  

  24.             mBluetoothGatt = bluetoothGatt;  

  25.             mClientIf = 0;

  26.         }  

  27.         public void startRegisteration() {

  28.             synchronized (this) {

  29.                 if (mClientIf == -1) return;//这个就不解释了

  30.                 try {

  31.                     UUID uuid = UUID.randomUUID();  

  32.                     mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);//注册

  33.                     wait(LE_CALLBACK_TIMEOUT_MILLIS);//等待2秒,在过程中会依次回调onClientRegistered和onMultiAdvertiseCallback

  34.                 } catch (InterruptedException | RemoteException e) {

  35.                     Log.e(TAG, "Failed to start registeration", e);

  36.                 }  

  37. //注册成功并且广播成功,加入广播缓存,以callback为key的Hashmap,callback为用户自己定义的Callback

  38.                 if (mClientIf > 0 && mIsAdvertising) {

  39.                     mLeAdvertisers.put(mAdvertiseCallback, this);

  40.                 } else if (mClientIf <= 0) {//注册失败

  41.                     // Registration timeout, reset mClientIf to -1 so no subsequent operations can

  42.                     // proceed.

  43.                     if (mClientIf == 0) mClientIf = -1;

  44.                     // Post internal error if registration failed.

  45.                     postStartFailure(mAdvertiseCallback,  

  46.                             AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);  

  47.                 } else {//注册成功但广播开启失败

  48.                     // Unregister application if it's already registered but advertise failed.

  49.                     try {

  50.                         mBluetoothGatt.unregisterClient(mClientIf);  

  51.                         mClientIf = -1;

  52.                     } catch (RemoteException e) {

  53.                         Log.e(TAG, "remote exception when unregistering", e);

  54.                     }  

  55.                 }  

  56.             }  

  57.         }  

  58.         public void stopAdvertising() {

  59.             synchronized (this) {

  60.                 try {

  61.                     mBluetoothGatt.stopMultiAdvertising(mClientIf);  

  62.                     wait(LE_CALLBACK_TIMEOUT_MILLIS);  

  63.                 } catch (InterruptedException | RemoteException e) {

  64.                     Log.e(TAG, "Failed to stop advertising", e);

  65.                 }  

  66.                 // Advertise callback should have been removed from LeAdvertisers when

  67.                 // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never

  68.                 // invoked and wait timeout expires, remove callback here.

  69.                 if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {

  70.                     mLeAdvertisers.remove(mAdvertiseCallback);  

  71.                 }  

  72.             }  

  73.         }  

  74.         /**

  75. * Application interface registered - app is ready to go

  76. */

  77.         @Override

  78.         public void onClientRegistered(int status, int clientIf) {

  79.             Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);

  80.             synchronized (this) {

  81.                 if (status == BluetoothGatt.GATT_SUCCESS) {

  82.                     try {

  83.                         if (mClientIf == -1) {//在2秒内未完成注册,超时

  84.                             // Registration succeeds after timeout, unregister client.

  85.                             mBluetoothGatt.unregisterClient(clientIf);  

  86.                         } else {//完成注册,并开始广播

  87.                             mClientIf = clientIf;  

  88.                             mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,  

  89.                                     mScanResponse, mSettings);  

  90.                         }  

  91.                         return;

  92.                     } catch (RemoteException e) {

  93.                         Log.e(TAG, "failed to start advertising", e);

  94.                     }  

  95.                 }  

  96.                 // Registration failed.

  97.                 mClientIf = -1;

  98.                 notifyAll();  

  99.             }  

  100.         }  

  101.         @Override

  102.         public void onMultiAdvertiseCallback(int status, boolean isStart,

  103.                 AdvertiseSettings settings) {  

  104.             synchronized (this) {

  105.                 if (isStart) {//广播成功时的回调

  106.                     if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {

  107.                         // Start success

  108.                         mIsAdvertising = true;

  109.                         postStartSuccess(mAdvertiseCallback, settings);  

  110.                     } else {

  111.                         // Start failure.

  112.                         postStartFailure(mAdvertiseCallback, status);  

  113.                     }  

  114.                 } else {//stop 时的回调,用来反注册和清除缓存的callback

  115.                     // unregister client for stop.

  116.                     try {

  117.                         mBluetoothGatt.unregisterClient(mClientIf);  

  118.                         mClientIf = -1;

  119.                         mIsAdvertising = false;

  120.                         mLeAdvertisers.remove(mAdvertiseCallback);  

  121.                     } catch (RemoteException e) {

  122.                         Log.e(TAG, "remote exception when unregistering", e);

  123.                     }  

  124.                 }  

  125.                 notifyAll();  

  126.             }  

  127.         }  

  128.     }  

  129.     private void postStartFailure(final AdvertiseCallback callback, final int error) {

  130.         mHandler.post(new Runnable() {

  131.             @Override

  132.             public void run() {

  133.                 callback.onStartFailure(error);  

  134.             }  

  135.         });  

  136.     }  

  137.     private void postStartSuccess(final AdvertiseCallback callback,

  138.             final AdvertiseSettings settings) {

  139.         mHandler.post(new Runnable() {

  140.             @Override

  141.             public void run() {

  142.                 callback.onStartSuccess(settings);  

  143.             }  

  144.         });  

  145.     }

  

AdvertiseCallbackWrapper的成员变量mClientIf非常重要,在广播发送和停止的过程中起着重要的作用。这里先简单的记住该属性的以下特征:

mClientIf=0——>未注册

mClinetIf=-1——>广播停止或注册超时

mClientIf>0——>已注册并且已经广播成功

mClientIf默认值为0

这时我们追踪到startRegisteration这个方法了,该方法里面调用了registerClient方法,经过IPC通信后会回调到onClientRegistered方法,继续调用到了startMultiAdvertising方法,接着触发onMultiAdvertiseCallback,成功发送广播后,将该AdvertiseCallbackWrapper对象加入mLeAdvertisers。

这里我们需要注意和了解以下几点:

1.在调用startRegisteration的2秒的时间内,如果没有注册成功且广播成功,这次广播数据的行为均为失败。

2.即使2秒之后onClientRegistered回调,也将视为注册未成功,并进行解注册操作。

startAdvertising方法就到这,至于更底层的细节后续的文章会展开,下面我们看一下其对应的stopAdvertising方法

[java] view plain copy

  1. /**
  2. * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
  3. * {@link BluetoothLeAdvertiser#startAdvertising}.
  4. * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
  5. *
  6. * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
  7. */
  8.   public void stopAdvertising(final AdvertiseCallback callback) {
  9.       synchronized (mLeAdvertisers) {
  10.           if (callback == null) {
  11.               throw new IllegalArgumentException("callback cannot be null");
  12.           }  
  13.           AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);  
  14.           if (wrapper == null) return;
  15.           wrapper.stopAdvertising();  
  16.       }  
  17.   }

  

大家可以看到,stopAdvertising方法内部是调用AdvertiseCallbackWrapper.stopAdvertising方法。这里必须注意stopAdvertising方法的callback必须和start时传入的callback参数是同一个。否则在mLeAdvertisers缓存里是找不到相应的AdvertiseCallbackWrapper的实例的,就无法正常停止广播。

转载请注明:http://blog.csdn.net/android_jiangjun/article/details/77946857

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
PhoneGap设置Icon
参考:http://cordova.apache.org/docs/en/latest/config\_ref/images.html通过config.xml中的<icon标签来设置Icon<iconsrc"res/ios/icon.png"platform"ios"width"57"height"57"densi
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这