本文将重点描述Android蓝牙GATT连接的大致流程,不会过多地纠缠代码细节,只为了从架构上梳理清楚,为接下来深入研究底层机制奠定一个宏观认识。
首先建立GATT连接前,我们通常要扫描蓝牙设备,获得设备的BluetoothDevice对象,然后调用connectGatt去建立GATT连接并等待连接状态回调,接下来我们就开始分析这一过程,首先看看connectGatt的实现:
public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
IBluetoothManager managerService = adapter.getBluetoothManager();
try {
IBluetoothGatt iGatt = managerService.getBluetoothGatt();
if (iGatt == null) {
// BLE is not supported
return null;
}
BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this, transport);
gatt.connect(autoConnect, callback);
return gatt;
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
这里主要是获取IBluetoothGatt,Gatt相关操作是单独抽出来的,没有都塞到IBluetoothManager中,否则会让IBluetoothManager显得很臃肿,作为蓝牙总管IBluetoothManager还是简洁一些为好。这个IBluetoothGatt的真正实现在GattService中,不过在进入GattService之前,我们先看看这个BluetoothGatt的connect函数,这里为了突出重点略去了一些代码。
boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
if (!registerApp(callback)) {
return false;
}
return true;
}
这里只调用了registerApp,从字面意思上理解貌似与连接无关,只是注册一个调用方,我们看看其实现:
private boolean registerApp(BluetoothGattCallback callback) {
mCallback = callback;
UUID uuid = UUID.randomUUID();
try {
mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
} catch (RemoteException e) {
return false;
}
return true;
}
这里给用户传进来的callback保存起来,生成了一个UUID作为调用方的标识,然后调用IBluetoothGatt的registerClient去注册,奇怪的是这里传入的是另外一个BluetoothGattCallback,这是个典型的静态代理,想必回调后还要做一些额外处理才会走到我们自己的callback。
private final IBluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallbackWrapper() {
public void onClientRegistered(int status, int clientIf) {
mClientIf = clientIf;
if (status != GATT_SUCCESS) {
mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE, BluetoothProfile.STATE_DISCONNECTED);
return;
}
try {
mService.clientConnect(mClientIf, mDevice.getAddress(), !mAutoConnect, mTransport);
} catch (RemoteException e) {
Log.e(TAG,"",e);
}
}
......
}
这个Callback是一个BluetoothGattCallbackWrapper对象,相当于在我们自己的callback基础上增加了一些别的接口,这些接口只是用于系统内部调用。这里的onClientRegistered就是新增的接口之一,也是我们上面调用registerClient之后的回调。这个回调会返回一个clientIf和status,如果status不是成功则直接返回失败,否则继续调用IBluetoothGatt的clientConnect去真正建立连接。
好了,到此为止我们清楚了Gatt连接是分为两步的,首先要获取一个ClientIf,然后再去连接。这两个操作的实现都是在GattService中,我们先看registerClient,如下:
void registerClient(UUID uuid, IBluetoothGattCallback callback) {
mClientMap.add(uuid, callback, this);
gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
}
这里给uuid和callback建立映射,等到需要回调的时候通过uuid就可以找到callback了。再来看看gattClientRegisterAppNative的实现,是在com_android_bluetooth_gatt.cpp中,如下:
static void gattClientRegisterAppNative(JNIEnv* env, jobject object, jlong app_uuid_lsb, jlong app_uuid_msb )
{
bt_uuid_t uuid;
if (!sGattIf) return;
set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
sGattIf->client->register_client(&uuid);
}
这里sGattIf是在GattService启动的时候初始化的,对应的是一组Gatt操作的接口,包括初始化、清理、gatt client和server相关的接口。这里gatt连接作为client端调到了register_client,传入的是调用方的uuid。其实现是btif_gattc_register_app函数,如下:
static bt_status_t btif_gattc_register_app(bt_uuid_t *uuid) {
btif_gattc_cb_t btif_cb;
memcpy(&btif_cb.uuid, uuid, sizeof(bt_uuid_t));
return btif_transfer_context(btgattc_handle_event, BTIF_GATTC_REGISTER_APP, (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
}
这里调到了btif_transfer_context,从字面上理解是改变上下文,其实际意义是切换运行线程到btif task。这里有两个问题,为什么要切换线程?如何切换线程?
首先看第一个问题,为什么要切换线程,因为调用方是跨进程调到GattService中的,所以会运行在GattService的Binder线程池中,这样就要考虑多线程同步的问题了,因为Gatt Native层实现中有大量的全局变量,多线程环境下肯定会出问题。这里有两种做法,要么到处上锁,要么切换运行线程。类似的在Java中,我们要么加synchronized,要么干脆给所有操作都post到统一的工作线程中。
再来看第二个问题,在Android Java中,我们要切换运行线程只要将逻辑封装到Runnable中然后Post到目标线程的消息队列即可。而这里是Native层该怎么做呢?其实核心思想大致相同,线程中有一个消息队列,我们将消息和操作封装成一个msg,丢到该消息队列中,再将线程唤醒去取消息执行即可。虽然说起来简单,做起来可比Java复杂得多,只是Java中很多底层细节都封装得很好了,我们上层不用再考虑而已。
在蓝牙模块初始化的时候,native层会启动两个线程,一个是btif task,另一个是btu task。上层下来的所有调用都要先丢到btif task中,然后再看情况继续丢到btu task中处理。
我们回到btif_gattc_register_app这个函数,这里虽然切换了上下文,但是要做的事还是不变的,只是改头换面了一下,变成了一个BTIF_GATTC_REGISTER_APP消息和btgattc_handle_event函数。这个函数从字面意思上理解是处理各类事件的,想必里面就是switch case了,我们只关心BTIF_GATTC_REGISTER_APP事件,其处理函数为BTA_GATTC_AppRegister,如下:
void BTA_GATTC_AppRegister(tBT_UUID *p_app_uuid, tBTA_GATTC_CBACK *p_client_cb) {
......
if ((p_buf = (tBTA_GATTC_API_REG *) GKI_getbuf(sizeof(tBTA_GATTC_API_REG))) != NULL)
{
p_buf->hdr.event = BTA_GATTC_API_REG_EVT;
if (p_app_uuid != NULL) {
memcpy(&p_buf->app_uuid, p_app_uuid, sizeof(tBT_UUID));
}
p_buf->p_cback = p_client_cb;
bta_sys_sendmsg(p_buf);
}
return;
}
到这里真让人无语了,简单的一个注册就像踢皮球一样被丢来丢去,又被封装成消息发射出去了,这回是被丢到了另一个线程中,就是传说中的btu task。怎么丢的我们暂时不管,还是先搞清楚怎么注册才最要紧,经过了千辛万苦终于到了真正的注册环节,就是bta_gattc_register函数了,如下:
void bta_gattc_register(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_data) {
tBTA_GATTC cb_data;
memset(&cb_data, 0, sizeof(cb_data));
cb_data.reg_oper.status = BTA_GATT_NO_RESOURCES;
for (i = 0; i < BTA_GATTC_CL_MAX; i ++) {
if (!p_cb->cl_rcb[i].in_use) {
if ((p_cb->cl_rcb[i].client_if = GATT_Register(p_app_uuid, &bta_gattc_cl_cback)) == 0) {
status = BTA_GATT_ERROR;
} else {
p_cb->cl_rcb[i].in_use = TRUE;
p_cb->cl_rcb[i].p_cback = p_data->api_reg.p_cback;
memcpy(&p_cb->cl_rcb[i].app_uuid, p_app_uuid, sizeof(tBT_UUID));
/* BTA use the same client interface as BTE GATT statck */
cb_data.reg_oper.client_if = p_cb->cl_rcb[i].client_if;
if ((p_buf = (tBTA_GATTC_INT_START_IF *) GKI_getbuf(sizeof(tBTA_GATTC_INT_START_IF))) != NULL) {
p_buf->hdr.event = BTA_GATTC_INT_START_IF_EVT;
p_buf->client_if = p_cb->cl_rcb[i].client_if;
bta_sys_sendmsg(p_buf);
status = BTA_GATT_OK;
} else {
GATT_Deregister(p_cb->cl_rcb[i].client_if);
status = BTA_GATT_NO_RESOURCES;
memset( &p_cb->cl_rcb[i], 0 , sizeof(tBTA_GATTC_RCB));
}
break;
}
}
}
if (p_data->api_reg.p_cback) {
if (p_app_uuid != NULL) {
memcpy(&(cb_data.reg_oper.app_uuid), p_app_uuid,sizeof(tBT_UUID));
}
cb_data.reg_oper.status = status;
(*p_data->api_reg.p_cback)(BTA_GATTC_REG_EVT, (tBTA_GATTC *)&cb_data);
}
}
这个函数稍微有点长,不过逻辑很简单,就是在一个for循环中遍历看是否有可用的clientif,如果没有就返回BTA_GATT_NO_RESOURCES,值为128。遍历的时候发现某个槽没有人用就会去调用GATT_Register注册,注册成功就会返回一个clientIf,然后就要开始往java层回调了。在往回走之前,我们先看看GATT_Register的实现:
tGATT_IF GATT_Register (tBT_UUID *p_app_uuid128, tGATT_CBACK *p_cb_info)
{
tGATT_REG *p_reg;
UINT8 i_gatt_if=0;
tGATT_IF gatt_if=0;
for (i_gatt_if = 0, p_reg = gatt_cb.cl_rcb; i_gatt_if < GATT_MAX_APPS; i_gatt_if++, p_reg++)
{
if (!p_reg->in_use)
{
memset(p_reg, 0 , sizeof(tGATT_REG));
i_gatt_if++; /* one based number */
p_reg->app_uuid128 = *p_app_uuid128;
gatt_if =
p_reg->gatt_if = (tGATT_IF)i_gatt_if;
p_reg->app_cb = *p_cb_info;
p_reg->in_use = TRUE;
break;
}
}
return gatt_if;
}
这里逻辑很简单,就是看哪个槽没有被人占用,不过注意的是这个槽和上面的槽是不同的,上面的槽是bta_gattc_cb中的cl_rcb,这的槽是gatt_cb的cl_rcb。不过两个槽大小都一样,都是32。这样我们了解到clientIf是有数量限制的,而且是系统全局的,而不是单个APP进程内的限制。每次用完之后要及时释放,否则别的人就没法再用了。
好了接下来我们踏上归途了,看看拿到这个clientIf之后是怎么回调回上层的。回到bta_gattc_register函数中,回调是从这一句开始的
(*p_data->api_reg.p_cback)(BTA_GATTC_REG_EVT, (tBTA_GATTC *)&cb_data);
这个p_cback是什么呢,注册的时候被封装成消息转手了无数次,但还是给揪出来了,这个指针指向的是bta_gattc_cback,如下:
static void bta_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data) {
bt_status_t status = btif_transfer_context(btif_gattc_upstreams_evt,
(uint16_t) event, (void*) p_data, sizeof(tBTA_GATTC), btapp_gattc_req_data);
}
这里可以理解,毕竟来的路上是怎么切过来的,回去的时候就得怎么切回去。之前是先切到btif task,再切到btu task,现在要从btu task切回到btif task了,回调是btif_gattc_upstreams_evt,事件是BTA_GATTC_REG_EVT,如下:
static void btif_gattc_upstreams_evt(uint16_t event, char* p_param)
{
tBTA_GATTC *p_data = (tBTA_GATTC*) p_param;
switch (event)
{
case BTA_GATTC_REG_EVT:
{
bt_uuid_t app_uuid;
bta_to_btif_uuid(&app_uuid, &p_data->reg_oper.app_uuid);
bt_gatt_callbacks->client->register_client_cb(p_data->reg_oper.status, p_data->reg_oper.client_if, &app_uuid);
break;
}
......
}
}
这里的register_client_cb最终指向的是btgattc_register_app_cb函数,这已经回到了gatt service的native中了,如下:
void btgattc_register_app_cb(int status, int clientIf, bt_uuid_t *app_uuid)
{
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
clientIf, UUID_PARAMS(app_uuid));
}
这里调用到了Java层的onClientRegistered函数,返回的正是clientIf和uuid。至此,整个clientIf的注册流程终于走通了,虽然中间有很多代码细节我们没有深究,不过那都不重要了,有了对总体的把握,以后遇到具体问题再细看也不迟。而且代码细节很可能在以后Android升级时有重大改动,但总体思想和大致流程基本不会变的。
总结一下调用流程,App发起的Gatt连接请求被丢到GattService中,分解为两步走,第一步是注册ClientIf,注册成功后再拿着ClientIf建立真正的连接。先看ClientIf的注册,会往下走到GattService的native层中,再往下走到BlueDroid层,注册ClientIf完之后,会回到GattService的native层,再回调到GattService java层,这时如果ClientIf是注册成功的,则继续走Gatt连接流程,否则直接回调失败给用户。
下文我们将继续分析蓝牙真正的Gatt连接流程。