[Android源碼分析]從spec角度來詳細分析inquiry command帶來的影響

上文我們詳細介紹瞭Android是如何通過ui操作到最終發出inquiry command來實現藍牙的掃描功能的。本文曉東將會和大傢一起來看看inquiry command的格式,以及發出這個command後會產生哪些影響。

4、inquiry cmd的格式分析。

在藍牙core spec中明確定義瞭inquirycmd的格式已經返回的event。我們來具體看看:

Inquiry command的格式[1]

針對這個command的參數設置如下:

LAP:在spec上是這樣描述這個參數的:“The reserved LAP addresses are 0x9E8B00-0X9E8B3F,The general LAP is 0x9E8B33.”也就是說,LAP的地址范圍是0x9E8B00-0X9E8B3F,一般而言我們設為0x9E8B33。從上面的代碼我們也可以發現,android中的確使用的是推薦值:uint8_t lap[3] = {0x33, 0x8b, 0x9e };

Inquiry length:這個顧名思義就是掃描的時間長度。上面的LENGTH_BR_INQ的值是0x08,簡單計算一下08*1.28s,大概就是10s瞭,所以,我們從上層才會看到一般的android手機搜索的時間就是10s,若是需要修改,則可以改這邊的參數,當然最長不能超過61.44s瞭。

Num_Responses:就是響應的設備數目,這裡設為0就是不限制搜索到的設備數目。當然一般而言,我們都不會設置搜素到的設備的,呵呵~~

這個command發送下去會產生哪些event呢,spec中也是有明確規定的:

A Command Status event shall be sentfrom the BR/EDR Controller to the Host,when the BR/EDRController has started the Inquiry process. Unless filtered, an Inquiry Resultevent shall be created for each BR/EDR Controller which responds to the Inquirymessage. In addition, multiple BR/EDR Controllers which respond to the Inquiremessage may be combined into the same event. An Inquiry Complete event shall begenerated when the Inquiry process has completed.

總的意思就是說,我們首先要產生一個command status的event,這個event產生後就表示藍牙已經開始掃描設備瞭。然後會有inquiry resultevent會回報上來。需要註意的有可能多個設備在event中回報上來,所以需要繼續解析這個event。在最後,inquiry complete event會上來表示inquiry完成瞭。所以,下面我們就會主要討論這幾個event。

 

5、command statusevent的處理。

在對event的解析函數中是這樣寫的:

 

 switch (eh->evt) {
        case EVT_CMD_STATUS:
                cmd_status(index, ptr);
                break;

 

 

 

所以會調用cmd_status這個函數:

 

static inline void cmd_status(int index, void *ptr)
{
        evt_cmd_status *evt = ptr;
        uint16_t opcode = btohs(evt->opcode);
//很多cmd都會產生這個event,隻是唯獨對inquiry需要做一些特殊的處理
        //若是inquiry的cmd有一個特殊的操作
        if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))
                cs_inquiry_evt(index, evt->status);
}
static inline void cs_inquiry_evt(int index, uint8_t status)
{
        //這裡有個inquiry的錯誤操作
        //原本就是打印一個錯誤的信息,感覺對錯誤的處理還是有所欠缺啊
        if (status) {
                error("Inquiry Failed with status 0x%02x", status);
                return;
        }

        //這裡在ok的情況下,需要設置狀態為inq,主要就是向上層回復discovering的property change
        set_state(index, DISCOV_INQ);
}
Set_State的函數片段:
       case DISCOV_SCAN:               
                //設置adapter的state
                adapter_set_state(adapter, STATE_DISCOV);
                break;
      adapter_set_state的函數片段:
  case STATE_DISCOV:
                //設置discov_active標志
                discov_active = TRUE;
                //向上層回復discovering的property change
                emit_property_changed(connection, path,
                                        ADAPTER_INTERFACE, "Discovering",
                                        DBUS_TYPE_BOOLEAN, &discov_active);
                break;
從上面的spec分析中我們已經知道cmd status就是表示inquiry開始瞭,所以我們有必要通知上層瞭,比如說上層的progress的小圓圈可以轉起來瞭。我們回到上層去看看:
//這個函數位於eventloop中
        } else if (name.equals("Discovering")) {
            Intent intent;
        //收到discovering的property change的處理
            adapterProperties.setProperty(name, propValues[1]);
        //根據這個值來發送對應的broadcast
            if (propValues[1].equals("true")) {
                intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            } else {
                // Stop the discovery.
                mBluetoothService.cancelDiscovery();
                intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            }
            mContext.sendBroadcast(intent, BLUETOOTH_PERM);

 

 

 

所以,framework層對這個的處理就是發送一個ACTION_DISCOVERY_STARTED的broadcast,這樣所有的receiver就可以動起來瞭。

6、ACTION_DISCOVERY_STARTED的receiver分析

從代碼中我們可以看到這個action一共有兩個receiver,一個是靜態註冊的一個是動態註冊是:

6.1 BluetoothDiscoveryReceiver

這個receiver是在settings中的androidmanifest中註冊的:

 

        
            
                
                
                
            
        

這個receiver的具體操作如下:

public final class BluetoothDiscoveryReceiver extends BroadcastReceiver {
    private static final String TAG = "BluetoothDiscoveryReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.v(TAG, "Received: " + action);

        if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) ||
                action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                //就是更新共享空間中的掃描開始和掃描結束的時間
            LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
        }
    }
}

 

 

 

其實就是更新時間戳,事實上,我們在開始掃描的時候有檢查過這個時間戳,這裡也就是那個地方用的。

6.2 ScanningStateChangedHandler

這個receiver是一個handler,他的註冊如下:

 

addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
//處理函數如下
    private class ScanningStateChangedHandler implements Handler {
        private final boolean mStarted;

        ScanningStateChangedHandler(boolean started) {
            mStarted = started;
        }
        public void onReceive(Context context, Intent intent,
                BluetoothDevice device) {
//調用註冊的callback中的onScanningStateChanged函數,具體見6.2.1
            synchronized (mCallbacks) {
                for (BluetoothCallback callback : mCallbacks) {
                    callback.onScanningStateChanged(mStarted);
                }
            }
//這個函數就是把上次掃描到的設備拿清除掉,所以,我們會發現在掃描的開始原來的設備就都不見瞭,具體見6.2.2
            mDeviceManager.onScanningStateChanged(mStarted);
            LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
        }
}

 

 

 

6.2.1 註冊的callback

從代碼中我們可以看到有以下callback:

 

1)DeviceListPreferenceFragment.java
    public void onScanningStateChanged(boolean started) {
        if (started == false) {
	//這個是掃描結束的處理
            removeOutOfRangeDevices();
        }
        updateProgressUi(started);
    }

    private void updateProgressUi(boolean start) {
        //就是那個小圓圈就開始動起來瞭
        if (mDeviceListGroup instanceof ProgressCategory) {
            ((ProgressCategory) mDeviceListGroup).setProgress(start);
        }
    }

 

 

 

6.2.2 mDeviceManager.onScanningStateChanged的分析

 

public synchronized void onScanningStateChanged(boolean started) {
        // If starting a new scan, clear old visibility
        // Iterate in reverse order since devices may be removed.
        //掃描所有的cached的device
        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
                //假如是開始掃描,就是把他不顯示瞭
            if (started) {
                cachedDevice.setVisible(false);
            } else {
                //掃描結束,若是他不是已經配對的,並且是不可見的,我們就把它從cacheddevices中remove掉。
                //這些設備其實就是那些之前掃描到過,但是這次掃描沒有掃描到的設備。
                //沒有開始就把設備remove掉的一個好處就是,若是這次再次掃描到,我們不需要再加入進去
                if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
                        cachedDevice.isVisible() == false) {
                    mCachedDevices.remove(cachedDevice);
                }
            }
        }
    }

 

 

 

至此,command statusevent的處理就全部ok瞭,主要就是向上層匯報說我們開始掃描設備瞭,你可以把ui上上次掃描的設備去除掉,並且把開始掃描的ui上的小圈圈動起來瞭。

 

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



發佈留言