Android BLE 总结-源码篇(BluetoothLeAdvertiser)
在做Android BLE的应用程序时,我们发出广播数据是调用BluetoothLeAdvertiser的startAdvertising方法,如下所示: mBluetoothLeAdvertiser.startAdvertising(advertiseSettings,
advertiseData, myAdvertiseCallback); 那么我打算写的BLE总结之源码篇就以此为线索来分析Android BLE FrameWork方面的东西。 public void startAdvertising(AdvertiseSettings settings,
AdvertiseData advertiseData, final AdvertiseCallback callback) {
startAdvertising(settings, advertiseData, null, callback);
}
public void startAdvertising(AdvertiseSettings settings,
AdvertiseData advertiseData, AdvertiseData scanResponse,
final AdvertiseCallback callback) {
synchronized (mLeAdvertisers) { //该check只是检查mBluetoothAdater是否为null和其状态是否为State_ON BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
} if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&
!mBluetoothAdapter.isPeripheralModeSupported()) {//是否支持广播和作为外围设备
postStartFailure(callback,
AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
return;
}
boolean isConnectable = settings.isConnectable();
if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||
totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
return;
}
if (mLeAdvertisers.containsKey(callback)) {
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
return;
}
IBluetoothGatt gatt;
try {
gatt = mBluetoothManager.getBluetoothGatt();
} catch (RemoteException e) {
Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
return;
}
AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
scanResponse, settings, gatt);
wrapper.startRegisteration();
}
} 大家可以看到在startAdvertising内部,首先经过了一系列的判断,然后包装了一个叫作AdvertiseCallbackWrapper的类来做发广播数据的行为。 我们先看一下startAdvertising内部都是做了哪些判断:
1.判断蓝牙是否已经打开,否则抛出异常。 2.判断回调callback是否为空 3.判断当前设备是否支持广播数据和作为外围设备 4.判断广播数据包的长度是否超过了31字节 5.判断广播是否已经开始 经过了这5步初步的判断,下面来到了最重要的地方,mBluetoothManager.getBluetoothGatt();获取一个引用,最终的发送广播和停止广播都是通过这个引用来进行实现的。这里不进行展开,因为本文主要是对BluetoothLeAdvertiser的解读。 下面我们就来看看刚才提到的AdvertiseCallbackWrapper,代码如下: /**
* Bluetooth GATT interface callbacks for advertising.
*/
private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {
private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
private final AdvertiseCallback mAdvertiseCallback;
private final AdvertiseData mAdvertisement;
private final AdvertiseData mScanResponse;
private final AdvertiseSettings mSettings;
private final IBluetoothGatt mBluetoothGatt;
// mClientIf 0: not registered
// -1: advertise stopped or registration timeout
// >0: registered and advertising started
private int mClientIf;
private boolean mIsAdvertising = false;
public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
AdvertiseData advertiseData, AdvertiseData scanResponse,
AdvertiseSettings settings,
IBluetoothGatt bluetoothGatt) {
mAdvertiseCallback = advertiseCallback;
mAdvertisement = advertiseData;
mScanResponse = scanResponse;
mSettings = settings;
mBluetoothGatt = bluetoothGatt;
mClientIf = 0;
}
public void startRegisteration() {
synchronized (this) {
if (mClientIf == -1) return;//这个就不解释了
try {
UUID uuid = UUID.randomUUID();
mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);//注册
wait(LE_CALLBACK_TIMEOUT_MILLIS);//等待2秒,在过程中会依次回调onClientRegistered和onMultiAdvertiseCallback
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Failed to start registeration", e);
} //注册成功并且广播成功,加入广播缓存,以callback为key的Hashmap,callback为用户自己定义的Callback if (mClientIf > 0 && mIsAdvertising) {
mLeAdvertisers.put(mAdvertiseCallback, this);
} else if (mClientIf <= 0) {//注册失败
// Registration timeout, reset mClientIf to -1 so no subsequent operations can
// proceed.
if (mClientIf == 0) mClientIf = -1;
// Post internal error if registration failed.
postStartFailure(mAdvertiseCallback,
AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
} else {//注册成功但广播开启失败
// Unregister application if it's already registered but advertise failed.
try {
mBluetoothGatt.unregisterClient(mClientIf);
mClientIf = -1;
} catch (RemoteException e) {
Log.e(TAG, "remote exception when unregistering", e);
}
}
}
}
public void stopAdvertising() {
synchronized (this) {
try {
mBluetoothGatt.stopMultiAdvertising(mClientIf);
wait(LE_CALLBACK_TIMEOUT_MILLIS);
} catch (InterruptedException | RemoteException e) {
Log.e(TAG, "Failed to stop advertising", e);
}
// Advertise callback should have been removed from LeAdvertisers when
// onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
// invoked and wait timeout expires, remove callback here.
if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
mLeAdvertisers.remove(mAdvertiseCallback);
}
}
}
/**
* Application interface registered - app is ready to go
*/
@Override
public void onClientRegistered(int status, int clientIf) {
Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
synchronized (this) {
if (status == BluetoothGatt.GATT_SUCCESS) {
try {
if (mClientIf == -1) {//在2秒内未完成注册,超时
// Registration succeeds after timeout, unregister client.
mBluetoothGatt.unregisterClient(clientIf);
} else {//完成注册,并开始广播
mClientIf = clientIf;
mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,
mScanResponse, mSettings);
}
return;
} catch (RemoteException e) {
Log.e(TAG, "failed to start advertising", e);
}
}
// Registration failed.
mClientIf = -1;
notifyAll();
}
}
@Override
public void onMultiAdvertiseCallback(int status, boolean isStart,
AdvertiseSettings settings) {
synchronized (this) {
if (isStart) {//广播成功时的回调
if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
// Start success
mIsAdvertising = true;
postStartSuccess(mAdvertiseCallback, settings);
} else {
// Start failure.
postStartFailure(mAdvertiseCallback, status);
}
} else {//stop 时的回调,用来反注册和清除缓存的callback
// unregister client for stop.
try {
mBluetoothGatt.unregisterClient(mClientIf);
mClientIf = -1;
mIsAdvertising = false;
mLeAdvertisers.remove(mAdvertiseCallback);
} catch (RemoteException e) {
Log.e(TAG, "remote exception when unregistering", e);
}
}
notifyAll();
}
}
}
private void postStartFailure(final AdvertiseCallback callback, final int error) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onStartFailure(error);
}
});
}
private void postStartSuccess(final AdvertiseCallback callback,
final AdvertiseSettings settings) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onStartSuccess(settings);
}
});
} 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方法 /**
* Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
* {@link BluetoothLeAdvertiser#startAdvertising}.
* <p>
* Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
*/
public void stopAdvertising(final AdvertiseCallback callback) {
synchronized (mLeAdvertisers) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
if (wrapper == null) return;
wrapper.stopAdvertising();
}
} 大家可以看到,stopAdvertising方法内部是调用AdvertiseCallbackWrapper.stopAdvertising方法。这里必须注意stopAdvertising方法的callback必须和start时传入的callback参数是同一个。否则在mLeAdvertisers缓存里是找不到相应的AdvertiseCallbackWrapper的实例的,就无法正常停止广播。
转载请注明:http://blog.csdn.net/android_jiangjun/article/details/77946857
Android BLE 总结-源码篇(BluetoothLeAdvertiser)的更多相关文章
- Ubantu16.04进行Android 8.0源码编译
参考这篇博客 经过测试,8.0源码下载及编译之后,占用100多G的硬盘空间,尽量给ubantu系统多留一些硬盘空间,如果后续需要在编译好的源码上进行开发,需要预留更多的控件,为了防止后续出现文件权限问 ...
- 第一部分:开发前的准备-第八章 Android SDK与源码下载
第8章 Android SDK与源码下载 如果你是新下载的SDK,请阅读一下步骤了解如何设置SDK.如果你已经下载使用过SDK,那么你应该使用AVD Manager,来更新即可. 下面是构建Andro ...
- [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程
本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...
- 从谷歌官网下载android 6.0源码、编译并刷入nexus 6p手机
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/fuchaosz/article/details/52473660 1 前言 经过一周的奋战,终于从谷 ...
- 源码篇:ThreadLocal的奇思妙想(万字图文)
前言 ThreadLocal的文章在网上也有不少,但是看了一些后,理解起来总感觉有绕,而且看了ThreadLocal的源码,无论是线程隔离.类环形数组.弱引用结构等等,实在是太有意思了!我必须也要让大 ...
- 源码篇:Flutter Provider的另一面(万字图文+插件)
前言 阅读此文的彦祖,亦菲们,附送一枚Provider模板代码生成插件! 我为啥要写这个插件呢? 此事说来话短,我这不准备写解析Provider源码的文章,肯定要写这框架的使用样例啊,然后再哔哔源码呀 ...
- 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ...
- 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...
- 深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)>我们通过对mybatis源码的简单分析,可看出,在mybatis配置文件中,在configuration根 ...
随机推荐
- 小R与手机
Description 小R有n部手机,为了便于管理,他对一些手机设置了"呼叫转移"的功能. 具体来说,第 i(1≤i≤n) 部手机有个参数 ai(0≤ai≤n,ai≠i) .若 ...
- js和jquery 实现网站来消息网站标题闪动提示
js版 <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"&g ...
- Testing Is the Engineering Rigor of Software Development
Testing Is the Engineering Rigor of Software Development Neal Ford DEVELOPERS LOVE TO USE TORTURED M ...
- 使用Lua 局部变量来优化性能,同一时候比較局部变量和全局变量
在竞争激烈的游戏行业中,尤其页游,面对策划复杂和频繁的需求,使用脚本能够减少难度和成本.在使用Lua的过程中,会常常訪问全局变量来作为配置文件. 在訪问全局变量时,能够通过局部变量引用全局变量来优化. ...
- UVA 11578 - Situp Benches(dp)
题目链接:11578 - Situp Benches 题意:健♂身♂房有两个仰卧起坐坐垫,每次调整角度要花费10元/10度,每次使用要花费15,如今给定n个人的时间顺序,和所希望的角度,求最少花费 思 ...
- Jenkins系列之-—08 实现SQL脚本批量执行
公司内部推广DevOps,所有目前在维护阶段和开发阶段项目全部配置上了自动发布.采用Jenkins+SVN+ANT,之后批量执行SQL语句的实现提上日程 一.环境 Linux环境 安装ANT工具,且下 ...
- response响应和User-Agent历史
返回百度的源码,没有任何伪装: response是服务器响应的类文件,除了支持文件操作的方法以外,还支持以下方法:
- (9)launcher3 之 外部 更换主题Theme APP demo 实现原理以及demo
先说下我的思路: luancher3里面更换图标的逻辑例如以下: 先从APP资源包里查询--数据库查询--其它地方查询ICON 因此,我们仅仅须要把 从数据库获取ICON 代码提前到 从APP资源包 ...
- Android 中View仅仅能接收到ACTION_DOWN无法接收ACTION_MOVE和ACTION_UP解决的方法
昨天晚上调试了一晚上,在LinearLayout上接收屏幕动作,可是出现了问题, 以下的代码是本人调的代码 </pre><pre name="code" clas ...
- LeetCode215:Kth Largest Element in an Array
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the so ...