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地址是没有固定的,那用工具将其地址写好就可以了。