上文我們詳細介紹了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
android:name=".bluetooth.BluetoothDiscoveryReceiver">
<intent-filter>
<action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
這個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上的小圈圈動起來了。
若您覺得該文章對您有幫助,請在下面用鼠標輕輕按一下“頂”,哈哈~~·