1. 在做Android BLE的应用程序时,我们发出广播数据是调用BluetoothLeAdvertiserstartAdvertising方法,如下所示:
  2.  
  3. mBluetoothLeAdvertiser.startAdvertising(advertiseSettings,
  4. advertiseData, myAdvertiseCallback);
  5.  
  6. 那么我打算写的BLE总结之源码篇就以此为线索来分析Android BLE FrameWork方面的东西。
  7.  
  8. public void startAdvertising(AdvertiseSettings settings,
  9. AdvertiseData advertiseData, final AdvertiseCallback callback) {
  10. startAdvertising(settings, advertiseData, null, callback);
  11. }
  12. public void startAdvertising(AdvertiseSettings settings,
  13. AdvertiseData advertiseData, AdvertiseData scanResponse,
  14. final AdvertiseCallback callback) {
  15. synchronized (mLeAdvertisers) {
  16.  
  17. //该check只是检查mBluetoothAdater是否为null和其状态是否为State_ON
  18.  
  19. BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
  20. if (callback == null) {
  21. throw new IllegalArgumentException("callback cannot be null");
  22. }
  23.  
  24. if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&
  25. !mBluetoothAdapter.isPeripheralModeSupported()) {//是否支持广播和作为外围设备
  26. postStartFailure(callback,
  27. AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
  28. return;
  29. }
  30. boolean isConnectable = settings.isConnectable();
  31. if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||
  32. totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {
  33. postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
  34. return;
  35. }
  36. if (mLeAdvertisers.containsKey(callback)) {
  37. postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
  38. return;
  39. }
  40. IBluetoothGatt gatt;
  41. try {
  42. gatt = mBluetoothManager.getBluetoothGatt();
  43. } catch (RemoteException e) {
  44. Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
  45. postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
  46. return;
  47. }
  48. AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
  49. scanResponse, settings, gatt);
  50. wrapper.startRegisteration();
  51. }
  52. }
  53.  
  54. 大家可以看到在startAdvertising内部,首先经过了一系列的判断,然后包装了一个叫作AdvertiseCallbackWrapper的类来做发广播数据的行为。
  55.  
  56. 我们先看一下startAdvertising内部都是做了哪些判断:
  57. 1.判断蓝牙是否已经打开,否则抛出异常。
  58.  
  59. 2.判断回调callback是否为空
  60.  
  61. 3.判断当前设备是否支持广播数据和作为外围设备
  62.  
  63. 4.判断广播数据包的长度是否超过了31字节
  64.  
  65. 5.判断广播是否已经开始
  66.  
  67. 经过了这5步初步的判断,下面来到了最重要的地方,mBluetoothManager.getBluetoothGatt();获取一个引用,最终的发送广播和停止广播都是通过这个引用来进行实现的。这里不进行展开,因为本文主要是对BluetoothLeAdvertiser的解读。
  68.  
  69. 下面我们就来看看刚才提到的AdvertiseCallbackWrapper,代码如下:
  70.  
  71. /**
  72. * Bluetooth GATT interface callbacks for advertising.
  73. */
  74. private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {
  75. private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
  76. private final AdvertiseCallback mAdvertiseCallback;
  77. private final AdvertiseData mAdvertisement;
  78. private final AdvertiseData mScanResponse;
  79. private final AdvertiseSettings mSettings;
  80. private final IBluetoothGatt mBluetoothGatt;
  81. // mClientIf 0: not registered
  82. // -1: advertise stopped or registration timeout
  83. // >0: registered and advertising started
  84. private int mClientIf;
  85. private boolean mIsAdvertising = false;
  86. public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
  87. AdvertiseData advertiseData, AdvertiseData scanResponse,
  88. AdvertiseSettings settings,
  89. IBluetoothGatt bluetoothGatt) {
  90. mAdvertiseCallback = advertiseCallback;
  91. mAdvertisement = advertiseData;
  92. mScanResponse = scanResponse;
  93. mSettings = settings;
  94. mBluetoothGatt = bluetoothGatt;
  95. mClientIf = 0;
  96. }
  97. public void startRegisteration() {
  98. synchronized (this) {
  99. if (mClientIf == -1) return;//这个就不解释了
  100. try {
  101. UUID uuid = UUID.randomUUID();
  102. mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);//注册
  103. wait(LE_CALLBACK_TIMEOUT_MILLIS);//等待2秒,在过程中会依次回调onClientRegistered和onMultiAdvertiseCallback
  104. } catch (InterruptedException | RemoteException e) {
  105. Log.e(TAG, "Failed to start registeration", e);
  106. }
  107.  
  108. //注册成功并且广播成功,加入广播缓存,以callback为key的Hashmap,callback为用户自己定义的Callback
  109.  
  110. if (mClientIf > 0 && mIsAdvertising) {
  111. mLeAdvertisers.put(mAdvertiseCallback, this);
  112. } else if (mClientIf <= 0) {//注册失败
  113. // Registration timeout, reset mClientIf to -1 so no subsequent operations can
  114. // proceed.
  115. if (mClientIf == 0) mClientIf = -1;
  116. // Post internal error if registration failed.
  117. postStartFailure(mAdvertiseCallback,
  118. AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
  119. } else {//注册成功但广播开启失败
  120. // Unregister application if it's already registered but advertise failed.
  121. try {
  122. mBluetoothGatt.unregisterClient(mClientIf);
  123. mClientIf = -1;
  124. } catch (RemoteException e) {
  125. Log.e(TAG, "remote exception when unregistering", e);
  126. }
  127. }
  128. }
  129. }
  130. public void stopAdvertising() {
  131. synchronized (this) {
  132. try {
  133. mBluetoothGatt.stopMultiAdvertising(mClientIf);
  134. wait(LE_CALLBACK_TIMEOUT_MILLIS);
  135. } catch (InterruptedException | RemoteException e) {
  136. Log.e(TAG, "Failed to stop advertising", e);
  137. }
  138. // Advertise callback should have been removed from LeAdvertisers when
  139. // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
  140. // invoked and wait timeout expires, remove callback here.
  141. if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
  142. mLeAdvertisers.remove(mAdvertiseCallback);
  143. }
  144. }
  145. }
  146. /**
  147. * Application interface registered - app is ready to go
  148. */
  149. @Override
  150. public void onClientRegistered(int status, int clientIf) {
  151. Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
  152. synchronized (this) {
  153. if (status == BluetoothGatt.GATT_SUCCESS) {
  154. try {
  155. if (mClientIf == -1) {//在2秒内未完成注册,超时
  156. // Registration succeeds after timeout, unregister client.
  157. mBluetoothGatt.unregisterClient(clientIf);
  158. } else {//完成注册,并开始广播
  159. mClientIf = clientIf;
  160. mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,
  161. mScanResponse, mSettings);
  162. }
  163. return;
  164. } catch (RemoteException e) {
  165. Log.e(TAG, "failed to start advertising", e);
  166. }
  167. }
  168. // Registration failed.
  169. mClientIf = -1;
  170. notifyAll();
  171. }
  172. }
  173. @Override
  174. public void onMultiAdvertiseCallback(int status, boolean isStart,
  175. AdvertiseSettings settings) {
  176. synchronized (this) {
  177. if (isStart) {//广播成功时的回调
  178. if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
  179. // Start success
  180. mIsAdvertising = true;
  181. postStartSuccess(mAdvertiseCallback, settings);
  182. } else {
  183. // Start failure.
  184. postStartFailure(mAdvertiseCallback, status);
  185. }
  186. } else {//stop 时的回调,用来反注册和清除缓存的callback
  187. // unregister client for stop.
  188. try {
  189. mBluetoothGatt.unregisterClient(mClientIf);
  190. mClientIf = -1;
  191. mIsAdvertising = false;
  192. mLeAdvertisers.remove(mAdvertiseCallback);
  193. } catch (RemoteException e) {
  194. Log.e(TAG, "remote exception when unregistering", e);
  195. }
  196. }
  197. notifyAll();
  198. }
  199. }
  200. }
  201. private void postStartFailure(final AdvertiseCallback callback, final int error) {
  202. mHandler.post(new Runnable() {
  203. @Override
  204. public void run() {
  205. callback.onStartFailure(error);
  206. }
  207. });
  208. }
  209. private void postStartSuccess(final AdvertiseCallback callback,
  210. final AdvertiseSettings settings) {
  211. mHandler.post(new Runnable() {
  212. @Override
  213. public void run() {
  214. callback.onStartSuccess(settings);
  215. }
  216. });
  217. }
  218.  
  219. AdvertiseCallbackWrapper的成员变量mClientIf非常重要,在广播发送和停止的过程中起着重要的作用。这里先简单的记住该属性的以下特征:
  220.  
  221. mClientIf=0——>未注册
  222.  
  223. mClinetIf=-1——>广播停止或注册超时
  224.  
  225. mClientIf>0——>已注册并且已经广播成功
  226.  
  227. mClientIf默认值为0
  228.  
  229. 这时我们追踪到startRegisteration这个方法了,该方法里面调用了registerClient方法,经过IPC通信后会回调到onClientRegistered方法,继续调用到了startMultiAdvertising方法,接着触发onMultiAdvertiseCallback,成功发送广播后,将该AdvertiseCallbackWrapper对象加入mLeAdvertisers
  230.  
  231. 这里我们需要注意和了解以下几点:
  232.  
  233. 1.在调用startRegisteration2秒的时间内,如果没有注册成功且广播成功,这次广播数据的行为均为失败。
  234.  
  235. 2.即使2秒之后onClientRegistered回调,也将视为注册未成功,并进行解注册操作。
  236.  
  237. startAdvertising方法就到这,至于更底层的细节后续的文章会展开,下面我们看一下其对应的stopAdvertising方法
  238.  
  239. /**
  240. * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
  241. * {@link BluetoothLeAdvertiser#startAdvertising}.
  242. * <p>
  243. * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
  244. *
  245. * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
  246. */
  247. public void stopAdvertising(final AdvertiseCallback callback) {
  248. synchronized (mLeAdvertisers) {
  249. if (callback == null) {
  250. throw new IllegalArgumentException("callback cannot be null");
  251. }
  252. AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
  253. if (wrapper == null) return;
  254. wrapper.stopAdvertising();
  255. }
  256. }
  257.  
  258. 大家可以看到,stopAdvertising方法内部是调用AdvertiseCallbackWrapper.stopAdvertising方法。这里必须注意stopAdvertising方法的callback必须和start时传入的callback参数是同一个。否则在mLeAdvertisers缓存里是找不到相应的AdvertiseCallbackWrapper的实例的,就无法正常停止广播。
  259. 转载请注明:http://blog.csdn.net/android_jiangjun/article/details/77946857

Android BLE 总结-源码篇(BluetoothLeAdvertiser)的更多相关文章

  1. Ubantu16.04进行Android 8.0源码编译

    参考这篇博客 经过测试,8.0源码下载及编译之后,占用100多G的硬盘空间,尽量给ubantu系统多留一些硬盘空间,如果后续需要在编译好的源码上进行开发,需要预留更多的控件,为了防止后续出现文件权限问 ...

  2. 第一部分:开发前的准备-第八章 Android SDK与源码下载

    第8章 Android SDK与源码下载 如果你是新下载的SDK,请阅读一下步骤了解如何设置SDK.如果你已经下载使用过SDK,那么你应该使用AVD Manager,来更新即可. 下面是构建Andro ...

  3. [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程

    本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...

  4. 从谷歌官网下载android 6.0源码、编译并刷入nexus 6p手机

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/fuchaosz/article/details/52473660 1 前言 经过一周的奋战,终于从谷 ...

  5. 源码篇:ThreadLocal的奇思妙想(万字图文)

    前言 ThreadLocal的文章在网上也有不少,但是看了一些后,理解起来总感觉有绕,而且看了ThreadLocal的源码,无论是线程隔离.类环形数组.弱引用结构等等,实在是太有意思了!我必须也要让大 ...

  6. 源码篇:Flutter Provider的另一面(万字图文+插件)

    前言 阅读此文的彦祖,亦菲们,附送一枚Provider模板代码生成插件! 我为啥要写这个插件呢? 此事说来话短,我这不准备写解析Provider源码的文章,肯定要写这框架的使用样例啊,然后再哔哔源码呀 ...

  7. 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ...

  8. 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...

  9. 深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)>我们通过对mybatis源码的简单分析,可看出,在mybatis配置文件中,在configuration根 ...

随机推荐

  1. servlet跳转页面后图片不显示

    我是用图片的相对路径,原先直接打开jsp的话图片是可以正常显示的,通过servlet跳转之后图片就显示不出来了 后来发现是图片路径的问题, 我是将图片放在WebRoot里面自己创建的img中,原先图片 ...

  2. DNA的复制

    半保留复制 DNA分子复制时, DNA分子的双螺旋将解开, 互补的碱基之间的氢键断裂, 解开的两条单链作为复制的模板, 游离的脱氧核苷酸依据碱基互补配对的原则, 通过形成氢键结合到作为模板的单链上. ...

  3. javascript 对象初探 (四)--- 内建对象之旅之Boolean

    var a = new Boolean() 我们要明白一点在这里的b是一个对象而不是一个基本数据类型的布尔值.如果想将b转化成基本数据类型的布尔值,我们可以调用她的valueof()方法(继承自Obj ...

  4. GCD CoreData 简化CoreData操作(转)

    来自会员带睡帽的青蛙的分享: 短话长说,开始写这个小工具到现在有两个月了,虽然东西少,但是很精练,改了又改,期间有不少问题 在坛子里获得了不少帮助 谢谢各位大大. 就是两个文件一个类 CoreData ...

  5. Eclipse 修改字符集

    Eclipse 修改字符集 默认情况下 Eclipse 字符集为 GBK,但现在很多项目采用的是 UTF-8,这是我们就需要设置我们的 Eclipse 开发环境字符集为 UTF-8, 设置步骤如下: ...

  6. mycat 连续分片 -&gt; 按日期(天)分片

    1,按日期(天)分片 按日期(天)分片:从開始日期算起,依照天数来分片 比如,从2016-01-01.每10天一个分片 注意事项:须要提前将分片规划好,建好.否则有可能日期超出实际配置分片数 2,加入 ...

  7. Cocos2d-x移植安卓的笔记

    一.下载所需软件 Java SDK   http://www.oracle.com/technetwork/java/javase/downloads/index.html  Windows x64 ...

  8. Java面向对象基础三

    1.函数的重载 2.构造函数的作用 (构造函数能够重载) 1.函数名必须和类名同样 2.没有返回值 3.使用 New 来调用构造函数 4.假设类中没有构造函数,编译器会自己主动帮忙载入一个參数为空.方 ...

  9. 轻松搞定RabbitMQ(四)——发布/订阅

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48729479 翻译地址:http://www.rabbitmq.com/tutorials ...

  10. xadmin入门使用

    ,官方文档:http://xadmin.readthedocs.io/en/docs-chinese/views_api.html 中文文档:https://www.kancloud.cn/net_y ...