1 Bluetooth属性获取流程
在BluetoothAdapterProperties.java中,有一个Map类型的变量:mPropertiesMap,该变量中就保存着当前运行中蓝牙的所有的属性值。所有需要读取当前蓝牙属性的地方都需要从该变量中获得。而该变量的值有两种获得方法,一个是直接从Kernel中读取,另外一种是由驱动上报属性值的变动,然后由Framework层进行获取。
1.1属性直接从Kernel读取
在BluetoothAdapterProperties->getAllProperties()中有:
mPropertiesMap.clear();
String properties[] = (String[]) mService
.getAdapterPropertiesNative(); //JNI调用从Kernel获得属性值
// The String Array consists of key-value pairs.
if (properties == null) {
Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
return;
}
for (int i = 0; i < properties.length; i++) {
String name = properties[i];
String newValue = null;
if (name == null) {
Log.e(TAG, "Error:Adapter Property at index " + i + " is null");
continue;
}
if (name.equals("Devices") || name.equals("UUIDs")) {
StringBuilder str = new StringBuilder();
int len = Integer.valueOf(properties[++i]);
for (int j = 0; j < len; j++) {
str.append(properties[++i]);
str.append(",");
}
if (len > 0) {
newValue = str.toString();
}
} else {
newValue = properties[++i];
}
mPropertiesMap.put(name, newValue); //将读取的属性值存入mPropertiesMap中
}
下面接着来看mService.getAdapterPropertiesNative();调用,这里mService是一个BluetoothService对象,而getAdapterPropertiesNative()是一个Native方法,所以直接跟到其具体实现android_server_bluetoothservice.cpp的getAdapterPropertiesNative()中去,在其代码里有:
reply = dbus_func_args_timeout(env,
nat->conn, -1, get_adapter_path(env, object),
DBUS_ADAPTER_IFACE, "GetProperties",
DBUS_TYPE_INVALID);
这里既是其获取具体属性的地方,而”GetProperties”则是后续继续调用的地方。不过到了这里,可能会发现,找不到这个函数了,其实这个函数是在蓝牙的底层实现里面,具体的文件位置可能根据android版本的不同而有所不同,我的版本下文件位置在:external\bluetooth\bluez\src\adapter.c里面。
在该文件里有:
{ "GetProperties", "", "a{sv}",get_properties },
既与GetProperties对应的函数实现是:get_properties(),进去看看:
/* Address */
property = srcaddr;
dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &property);
/* Name */
memset(str, 0, sizeof(str));
strncpy(str, (char *) adapter->name, MAX_NAME_LENGTH);
property = str;
dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &property);
/* Class */
dict_append_entry(&dict, "Class",
DBUS_TYPE_UINT32, &adapter->dev_class);
/* Powered */
value = (adapter->up && !adapter->off_requested) ? TRUE : FALSE;
dict_append_entry(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value);
/* Discoverable */
value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE;
dict_append_entry(&dict, "Discoverable", DBUS_TYPE_BOOLEAN, &value);
/* Pairable */
dict_append_entry(&dict, "Pairable", DBUS_TYPE_BOOLEAN,
&adapter->pairable);
/* DiscoverableTimeout */
dict_append_entry(&dict, "DiscoverableTimeout",
DBUS_TYPE_UINT32, &adapter->discov_timeout);
/* PairableTimeout */
dict_append_entry(&dict, "PairableTimeout",
DBUS_TYPE_UINT32, &adapter->pairable_timeout);
if (adapter->state == STATE_DISCOV)
value = TRUE;
else
value = FALSE;
/* Discovering */
dict_append_entry(&dict, "Discovering", DBUS_TYPE_BOOLEAN, &value);
/* Devices */
devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
for (i = 0, l = adapter->devices; l; l = l->next, i++) {
struct btd_device *dev = l->data;
devices[i] = (char *) device_get_path(dev);
}
dict_append_array(&dict, "Devices", DBUS_TYPE_OBJECT_PATH,
&devices, i);
g_free(devices);
/* UUIDs */
uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);
for (i = 0, list = adapter->services; list; list = list->next) {
sdp_record_t *rec = list->data;
char *uuid;
uuid = bt_uuid2string(&rec->svclass);
if (uuid)
uuids[i++] = uuid;
}
可以看到,这里就是蓝牙获取具体属性的地方了。其中Name代表了蓝牙的名称,Discoverable代表了是否可以被发现,Powered代表了是否是打开状态,Devices代表了上次成功配对的设备,UUIDs代表了上次配对的蓝牙支持的一些协议等。
那么,这些属性又是从哪里获取的呢?利用adb shell连上手机,进入/data/misc/bluetoothd,可以看到好多个MAC地址形式的文件夹,随便进入一个文件夹,可以看到里面有很多的配置文件,而那些属性值就是在这里面读取出来的。比如在profiles里面就保存了上次连接过的设备信息,在config里面保存了蓝牙的名称,可否配对等一些信息。
1.2驱动上报属性改变
在BluetoothEventLoop.java中有方法:onPropertyChanged(),对其解释是:
/**
* Called by native code on a PropertyChanged signal from
* org.bluez.Adapter. This method is also called from
* {@link BluetoothAdapterStateMachine} to set the "Pairable"
* property when Bluetooth is enabled.
* @param propValues a string array containing the key and one or more
* values.
*/
既当从org.bluez.Adapter发出了一个PropertyChanged的信号后,native代码就会调用onPropertyChanged()方法。
if (name.equals("Name")) {
.
} else if (name.equals("Pairable") || name.equals("Discoverable")) {
.
.
} else if (name.equals("Discovering")) {
.
} else if (name.equals("Devices") || name.equals("UUIDs")) {
String value = null;
int len = Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = new StringBuilder();
for (int i = 2; i < propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
value = str.toString();
}
String adapterObjectPath = adapterProperties.getObjectPath();
if ((value != null) && name.equals("UUIDs")) {
adapterProperties.setProperty(name, value);
updateBTState(value);
} else if ((value != null) && (value.startsWith(adapterObjectPath))) {
// Devices Prop expect value starts with obj path
adapterProperties.setProperty(name, value);
}
} else if (name.equals("Powered")) {
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,
propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));
} else if (name.equals("DiscoverableTimeout")) {
adapterProperties.setProperty(name, propValues[1]);
} else if (name.equals("Class")) {
adapterProperties.setProperty(name, propValues[1]);
}
可以看到,当蓝牙的属性值有了改变以后,就会立即反映到BluetoothAdapterProperties.java的变量mPropertiesMap中去,同时会发送相应的事件广播,来通知关注该属性改变的程序。
当获取蓝牙属性不正确时会导致一些问题,而获取蓝牙属性不正确很大的一个原因就是因为蓝牙的MAC地址没有固定,导致每次打开蓝牙时,MAC地址都不同。而在上面我们看到过,每个MAC地址代表的文件里面都储存了蓝牙的相关属性信息,当重新打开蓝牙,MAC地址和上次不同时,就会导致读取不到上次的一些属性信息,从而导致错误。
那么怎么检查MAC地址是不是固定了呢,一个比较简单的方法是连上手机后用ADB SHELL命令进入/data/misc/bluetoothd文件夹观看是否有多个MAC地址形式的文件夹,如果有,则说明MAC地址有可能是不固定的。
其实,还有一个比较简便的方法,通过命令可以看出:
进入/data/misc/bluetoothd下输入命令:hcitool dev 如下:
root@android:/data/misc/bluetoothd # hcitool dev
hcitool dev
Devices:
hci0 00:A0:C6:45:86:75
hci0后面跟的就是当前蓝牙的MAC地址,然后关闭蓝牙,再打开,再执行一次上面的命令,看蓝牙地址是否一样,如不一样,则说明MAC地址是没有固定的,那用工具将其地址写好就可以了。