Android Gatt连接流程源码分析之ClientIf注册

本文将重点描述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连接流程。

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/tronteng/article/details/53435231
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞