Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [Android源碼分析]藍牙搜索過程分析

[Android源碼分析]藍牙搜索過程分析

編輯:關於Android編程



在完成打開藍牙的分析之後,我們就正式進入到藍牙使用的階段了。毫無疑問,我們第一個對藍牙的操作當然就是掃描設備了。那就是這樣一個點擊“掃描設備”究竟干了些什麼,曉東和大家來仔細分析一下。

1、掃描設備按鍵的處理

代碼的實現看起來很清晰,

 

 

 

 @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ID_SCAN:
                //點擊搜索,首先檢查是否打開BT
                if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
			//開始掃描
                    startScanning();
                }
                return true;
	所以,就是調用	startScanning來進行真正的掃描操作。該函數的代碼實現如下:
    private void startScanning() {
        //這裡就是若是開始沒有搜索過,第一次搜索需要顯示那個搜索到的設備的那個組的ui
        if (!mAvailableDevicesCategoryIsPresent) {
            getPreferenceScreen().addPreference(mAvailableDevicesCategory);
        }

        //這裡的true,就是強制掃描,強制掃描和不強制掃描的差別,我們後面來看
        mLocalAdapter.startScanning(true);
    }
    void startScanning(boolean force) {
        // Only start if we're not already scanning
        //先檢查是否正在掃描。
        if (!mAdapter.isDiscovering()) {
            if (!force) {
                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
                // unless forced
                //不是強制掃描,則5分鐘之內再次搜索不響應                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
                    return;
                }

                // If we are playing music, don't scan unless forced.
                //若是在聽音樂,也不會掃描
                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
                if (a2dp != null && a2dp.isA2dpPlaying()) {
                    return;
                }
            }

                //開始掃描,並得到掃描的時間點
            if (mAdapter.startDiscovery()) {
                mLastScan = System.currentTimeMillis();
            }
        }

 

這裡有人就會問題,不強制掃描一般在什麼情況下發生啊,一個比較常見的情形就是比如我們選擇一個文件進行發送,然後選擇藍牙,就會跳出一系列的藍牙設備,假如你用得多的話,就會發現這裡有時會去掃描,有時則不會。這個時候其實就是用的不強制掃描,所以會出現不進行真正掃描的情況。

 

2、framework層和jni層中的startDiscovery的處理

 

在framework層中,這個函數的處理還是比較簡單的:

 

  public synchronized boolean startDiscovery() {
        //首先檢查是否有權限
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                "Need BLUETOOTH_ADMIN permission");
        //檢查是否已經enable bt
        if (!isEnabledInternal()) return false;

        return startDiscoveryNative();
}
整個這個過程除了權限的檢查和bt是否打開的檢查,並沒有做別的事情。所以,我們可以直接去看jni層的native函數即可。
static jboolean startDiscoveryNative(JNIEnv *env, jobject object) {
    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH
    DBusMessage *msg = NULL;
    DBusMessage *reply = NULL;
    DBusError err;
    const char *name;
    jboolean ret = JNI_FALSE;

    native_data_t *nat = get_native_data(env, object);
    if (nat == NULL) {
        goto done;
    }

    dbus_error_init(&err);

        //就是向bluez調用這個函數
    /* Compose the command */
    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
                                       get_adapter_path(env, object),
                                       DBUS_AD

 

所以,還是蠻清楚的,就是調用bluez中的StartDiscovery method,我們去bluez中看看就知道了。

 

3、bluez中StartDiscovery的method分析

 

在bluez的adapter.c中,我們可以很清楚地看到:

 

static GDBusMethodTable adapter_methods[] = {
……
        { "StartDiscovery",     "",     "",     adapter_start_discovery },

 

因此StartDiscovery對應的method函數就是adapter_start_discovery了,我們直接去看該函數即可:

 

//bluez中開始掃描的函數
static DBusMessage *adapter_start_discovery(DBusConnection *conn,
                                                DBusMessage *msg, void *data)
{
        struct session_req *req;
        struct btd_adapter *adapter = data;
        const char *sender = dbus_message_get_sender(msg);
        int err;

        //同樣的,首先要檢查adapter是否已經up
        if (!adapter->up)
                return btd_error_not_ready(msg);

        //看是否有disc的req在
        req = find_session(adapter->disc_sessions, sender);
        if (req) {
                session_ref(req);
                return dbus_message_new_method_return(msg);
        }

        //看是否有掃描的session在,若是有就直接return了
        if (adapter->disc_sessions)
                goto done;

        //把已經found的device清空了
        g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
        g_slist_free(adapter->found_devices);
        adapter->found_devices = NULL;
        //out-of-order的設備也清空了
        g_slist_free(adapter->oor_devices);
        adapter->oor_devices = NULL;

        //開始discovery,見3.1
        err = start_discovery(adapter);
        if (err < 0 && err != -EINPROGRESS)
                return btd_error_failed(msg, strerror(-err));

done:
        req = create_session(adapter, conn, msg, 0,
                                session_owner_exit);
	//加入到disc session
        adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);

        return dbus_message_new_method_return(msg);
}

 

3.1 bluez中的start_discovery

 

這個函數會處理一些殘余情況,就是把一些上次剩余的情況先干掉,然後再真正去發送對應的cmd

 

static int start_discovery(struct btd_adapter *adapter)
{
        /* Do not start if suspended */
        //suspended的狀態也不進行discovery
        if (adapter->state == STATE_SUSPENDED)
                return 0;

        /* Postpone discovery if still resolving names */
        //若是在resolve name也不去discover,所以其實上層ui也是在resolve name之後才可以再次掃描的,若是我們需要修改,這裡也是要修改的。
        if (adapter->state == STATE_RESOLVNAME)
                return -EINPROGRESS;

        //還沒有name request就直接cancel掉了
        pending_remote_name_cancel(adapter);

        //就是發送inquiry的cmd
        return adapter_ops->start_discovery(adapter->dev_id);
}

 

我們到hciops中就可以看到:

 

static struct btd_adapter_ops hci_ops = {
……
        .start_discovery = hciops_start_discovery,

 

所以,上面adapter_ops->start_discovery所調用的就是hciops_start_discovery了。

 

static int hciops_start_discovery(int index)
{
        int adapter_type = get_adapter_type(index);

        switch (adapter_type) {
        case BR_EDR_LE:
                return hciops_start_inquiry(index, LENGTH_BR_LE_INQ);
        case BR_EDR:
                //bredr,就是發送inquiry的cmd
                return hciops_start_inquiry(index, LENGTH_BR_INQ);
        case LE_ONLY:
                return hciops_start_scanning(index, TIMEOUT_LE_SCAN);
        default:
                return -EINVAL;
        }
}

 

這裡會根據不同的類型來做發送不同的cmd,我們現在已BREDR的設備為例來介紹。所以就看hciops_start_inquiry(index, LENGTH_BR_INQ);了

 

 

static int hciops_start_inquiry(int index, uint8_t length)
{
        struct dev_info *dev = &devs[index];
        uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
        inquiry_cp inq_cp;

        DBG("hci%d length %u", index, length);

        memset(&inq_cp, 0, sizeof(inq_cp));
        memcpy(&inq_cp.lap, lap, 3);
        inq_cp.length = length;
        inq_cp.num_rsp = 0x00;

        //發送inquiry的cmd,這裡就是根據spec來進行組包發送了,具體的inquiry cmd格式見下面4
        if (hci_send_cmd(dev->sk, OGF_LINK_CTL,
                        OCF_INQUIRY, INQUIRY_CP_SIZE, &inq_cp) < 0)
                return -errno;

        return 0;
}

 

至此,到這邊我們inquiry的command就發送給底層的bt controller,那再這之後會ui上何時開始顯示搜索的圈圈,何時跳出掃描到的設備,又是何時結束掃描的,我們將在後面的文章中詳細的給大家一一分析。

 




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

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved