[Android源碼分析]藍牙搜索過程中你所不知道的小細節

在上文中,我們說到藍牙搜索並沒有在收到inquiry complete的命令後就立即結束,而是會繼續發送remote name request的command去得到對方的名字,本文就來和大家一起詳細分析一下這些操作的流程。


10remote name request command的發送

       Remote name request從名字上我們就可以很清晰地看到,它的主要作用就是得到對端設備的名字信息。我們來從spec上看一下,這個command的格式:


命令的參數還是比較清晰的,我們在之前都已經講過了,就不重複了。我們來看一下,他會產生哪些event。在spec中這樣描述的:

When the BR/EDRController receives the Remote_Name_Request command,the BR/EDR Controllershall send the Command Status event to the Host.When the Link Managerhas completed the LMP messages to obtain the remote host supportedfeatures, if present, the BR/EDR Controller on the local BR/EDR Controllershall send a Remote Host Supported Features Notification event. When the LinkManager has completed the LMP messages to obtain the remote name, theBR/EDR Controller on the local BR/EDR Controller shall send a Remote Name RequestComplete event to the Host. If the remote host supported features page ispresent, the Remote Host Supported Features Notification

  event shall be sentbefore the Remote Name Request Complete event. If not, only the RemoteName Request Complete event shall be sent.

    大概的意思就是,首先會產生一個command status event,這個event我們在之前也看過,他除了inquirycommand會做處理外,其他的command並不會做特殊的處理。所以我們這裏就不再看了哦。若是,我們沒有和對方建立連接,也就是說沒有ACL Connection在,那麼會先去得到對端的supported features,然後產生一個Remote Host Supported Features Notificationevent。若是已經有了ACLlink,並且在之前已經得到過了remote name,則直接產生一個remote name request complete event即可。假如,我們正在建立的連接的過程中,那仍然需要先產生Remote Host Supported Features Notificationevent,然後產生RemoteName Request Completeevent。其他情況就直接產生Remote Name Request Complete event就好了。用幾張圖來更清晰地表達這個流程如下:


這裏就是發送command,然後產生command statusevent上來。



這是在沒有連接的情況下,我們會收到兩個event,一個是remote host supported features notificationevent,一個是remotename request completeevent。在有連接的情況下就如下圖了,這個過程是可選的其實你可以理解,在建立連接的過程中,我們已經去得到name過,若是這種情況,我們直接把名字告訴host就可以了,而不需要再去和遠端交互得到名字信息了。還是比較簡單的吧,嘻嘻~~


下面我們就來看這個兩個event吧,一個是remote host supported features notificationevent,一個是remotename request completeevent

11 remote hostsupported features notification event的分析

這個event總得來說還是比較簡單的,他的格式如下所示:


總得來說就是兩個參數,一個是bdaddr,一個是supported features,具體各個位表示的features的意思我就不多說了,大家自己查spec就可以知道了。我們來看一下代碼:

static inline void remote_features_notify(int index, void *ptr)
{
        struct dev_info *dev = &devs[index];
        evt_remote_host_features_notify *evt = ptr;

        //根據features來設置legacy的值爲false或者true
        if (evt->features[0] & 0x01)
                btd_event_set_legacy_pairing(&dev->bdaddr, &evt->bdaddr,
                                                                        FALSE);
        else
                btd_event_set_legacy_pairing(&dev->bdaddr, &evt->bdaddr,
                                                                        TRUE);

        //把相應的內容保存到features文件中
        write_features_info(&dev->bdaddr, &evt->bdaddr, NULL, evt->features);
}

就做了兩件事情,一個是根據features賦值legacy,一個是保存相關的內容到features文件中。目前來看沒有什麼大的動作,我們暫時就分析到這裏。

12Remote Name request Completeevent分析

         這個event用來表示remotename request的結束。他在spec中的表示如下:


我們可以看到,除了常規的statusbdaddr外,他還包括了remotename這個參數,他就是用來表示我們想要的user friendlyname。在spec中明確規定它的最大長度是248byte,不能超過。

         Android中對這個event的處理代碼如下:


static inline void remote_name_information(int index, void *ptr)
{
        struct dev_info *dev = &devs[index];
        evt_remote_name_req_complete *evt = ptr;
        char name[MAX_NAME_LENGTH + 1];

        DBG("hci%d status %u", index, evt->status);

        memset(name, 0, sizeof(name));

        //沒有問題,把evt name拷貝到name中
        if (!evt->status)
                memcpy(name, evt->name, MAX_NAME_LENGTH);

        btd_event_remote_name(&dev->bdaddr, &evt->bdaddr, evt->status, name);
}

void btd_event_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status,
                                char *name)
{
        struct btd_adapter *adapter;
        char srcaddr[18], dstaddr[18];
        struct btd_device *device;
        struct remote_dev_info match, *dev_info;

        if (status == 0) {
                //看是否是uft8格式的
                if (!g_utf8_validate(name, -1, NULL)) {
                        int i;
                        //把non ascii字符用空格替代

                        /* Assume ASCII, and replace all non-ASCII with
                         * spaces */
                        for (i = 0; name[i] != '\0'; i++) {
                                if (!isascii(name[i]))
                                        name[i] = ' ';
                        }
                        /* Remove leading and trailing whitespace characters */
                        g_strstrip(name);
                }

                //寫到names文件中
                write_device_name(local, peer, name);
        }
        //得到對應的device
        if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE))
                return;

        ba2str(local, srcaddr);
        ba2str(peer, dstaddr);

        if (status != 0)
                goto proceed;

        bacpy(&match.bdaddr, peer);
        match.name_status = NAME_ANY;

        //賦值到device info的name變量中
        dev_info = adapter_search_found_devices(adapter, &match);
        if (dev_info) {
                g_free(dev_info->name);
                dev_info->name = g_strdup(name);
                //同樣會通知上層,device found,device info改變,通知上層
                adapter_emit_device_found(adapter, dev_info);
        }

        //向上層通知name和alias的property change,詳細見12.1
        if (device)
                device_set_name(device, name);

proceed:
        /* remove from remote name request list */
        //把這個從found device列表中remove
        adapter_remove_found_device(adapter, peer);

        /* check if there is more devices to request names */
        //看是否還有設備需要resolve name,若是有,繼續這個循環,這裏return,若是沒有,就設爲idle了
        if (adapter_resolve_names(adapter) == 0)
                return;

        //沒有就設爲idle了
        adapter_set_state(adapter, STATE_IDLE);
}

12.1 device_set_name的分析

         這個函數其實蠻簡單的,就是像上層回報namealias兩個property change

void device_set_name(struct btd_device *device, const char *name)
{
        DBusConnection *conn = get_dbus_connection();

        if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0)
                return;

        strncpy(device->name, name, MAX_NAME_LENGTH);
		//向上層回報Name的property change的signal
        emit_property_changed(conn, device->path,
                                DEVICE_INTERFACE, "Name",
                                DBUS_TYPE_STRING, &name);

        if (device->alias != NULL)
                return;
		//向上層回報Alias的property change的signal
        emit_property_changed(conn, device->path,
                                DEVICE_INTERFACE, "Alias",
                                DBUS_TYPE_STRING, &name);
}

12.2 framework層對Namedeviceproperty change的處理

  if (name.equals("Name")) {
            mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
                //發送ACTION_NAME_CHANGED的broadcast
            Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
            intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
            mContext.sendBroadcast(intent, BLUETOOTH_PERM);

對於這個broadcast的處理如下:

addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());

    private class NameChangedHandler implements Handler {
        public void onReceive(Context context, Intent intent,
                BluetoothDevice device) {
        //更新名字
            mDeviceManager.onDeviceNameUpdated(device);
        }
}
    public void onDeviceNameUpdated(BluetoothDevice device) {
        //找到對應的設備
        CachedBluetoothDevice cachedDevice = findDevice(device);
        if (cachedDevice != null) {
                //更新名字
            cachedDevice.refreshName();
        }
    }
    void refreshName() {
        //得到名字
        fetchName();
        //改變名字的顯示 
        dispatchAttributesChanged();
    }

    private void fetchName() {
        //注意這裏先得到的alias名字,其實寫的並不合理,會有一些隱藏的問題在裏面
        mName = mDevice.getAliasName();

        //若是empty,就得到address,也就是顯示地址了
        if (TextUtils.isEmpty(mName)) {
            mName = mDevice.getAddress();
            if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
        }
    }

所以,還是很清晰吧,就是把ui上的名字改掉。只是在名字的獲取上還是有一些潛在的問題。

至於framework中對aliasdeviceproperty change的處理就很簡單了,就是修改了aliasproperty。就不再詳細寫出了。

13adapterstateidle的分析

    這個就是掃描在bluez層做的最後一件事了,他的代碼實現如下:

case STATE_IDLE:
                update_oor_devices(adapter);

                //就是向上層發出discovering flase的property change
                discov_active = FALSE;
                emit_property_changed(connection, path,
                                        ADAPTER_INTERFACE, "Discovering",
                                        DBUS_TYPE_BOOLEAN, &discov_active);

                //若是還有discover的session的話,就在timeout之後再次開始,這個一般是不會有啦。
                if (adapter_has_discov_sessions(adapter)) {
                        adapter->scheduler_id = g_timeout_add_seconds(
                                                main_opts.discov_interval,
                                                discovery_cb, adapter);
                }
                break;

很清晰,就是向上層發送了一個discoveringfalseproperty change。和開始的discovering trueproperty change是對應的,我們就不詳細分析,大概的操作就是把上層在掃描的圈圈停止掉,把開始掃描的ui返灰去除掉之類的操作。大家對着discovering true的分析看就明白了。

    至此,藍牙掃描從上到下涉及到的內容就已經全部分析完成了哦,你還有什麼疑問嗎?


若您覺得該文章對您有幫助,請在下面用鼠標輕輕按一下“頂”,哈哈~~·





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章