接上篇打開藍芽繼續,來一起看下藍芽搜索的流程,觸發藍芽搜索的條件形式上有兩種,一是在藍芽設置界面開啟藍芽會直接開始搜索,另一個是先打開藍芽開關在進入藍芽設置界面也會觸發搜索,也可能還有其它觸發方式,但最後都要來到BluetoothSettngs.java的startScanning(),我們分析的起點也從這裡開始,起步代碼如下
- private void updateContent(int bluetoothState, boolean scanState) {
- if (numberOfPairedDevices == 0) {
- preferenceScreen.removePreference(mPairedDevicesCategory);
- if (scanState == true) {
- mActivityStarted = false;
- startScanning();
- } else<span style="font-family: Arial, Helvetica, sans-serif;"> ........</span>
- }
- private void startScanning() {
- if (!mAvailableDevicesCategoryIsPresent) {
- getPreferenceScreen().addPreference(mAvailableDevicesCategory);
- }
- mLocalAdapter.startScanning(true);
- }
其實在這裡藍芽搜索和打開流程是結構上是一致的,利用LocalBluetoothAdapter.java過渡到BluetoothAdapter.java再跳轉至AdapterService.java要稍微留意下的是在這個過渡中startScaning()方法變成了startDiscovery()方法,看下代碼:packages/apps/Settings/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java
- void startScanning(boolean force) {
- if (!mAdapter.isDiscovering()) {
- if (!force) {
-
-
- if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
- return;
- }
-
- A2dpProfile a2dp = mProfileManager.getA2dpProfile();
- if (a2dp != null && a2dp.isA2dpPlaying()) {
- return;
- }
- }
-
- if (mAdapter.startDiscovery()) {
- mLastScan = System.currentTimeMillis();
- }
- }
BluetoothAdapter.java的那一段,路徑 /frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java
- public boolean startDiscovery() {
- .............................
- AdapterService service = getService();
- if (service == null) return false;
- return service.startDiscovery();
- }
這個service代碼寫得很明白AdapterService,轉了一圈從framework又回到packages了,
下面的代碼路逕自然是 :packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java,
- boolean startDiscovery() {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH ADMIN permission");
-
- return startDiscoveryNative();
- }
和打開藍芽根本就是一個套路,上面的流程略過一小步,很簡單的不寫了,下面要怎麼走,估計大家也都猜到了,JNI應該出場了,
路徑:/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
- static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
- ALOGV("%s:",__FUNCTION__);
-
- jboolean result = JNI_FALSE;
- if (!sBluetoothInterface) return result;
-
- int ret = sBluetoothInterface->start_discovery();
- result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
- return result;
- }
在下面要去哪?稍微要動下腦筋,不過我們在上一篇
android -- 藍芽 bluetooth (二) 打開藍芽已經說過怎麼找了,注意android.mk文件,先找頭文件,再找對應的實現C文件代碼。就是現在回顧下,藍芽打開和搜索的代碼流程我們都看了,跳轉都是一個套路,settings界面發起,LocalBluetoothAdapter.java過渡,去framework的轉轉(BluetoothAdapter.java)後回到packages的AdapterService.java,再走JNI來的external。流程就是這樣的,相信類似的功能跳轉(比如藍芽配對,關閉藍芽,停止掃瞄這些)大家都應該熟悉了,後面再有類似的功能就寫函數名一筆帶過了,還有這裡要注意的就是這個start_discovery()實現代碼的尋找,留意mk文件就是了,不複雜。小結結束,繼續看代碼 路徑:/external/bluetooth/bluedroid/btif/src/bluetooth.c
- static int start_discovery(void)
- {
-
- if (interface_ready() == FALSE)
- return BT_STATUS_NOT_READY;
-
- return btif_dm_start_discovery();
- }
下面代碼直接跳轉就可以找到,路徑external/bluetooth/bluedroid/btif/src/btif_dm.c
這個代碼有點多,不過裡面的信息也很多,所以連註釋也一起保留的貼出來了,藍芽的搜索實現並沒有像藍芽打開那樣交由vendor廠商實現,在這裡已經寫出來了,仔細看下那些#if和#else,都是一些查詢條件的調置,#if (BLE_INCLUDED == TRUE) 這個應該就google為藍芽4.0 LE作的準備了,也算是今年google I/O大會上宣佈即將支持藍芽4.0低能耗版一個佐證吧,對於代碼裡面那些字元串的含義看這裡好了external/bluetooth/bluedroid/bta/include/bta_api.h,一個頭文件,大部分字元串和結構體的定義都在這了,多少還有些註釋。
- bt_status_t btif_dm_start_discovery(void)
- {
- tBTA_DM_INQ inq_params;
- tBTA_SERVICE_MASK services = 0;
-
- BTIF_TRACE_EVENT1("%s", __FUNCTION__);
-
-
-
- #if (BLE_INCLUDED == TRUE)
- inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;
- #else
- inq_params.mode = BTA_DM_GENERAL_INQUIRY;
- #endif
- inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;
-
- inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;
- inq_params.report_dup = TRUE;
-
- inq_params.filter_type = BTA_DM_INQ_CLR;
-
-
-
- btif_dm_inquiry_in_progress = FALSE;
-
- BTA_DmSearch(&inq_params, services, bte_search_devices_evt);
-
- return BT_STATUS_SUCCESS;
- }
BTA_DmSearch()方法是看起來是要搜索了,不過裡面這個傢伙bte_search_devices_evt才是真正幹活的主力,所以我們先看它,在這個函數里
- static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data) {
- UINT16 param_len = 0;
-
- if (p_data)
- param_len += sizeof(tBTA_DM_SEARCH);
-
- switch (event)
- {
- case BTA_DM_INQ_RES_EVT:
- {
- if (p_data->inq_res.p_eir)
- param_len += HCI_EXT_INQ_RESPONSE_LEN;
- }
- break;
-
- case BTA_DM_DISC_RES_EVT:
- {
- if (p_data->disc_res.raw_data_size && p_data->disc_res.p_raw_data)
- param_len += p_data->disc_res.raw_data_size;
- }
- break;
- }
- BTIF_TRACE_DEBUG3("%s event=%s param_len=%d", __FUNCTION__, dump_dm_search_event(event), param_len);
-
-
- if (event == BTA_DM_INQ_RES_EVT)
- p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);
-
- btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len,
- (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);
- }
在上面的這個函數里又有這個bte_search_devices_evt,在它裡我們能看一個 HAL_CBACK,這是要往回發消息了,看下這個函數的全貌,說是全貌,不過還是只貼出一個case分支,太長了,大家還是自行還源碼吧。到這裡已經可以知道掃瞄到藍芽設備的mac地址和設備名,那個bdcpy函數就是在解析mac地址,有了這些,藍芽搜索是到應該在界面展示成果的時候了,開始回調,忘記代碼路徑了,這個函數都在這個文件裡: /external/bluetooth/bluedroid/btif/src/btif_dm.c
- static void btif_dm_search_devices_evt (UINT16 event, char *p_param)
-
- tBTA_DM_SEARCH *p_search_data;
- BTIF_TRACE_EVENT2("%s event=%s", __FUNCTION__, dump_dm_search_event(event));
-
- switch (event)
- {
- case BTA_DM_DISC_RES_EVT:
- {
- p_search_data = (tBTA_DM_SEARCH *)p_param;
-
- if (strlen((const char *) p_search_data->disc_res.bd_name))
- {
- bt_property_t properties[1];
- bt_bdaddr_t bdaddr;
- bt_status_t status;
-
- properties[0].type = BT_PROPERTY_BDNAME;
- properties[0].val = p_search_data->disc_res.bd_name;
- properties[0].len = strlen((char *)p_search_data->disc_res.bd_name);
- bdcpy(bdaddr.address, p_search_data->disc_res.bd_addr);
-
- status = btif_storage_set_remote_device_property(&bdaddr, &properties[0]);
- ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device property", status);
- HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,
- status, &bdaddr, 1, properties);
- }
-
- }
- break;
一小段log,下面的文字就在上面的函數里打出來的,即便上面的寫的函數沒有,肯定也在附近了。
05-30 13:52:14.890 1578 2612 D bt-btif : bte_search_devices_evt event=BTA_DM_INQ_RES_EVT param_len=524
05-30 13:52:14.890 1578 2612 D bt-btif : search_devices_copy_cb: event=BTA_DM_INQ_RES_EVT
05-30 13:52:14.890 1578 2584 I bt-btif : btif_dm_search_devices_evt event=BTA_DM_INQ_RES_EVT
05-30 13:52:14.890 1578 2584 D bt-btif : btif_dm_search_devices_evt() ec:89:f5:ba:fb:03 device_type = 0x1
當然回過頭我們還要看下那個BTA_DmSearch(),看它的實現,更應該是起消息發送的作用,代碼在/external/bluetooth/bluedroid/bta/dm/bta_dm_api.c,這個函數具體流程並沒有看多少,當工具方法看了,有時間看看還是沒壞處的。
- void BTA_DmSearch(tBTA_DM_INQ *p_dm_inq, tBTA_SERVICE_MASK services, tBTA_DM_SEARCH_CBACK *p_cback)
- { tBTA_DM_API_SEARCH *p_msg;
- if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)
- {
- memset(p_msg, 0, sizeof(tBTA_DM_API_SEARCH));
-
- p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;
- memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));
- p_msg->services = services;
- p_msg->p_cback = p_cback;
- p_msg->rs_res = BTA_DM_RS_NONE;
- bta_sys_sendmsg(p_msg);
- }
- }
看了上面方法後我們 要回去了看看,代碼通過JNI下來的,回去也是看JNI的回調方法
- method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
deviceFoundCallback方法最後會來java層的/packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java
- void deviceFoundCallback(byte[] address) {
-
-
- BluetoothDevice device = getDevice(address);
- debugLog("deviceFoundCallback: Remote Address is:" + device);
- DeviceProperties deviceProp = getDeviceProperties(device);
- if (deviceProp == null) {
- errorLog("Device Properties is null for Device:" + device);
- return;
- }
-
- Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothDevice.EXTRA_CLASS,
- new BluetoothClass(Integer.valueOf(deviceProp.mBluetoothClass)));
- intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
- intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
-
- mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
- }
到這裡就是給界面發廣播,應用層收到廣播顯示出來,通過這個handle,這個handle可以在BluetoothEventManager.java的建構子里找到,
- addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device = intent
- .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
- Handler handler = mHandlerMap.get(action);
- if (handler != null) {
- handler.onReceive(context, intent, device);
- }
- }
- };
這裡handle對應要看DeviceFoundHandler,也就是下面貼出來的代碼,
- private class DeviceFoundHandler implements Handler {
- public void onReceive(Context context, Intent intent,
- BluetoothDevice device) {
- ........................
-
-
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
- if (cachedDevice == null) {
- cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
- Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
- + cachedDevice);
-
- dispatchDeviceAdded(cachedDevice);
- }
- ......................
- }
- }
在if語句中dispatchDeviceAdded()向界面分發消息,最後處理消息的地方在這裡,已經到settings應用裡了
- public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
- if (mDevicePreferenceMap.get(cachedDevice) != null) {
- return;
- }
-
-
- if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
-
- if (mFilter.matches(cachedDevice.getDevice())) {
- createDevicePreference(cachedDevice);
- }
- }
上面代碼中最後一個分支就是界面顯示要做的事了,從settings界面開始再到settings界面顯示出搜索到藍芽結束,後面的代碼不再寫了,本文關心的東東到此結束。
- void createDevicePreference(CachedBluetoothDevice cachedDevice) {
- BluetoothDevicePreference preference = new BluetoothDevicePreference(
- getActivity(), cachedDevice);
-
- initDevicePreference(preference);
- mDeviceListGroup.addPreference(preference);
- mDevicePreferenceMap.put(cachedDevice, preference);
- }
到目前為止,包括前面的打開流程分析,還僅是針對代碼流程做的分析,對於藍芽協議方面東西還沒有涉及,比如藍芽是如何發現其它藍芽設備,這個流程究竟是怎麼工作還不是很清楚,後續會儘量關注這些問題,估計看起來就沒那麼容易,歡迎有經驗的朋友指點一二,當然對於本文不足,歡迎拍磚討論。分享是快樂的,謝謝!