我们注意到GattService中的所有GATT操作都会带两个参数,一个是clientIf,另一个是address,拿到这两个参数后,都会调用Integer connId = mClientMap.connIdByAddress(clientIf, address);
获取对应的connId,然后之后的操作都是以这个connId为Key。我们看connIdByAddress的实现:
Integer connIdByAddress(int id, String address) {
App entry = getById(id);
if (entry == null) return null;
Iterator<Connection> i = mConnections.iterator();
while (i.hasNext()) {
Connection connection = i.next();
if (connection.address.equalsIgnoreCase(address) && connection.appId == id)
return connection.connId;
}
return null;
}
先看getById是怎么获取App的,这里传入的id是ClientIf:
App getById(int id) {
Iterator<App> i = mApps.iterator();
while (i.hasNext()) {
App entry = i.next();
if (entry.id == id) return entry;
}
Log.e(TAG, "Context not found for ID " + id);
return null;
}
这个mApps是一个链表,如下:
List<App> mApps = new ArrayList<App>();
为什么这里要定义App呢,对于Android系统蓝牙服务来说,每个蓝牙请求都来自于不同的对象,所以这里定义这个类就是为了区分不同的对象,从字面意思上理解这个对象应该是App。而每个App都会被分配一个id,这个getById就是通过id来找到对应的App对象。什么时候给App分配id的呢,在注册完ClientIf的回调中,如下:
void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb) throws RemoteException {
UUID uuid = new UUID(uuidMsb, uuidLsb);
ClientMap.App app = mClientMap.getByUuid(uuid);
if (app != null) {
if (status == 0) {
app.id = clientIf;
app.linkToDeath(new ClientDeathRecipient(clientIf));
} else {
mClientMap.remove(uuid);
}
app.callback.onClientRegistered(status, clientIf);
}
}
我们回忆设备的连接过程,首先是生成一个随机的UUID,然后去注册ClientIf,注册完后就会有这个回调了。我们看registerClient,如下:
void registerClient(UUID uuid, IBluetoothGattCallback callback) {
mClientMap.add(uuid, callback, this);
gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
}
这里必然是通过uuid和callback生成了一个App保存起来了,之所以要callback是因为这个callback是一个IBinder,通过关注其生死可以知道调用方是否还在,这样当调用方被杀掉时可以停止蓝牙扫描或断开连接。有了这个App之后在onClientRegistered时才能通过uuid取出app,然后给clientIf设置为app的id。
这里和我们设想的有点不一样,开始我以为是一个App对应一个id,而这里App的id是clientIf,而事实上一个App中可以有很多clientIf。不过想想也没关系,其实对系统来说不用管请求来自哪个App或是哪个进程,只要管连接就行了,而描述连接的句柄就是clientIf。如果连接都是同一个设备,但是来自于两个不同的App进程,那么ClientIf必然也是不一样的,就算同一个App里我们对同一个设备打开了两个Gatt,他们的ClientIf也是不一样的,不然对设备的读写就会串了。所以这个GattService里的App其实是从系统蓝牙服务来说的,就是一个Client。
回到connIdByAddress,获取App entry以后,遍历mConnections,这是描述已连接的设备,如下:
/** Internal list of connected devices **/
Set<Connection> mConnections = new HashSet<Connection>();
遍历mConnections,要同时满足appId和address都匹配上才返回。这样可以理解,可能某个设备分别在两个App中都连上了,那样在与设备通信的时候不能冲突了,所以必须要appId和address都匹配才行。
这个Connection是在设备连接上的时候注册的,并且设备连接上的回调中会传入connId,以后再与设备操作都不是用address或者clientIf,而是都用这个connId了。
void onConnected(int clientIf, int connId, int status, String address)
throws RemoteException {
if (status == 0) mClientMap.addConnection(clientIf, connId, address);
ClientMap.App app = mClientMap.getById(clientIf);
if (app != null) {
app.callback.onClientConnectionState(status, clientIf,
(status==BluetoothGatt.GATT_SUCCESS), address);
}
}
到了这里connection id是怎么回事我们应该清楚了,但有一个最核心的问题还没搞清楚,那就是connection id是怎么生成的?我们只知道onConnected回调回来时就带上了connId,就是说这个connId是底层生成好的,然后传上来的。那我们要搞清楚底层是怎么生成这个connid的,是谁调的这个onConnected呢?
答案在com_android_bluetooth_gatt.cpp中的btgattc_open_cb函数,如下
void btgattc_open_cb(int conn_id, int status, int clientIf, bt_bdaddr_t* bda) {
jstring address = bdaddr2newjstr(sCallbackEnv, bda);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnected,
clientIf, conn_id, status, address);
sCallbackEnv->DeleteLocalRef(address);
}
这里的conn_id也是生成好的,我们在bluedroid中找,发现是在gatt_int.h中,如下:
#define GATT_CREATE_CONN_ID(tcb_idx, gatt_if) ((UINT16) ((((UINT8)(tcb_idx) ) << 8) | ((UINT8) (gatt_if))))
这个tcb_idx和gatt_if是什么东西?我现在还不能给出答案,不过没关系,随着研究逐步展开,这些问题都会迎刃而解。