編輯:關於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上何時開始顯示搜索的圈圈,何時跳出掃描到的設備,又是何時結束掃描的,我們將在後面的文章中詳細的給大家一一分析。
若您覺得該文章對您有幫助,請在下面用鼠標輕輕按一下“頂”,哈哈~~·
今天同事在工作中碰到一個問題, 就是EditText中的文字在設定大小後, Hint文本由於太長導致在EditText中無法完整的顯示, 所以問有沒有單獨設置Hint文本
??上一篇文章,我們主要分析了Activity的正常情況下生命周期及其方法,本篇主要涉及內容為Activity的異常情況下的生命周期。Activity異常生命周期??異常
Agenda:一張圖看Camera2框架類圖 CameraService啟動 ICameraService.cpp Camera2Client.h與Camera2Clie
前些天搞了個系統的下拉刷新跟上拉加載,由於效果一般所以才會有了今天這篇博文對於大多數的碼農來說,能弄出些自己感興趣的好東西還是比較開心的。--package com.ex