BLE scan 在bluedroid的实现中,有两个接口:一个是discovery,一个是ble observe,这两者有什么区别呢?

这里追了一下代码发现,inquiry 是上层调用search 相关的接口的实现函数,ble observe 是调用GATT scan的接口的实现函数,这篇文章分析一下,在调用GATT 接口实现scan的流程。

GATT的服务代码逻辑在哪里实现的呢?其核心代码的实现是bluedroid里面,但是,上层的应用是不可能直接调用协议栈的代码的,其实在bluedroid上面还会进行封装一个GATT的服务,其实现在package/app/Bluetooth下面的GattService.java,这里面实现了关于GATT相关的各种接口,应用层的代码通过binder 调用绑定到这些接口,并完成一系列的调用。

看一下GattService.java的实现:

    /**
* Handlers for incoming service calls
*/
private static class BluetoothGattBinder extends IBluetoothGatt.Stub implements IProfileServiceBinder {
private GattService mService; public BluetoothGattBinder(GattService svc) {
mService = svc;
} public boolean cleanup() {
mService = null;
return true;
}
...
@Override
public void startScan(int appIf, boolean isServer, ScanSettings settings,
List<ScanFilter> filters, List storages, String callingPackage) {
GattService service = getService();
if (service == null) return;
service.startScan(appIf, isServer, settings, filters, storages, callingPackage);
} public void stopScan(int appIf, boolean isServer) {
GattService service = getService();
if (service == null) return;
service.stopScan(new ScanClient(appIf, isServer));
} ...
}

可以看到其代码中实现了一个BluetoothGattBinder,这个上层应用程序在绑定完成的时候,会得到这个binder接口。我们也可以看到,这个binder实现的也是GATT相关的基本的接口。

分析一下startScan接口,发现其是调用到另一个service 的startScan的接口,那这个service 是哪里来的呢?其实这个service就是GATTService本身,在initBinder的时候,将this 指针传入。

    protected IProfileServiceBinder initBinder() {
return new BluetoothGattBinder(this);
}

我们的重点是分析startScan 这个接口的流程,现在我们看GATTService是如何实现这个接口的

    void startScan(int appIf, boolean isServer, ScanSettings settings,
List<ScanFilter> filters, List<List<ResultStorageDescriptor>> storages,
String callingPackage) {
...
final ScanClient scanClient = new ScanClient(appIf, isServer, settings, filters, storages);
...
mScanManager.startScan(scanClient);
}

新建了一个scanClient 类,并将此类传入到mScanManager.startScan中:

    void startScan(ScanClient client) {
sendMessage(MSG_START_BLE_SCAN, client);
}

此时的代码走到了ScanManager.java里面,不管上层的代码如何流转,我们知道,最后肯定还是调用到JNI 的接口,然后到达bluedroid里面,接着看:

    // Handler class that handles BLE scan operations.
private class ClientHandler extends Handler {
...
@Override
public void handleMessage(Message msg) {
ScanClient client = (ScanClient) msg.obj;
switch (msg.what) {
case MSG_START_BLE_SCAN://处理事件
handleStartScan(client);
break;
case MSG_STOP_BLE_SCAN:
handleStopScan(client);
break;
...
}
} void handleStartScan(ScanClient client) {//处理scan的实现函数
Utils.enforceAdminPermission(mService);...
// Begin scan operations.
if (isBatchClient(client)) {
mBatchClients.add(client);
mScanNative.startBatchScan(client);
} else {
mRegularScanClients.add(client);
mScanNative.startRegularScan(client);
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
}
}
} ...
}

这边分析一下startBatchScan是vendor command 相关,那么一般都是调用到mScanNative.startRegularScan,这边已经调用到了native层面,具体看看其实现:

        void startRegularScan(ScanClient client) {
if (isFilteringSupported() && mFilterIndexStack.isEmpty()
&& mClientFilterIndexMap.isEmpty()) {
initFilterIndexStack();
}
if (isFilteringSupported()) {
configureScanFilters(client);
}
// Start scan native only for the first client.
if (numRegularScanClients() == 1) {
gattClientScanNative(true);
}
}

这边继续往下调用到gattClientScanNative(true) :这里调用到JNI 层,其实现在文件com_android_bluetooth_gatt.cpp

static void gattClientScanNative(JNIEnv* env, jobject object, jboolean start)
{
if (!sGattIf) return;
sGattIf->client->scan(start);
}

到这里就很明确了,其最终调用的是sGattIf中client 的scan的接口,那其接口是怎么样的呢?

其是在bluetooth.c里面通过get_profile_interface 来获取GATT的interface的,

static const btgatt_interface_t btgattInterface = {
sizeof(btgattInterface),
btif_gatt_init,
btif_gatt_cleanup,
&btgattClientInterface,
&btgattServerInterface,
};

中的client 的接口如下:

const btgatt_client_interface_t btgattClientInterface = {
btif_gattc_register_app,
btif_gattc_unregister_app,
btif_gattc_scan,
btif_gattc_open,
btif_gattc_close,
btif_gattc_listen,
...
}

那其实调用的就是:btif_gattc_scan

static bt_status_t btif_gattc_scan( bool start )
{
CHECK_BTGATT_INIT();
btif_gattc_cb_t btif_cb;
return btif_transfer_context(btgattc_handle_event, start ? BTIF_GATTC_SCAN_START : BTIF_GATTC_SCAN_STOP,
(char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
}

这里将处理的流程transfer到bt_jni_workqueue_thread线程了,从这个线程的名字来看,主要是处理从JNI 下来的事件。看看具体做了什么:

static void btgattc_handle_event(uint16_t event, char* p_param)
{
...
btif_gattc_cb_t* p_cb = (btif_gattc_cb_t*) p_param;
if (!p_cb) return;
switch (event)
{
...
case BTIF_GATTC_SCAN_START:
btif_gattc_init_dev_cb();
BTA_DmBleObserve(TRUE, , bta_scan_results_cb);//调用的是这个函数
break;
...

继续看BTA_DmBleObserve,注意第一个参数是true,表示开始scan,第二个参数是持续时间,0表示一直持续:

extern void BTA_DmBleObserve(BOOLEAN start, UINT8 duration,
tBTA_DM_SEARCH_CBACK *p_results_cb)
{
tBTA_DM_API_BLE_OBSERVE *p_msg;
APPL_TRACE_API("BTA_DmBleObserve:start = %d ", start);
if ((p_msg = (tBTA_DM_API_BLE_OBSERVE *) GKI_getbuf(sizeof(tBTA_DM_API_BLE_OBSERVE))) != NULL)
{
memset(p_msg, , sizeof(tBTA_DM_API_BLE_OBSERVE));
p_msg->hdr.event = BTA_DM_API_BLE_OBSERVE_EVT;//向bt_workqueue_thread发送BTA_DM_API_BLE_OBSERVE_EVT
     p_msg->start = start;
     p_msg->duration = duration; 
p_msg->p_cback = p_results_cb;
bta_sys_sendmsg(p_msg);
}
}

看了一下代码发现bt_workqueue_thread 是处理事件的主线程,bta_sys_sendmsg(p_msg); 这个函数是将消息发送到btu_bta_msg_queue,而这个queue是和bt_workqueue_thread绑定的,队列里面的消息都会在这个线程里面处理:

void bta_sys_sendmsg(void *p_msg)
{
if (btu_bta_msg_queue)
fixed_queue_enqueue(btu_bta_msg_queue, p_msg);
}

那现在 关于scan的event 的处理已经来到了另一个线程:bt_workqueue_thread,那么该队列里面有了数据线程如何处理?

  fixed_queue_register_dequeue(btu_bta_msg_queue,
thread_get_reactor(bt_workqueue_thread),
btu_bta_msg_ready,
NULL);

根据上面的代码,我们知道会调用到btu_bta_msg_ready:

void btu_bta_msg_ready(fixed_queue_t *queue, UNUSED_ATTR void *context) {
BT_HDR *p_msg = (BT_HDR *)fixed_queue_dequeue(queue);//消息出列
bta_sys_event(p_msg);
}

也就是先让消息处理,然后再调用bta_sys_event来处理:那至此我们知道,凡是调用到bta_sys_sendmsg,那最终处理的函数都是bta_sys_event来处理,而这个函数的处理方式也是一种dispatch的机制:

void bta_sys_event(BT_HDR *p_msg)
{
...
/* get subsystem id from event */
id = (UINT8) (p_msg->event >> );
/* verify id and call subsystem event handler */
if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL))
{
freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);
}
...
}

其思想就是找到该事件对应的处理函数,这些event的高8bit 是属于事件的类型,或者称为主事件,而event的低8 bit是事件的子类,或者称为子事件。处理的过程是先通过主事件找到事件的处理函数handler(当然肯定是事先注册好的),然后在该处理函数中处理子事件。

那该事件的处理函数handler 是什么呢?

/*******************************************************************************
**
** Function bta_sys_register
**
** Description Called by other BTA subsystems to register their event
** handler.
**
**
** Returns void
**
*******************************************************************************/
void bta_sys_register(UINT8 id, const tBTA_SYS_REG *p_reg)
{
bta_sys_cb.reg[id] = (tBTA_SYS_REG *) p_reg;
bta_sys_cb.is_reg[id] = TRUE;
}

这里是注册的地方,根据函数的注释,是BTA 的子系统注册自己的event 的处理函数 时候所调用的。下图很容易看出有哪些模块调用这个注册函数

对于BTA_DM_API_BLE_OBSERVE_EVT 这个event 可知其主事件是BTA_ID_DM = 1 ,其注册 的地方在BTA_EnableBluetooth:

bta_sys_register (BTA_ID_DM, &bta_dm_reg );

那现在我们知道,其处理的函数的入口就是bta_dm_reg:

static const tBTA_SYS_REG bta_dm_reg =
{
bta_dm_sm_execute,
bta_dm_sm_disable
};
BOOLEAN bta_dm_sm_execute(BT_HDR *p_msg)
{
UINT16 event = p_msg->event & 0x00ff;//取出子事件
/* execute action functions */
if(event < BTA_DM_NUM_ACTIONS)
{
(*bta_dm_action[event])( (tBTA_DM_MSG*) p_msg);
}
return TRUE;
}

这里我们发现,其设计还是比较巧妙,每个event 对应的处理函数,是在一个大的数组中,用事件的子事件(低8bit)来寻址,这有点分页的意味了。

这里该事件真正的处理函数是bta_dm_ble_observe:并调用如下代码:

((status = BTM_BleObserve(TRUE, p_data->ble_observe.duration,
bta_dm_observe_results_cb, bta_dm_observe_cmpl_cb))!= BTM_CMD_STARTED)

这里我们发现,函数调用已经进入到stack里面了,BTM_BleObserve,看看其具体实现,这里我们应该还记得,这里的第二个参数传进来的时候是0:

tBTM_STATUS BTM_BleObserve(BOOLEAN start, UINT8 duration,
tBTM_INQ_RESULTS_CB *p_results_cb, tBTM_CMPL_CB *p_cmpl_cb)
{
...
UINT32 scan_interval = !p_inq->scan_interval ? BTM_BLE_GAP_DISC_SCAN_INT : p_inq->scan_interval;//发现参数是优先使用inquiry的参数
UINT32 scan_window = !p_inq->scan_window ? BTM_BLE_GAP_DISC_SCAN_WIN : p_inq->scan_window;
...
if (start)
{
/* shared inquiry database, do not allow observe if any inquiry is active */
if (BTM_BLE_IS_OBS_ACTIVE(btm_cb.ble_ctr_cb.scan_activity))//如果有observe 直接返回
{
BTM_TRACE_ERROR("%s Observe Already Active", __func__);
return status;
}
btm_cb.ble_ctr_cb.p_obs_results_cb = p_results_cb;
btm_cb.ble_ctr_cb.p_obs_cmpl_cb = p_cmpl_cb;
status = BTM_CMD_STARTED;
/* scan is not started */
if (!BTM_BLE_IS_SCAN_ACTIVE(btm_cb.ble_ctr_cb.scan_activity))//没有其他的scan 行为才继续执行
{
/* allow config of scan type */
p_inq->scan_type = (p_inq->scan_type == BTM_BLE_SCAN_MODE_NONE) ?
BTM_BLE_SCAN_MODE_ACTI: p_inq->scan_type;
...
p_inq->scan_duplicate_filter = BTM_BLE_DUPLICATE_DISABLE;
status = btm_ble_start_scan();//开始scan
}
if (status == BTM_CMD_STARTED)
{
btm_cb.ble_ctr_cb.scan_activity |= BTM_LE_OBSERVE_ACTIVE;
if (duration != )
/* start observer timer */
btu_start_timer (&btm_cb.ble_ctr_cb.obs_timer_ent, BTU_TTYPE_BLE_OBSERVE, duration);//这里注意,如果duration设置了,那么经过一定时间就会超时,然后会停止scan,如果没有设置这个值,就会一直scan
}
}

这里注意一下代码中有这样一句注释:shared inquiry database, do not allow observe if any inquiry is active,说明oberve的优先级还是很低的。从代码中也 可以看出只有当没有其他的scan的行为,observe才会继续进行。另外对于scan type 是active还是passive的问题,当p_inq->scan_interval 没有设置的话,就使用active,否则就使用当前的设置值。从这也可以看出,active 是优先被使用的。

最后看看btm_ble_start_scan的实现,这个就很简单了,直接通过HCI 来发送命令了:

tBTM_STATUS btm_ble_start_scan(void)
{
tBTM_BLE_INQ_CB *p_inq = &btm_cb.ble_ctr_cb.inq_var;
tBTM_STATUS status = BTM_CMD_STARTED; /* start scan, disable duplicate filtering */
if (!btsnd_hcic_ble_set_scan_enable (BTM_BLE_SCAN_ENABLE, p_inq->scan_duplicate_filter))//HCI command
{
status = BTM_NO_RESOURCES;
}
else
{
if (p_inq->scan_type == BTM_BLE_SCAN_MODE_ACTI)
btm_ble_set_topology_mask(BTM_BLE_STATE_ACTIVE_SCAN_BIT);//更新拓扑
else
btm_ble_set_topology_mask(BTM_BLE_STATE_PASSIVE_SCAN_BIT);
}
return status;
}

scan结果的回报:


前面注册的时候,我们看到

        case BTIF_GATTC_SCAN_START:
btif_gattc_init_dev_cb();
BTA_DmBleObserve(TRUE, , bta_scan_results_cb);
break;

其回调函数是bta_scan_results_cb,当搜索结果上来的时候,该函数会被调用:

static void bta_scan_results_cb (tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
{
...
switch (event)
{
case BTA_DM_INQ_RES_EVT:
{
...
}
break; case BTA_DM_INQ_CMPL_EVT:
{
...
}
btif_transfer_context(btif_gattc_upstreams_evt, BTIF_GATT_OBSERVE_EVT,
(char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
}

代码中针对BTA_DM_INQ_RES_EVT和BTA_DM_INQ_CMPL_EVT 都会有自己的一些处理,但是最后都要经过btif_gattc_upstreams_evt的处理,并且是event = BTIF_GATT_OBSERVE_EVT

看具体的代码实现:

        case BTIF_GATT_OBSERVE_EVT:
{
btif_gattc_cb_t *p_btif_cb = (btif_gattc_cb_t*) p_param;
...
BTIF_STORAGE_FILL_PROPERTY(&properties,
BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type), &dev_type);
btif_storage_set_remote_device_property(&(p_btif_cb->bd_addr), &properties); HAL_CBACK(bt_gatt_callbacks, client->scan_result_cb,
&p_btif_cb->bd_addr, p_btif_cb->rssi, p_btif_cb->value);
break;
}

上面做的主要就是保存设备的属性,以及向上层汇报相关的设备信息:使用bt_gatt_callbacks中的 client->scan_result_cb,接口。

那这个接口是哪里来的呢?

static bt_status_t btif_gatt_init( const btgatt_callbacks_t* callbacks )
{
bt_gatt_callbacks = callbacks;
return BT_STATUS_SUCCESS;
}

发现是gatt 模块init的时候赋值的,那么我们就知道其callback 来源于JNI层面:

static const btgatt_callbacks_t sGattCallbacks = {
sizeof(btgatt_callbacks_t),
&sGattClientCallbacks,
&sGattServerCallbacks
};
static const btgatt_client_callbacks_t sGattClientCallbacks = {
btgattc_register_app_cb,
btgattc_scan_result_cb,//此函数
btgattc_open_cb,
...

通过JNI方法的回调:

void btgattc_scan_result_cb(bt_bdaddr_t* bda, int rssi, uint8_t* adv_data)
{
...
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult
, address, rssi, jb);//调用method_onScanResult
... checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); }

这个方法的实现是在java层,那到底对应于哪一个文件呢?

int register_com_android_bluetooth_gatt(JNIEnv* env)
{
int register_success =
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/ScanManager$ScanNative",
sScanMethods, NELEM(sScanMethods));
register_success &=
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/AdvertiseManager$AdvertiseNative",
sAdvertiseMethods, NELEM(sAdvertiseMethods));
return register_success &
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
sMethods, NELEM(sMethods));
}

发现sMethods对应于"com/android/bluetooth/gatt/GattService" ,那我们知道其实现是在GattService.java里面。看具体的实现:

    void onScanResult(String address, int rssi, byte[] adv_data) {
if (VDBG) Log.d(TAG, "onScanResult() - address=" + address
+ ", rssi=" + rssi);
List<UUID> remoteUuids = parseUuids(adv_data);
for (ScanClient client : mScanManager.getRegularScanQueue()) {
if (client.uuids.length > ) {
int matches = ;
for (UUID search : client.uuids) {
for (UUID remote: remoteUuids) {
if (remote.equals(search)) {
++matches;
break; // Only count 1st match in case of duplicates
}
}
} if (matches < client.uuids.length) continue;
} if (!client.isServer) {
ClientMap.App app = mClientMap.getById(client.clientIf);
if (app != null) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter()
.getRemoteDevice(address);
ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(adv_data),
rssi, SystemClock.elapsedRealtimeNanos());
// Do no report if location mode is OFF or the client has no location permission
// PEERS_MAC_ADDRESS permission holders always get results
if (hasScanResultPermission(client) && matchesFilters(client, result)) {
try {
ScanSettings settings = client.settings;
if ((settings.getCallbackType() &
ScanSettings.CALLBACK_TYPE_ALL_MATCHES) != ) {
app.callback.onScanResult(result);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception: " + e);
mClientMap.remove(client.clientIf);
mScanManager.stopScan(client);
}
}
}
} else {
ServerMap.App app = mServerMap.getById(client.clientIf);
if (app != null) {
try {
app.callback.onScanResult(address, rssi, adv_data);
} catch (RemoteException e) {
Log.e(TAG, "Exception: " + e);
mServerMap.remove(client.clientIf);
mScanManager.stopScan(client);
}
}
}
}
}

到这里呢,协议栈就将关于设备的信息上传到bluetooth.apk了,在这个函数里面,我们可以看到其最终调用到app.callback.onScanResult(address, rssi, adv_data);,这边应该是回调到更上一层应用。

GATT scan的流程的更多相关文章

  1. GATT服务搜索流程(一)

    GATT的规范阅读起来还是比较简答, 但是这样的规范在代码上是如何实现的呢?下面就分析一下bluedroid 协议栈关于GATT的代码流程. BLE的设备都是在SMP之后进行ATT的流程的交互.从代码 ...

  2. GATT服务搜索流程(二)

    关于bta_dm_cb.p_sec_cback,这里我们之前已经分析过,他就是bte_dm_evt ,最终调用的函数btif_dm_upstreams_evt : static void btif_d ...

  3. HBase Scan流程分析

    HBase Scan流程分析 HBase的读流程目前看来比较复杂,主要由于: HBase的表数据分为多个层次,HRegion->HStore->[HFile,HFile,...,MemSt ...

  4. Android WIFI 启动流程

    参考:http://blog.chinaunix.net/uid-26215986-id-3260413.html 一. WIFI 工作步骤 1. Wifi模块初始化 2. Wifi启动 3. 查找热 ...

  5. HQueue:基于HBase的消息队列

    HQueue:基于HBase的消息队列   凌柏   ​1. HQueue简介 HQueue是一淘搜索网页抓取离线系统团队基于HBase开发的一套分布式.持久化消息队列.它利用HTable存储消息数据 ...

  6. 【转载】HBase 数据库检索性能优化策略

    转自:http://www.ibm.com/developerworks/cn/java/j-lo-HBase/index.html 高性能 HBase 数据库 本文首先介绍了 HBase 数据库基本 ...

  7. [Android] 输入系统(三):加载按键映射

    映射表基本概念 由于Android调用getEvents得到的key是linux发送过来的scan code,而Android处理的是类似于KEY_UP这种统一类型的key code,因此需要有映射表 ...

  8. HBase 数据库检索性能优化策略--转

    https://www.ibm.com/developerworks/cn/java/j-lo-HBase/index.html HBase 数据表介绍 HBase 数据库是一个基于分布式的.面向列的 ...

  9. HBase多租户机制分析

    在HBase1.1.0发布之前,HBase同一集群上的用户.表都是平等的,没有优劣之分.这种’大同’社会看起来完美,实际上有很多问题.最棘手的主要有这么两个,其一是某些业务较其他业务重要,需要在资源有 ...

随机推荐

  1. FTP上传下载类

    public class FtpOperation { public static void UploadFile(FileInfo fileinfo, string targetDir, strin ...

  2. 使用托管快照创建作为 Azure 托管磁盘存储的 VHD 的副本

    创建快照 创建 OS 或数据磁盘 VHD 的快照,以便将其用作备份或用于排查 VM 问题. 快照是 VHD 的完整只读副本. 使用 Azure 门户创建快照 登录到 Azure 门户. 首先在左上角单 ...

  3. Oracle EBS OM 删除订单行

    DECLARE l_header_rec OE_ORDER_PUB.Header_Rec_Type; l_line_tbl OE_ORDER_PUB.Line_Tbl_Type; l_action_r ...

  4. Oracle EBS AP取消核销

    --取消核销 created by jenrry 20170425 DECLARE l_result BOOLEAN; l_msg_count NUMBER; l_result_n varchar2( ...

  5. IntelliJ IDEA2018激活方法

    前言: IntelliJ IDEA2018请在官网下载:https://www.jetbrains.com/idea/ 一.license server激活 输入http://idea.jialeen ...

  6. 一个服务器多个tomcat的配置

    下面我们把配置的详细过程写在下面,以供参考:(此例以配置三个Tomcat为例)1. 下载apache-tomcat-7.0.63,下载下来的文件为apache-tomcat-7.0.63.zip.2. ...

  7. SSH批量分发管理

    ssh服务认证类型主要有两个: 基于口令的安全验证: 基于口令的安全验证的方式就是大家一直在用的,只要知道服务器的ssh连接账户.口令.IP及开发的端口,默认22,就可以通过ssh客户端登陆到这台远程 ...

  8. python设计模式之工厂模式

    一.理解工厂模式 在面向对象编程中,术语“工厂”表示一个负责创建替他类型对象的类.通常情况下,作为一个工厂的类有一个对象以及与它关联的多个方法.客户端使用某些参数调用此方法,之后,工厂会据此创建所需类 ...

  9. Windows窗体数据抓取详解

    最近在客户项目上刚好遇到一个问题,项目需求是要获取某台机床的实时状态,问题点刚好就在于该机床不是传统意义上的数控机床,也不是PLC控制器,只有一个上传下载程序文件的应用程序,上面刚好有几个按钮可以大概 ...

  10. NOIP2018考前抱佛脚——数据结构基础及STL实现

    目录 动态数组 栈 队列 优先队列 动态数组 srand(time(0)); std::vector<int> qwq; for(int i = 1;i <= 10;++i) qwq ...