zoukankan      html  css  js  c++  java
  • 【转】Android bluetooth介绍(三): 蓝牙扫描(scan)设备分析

    原文网址:http://blog.csdn.net/xubin341719/article/details/38584469

    关键词:蓝牙blueZ  A2DP、SINK、sink_connect、sink_disconnect、sink_suspend、sink_resume、sink_is_connected、sink_get_properties、AUDIO、DBUS
    版本:基于android4.2之前版本 bluez
    内核:linux/linux3.08
    系统:android/android4.1.3.4
    作者:xubin341719(欢迎转载,请注明作者,请尊重版权谢谢)
    欢迎指正错误,共同学习、共同进步!!
    参考网站:
    http://blog.csdn.net/u011960402/article/details/17216563
    http://www.cnblogs.com/fityme/archive/2013/04/13/3019471.html socket相关
    http://hi.baidu.com/wwwkljoel/item/a35e5745d14e02e6bcf45170 setsockopt

    Android bluetooth介绍(一):基本概念及硬件接口
    Android bluetooth介绍(二): android 蓝牙代码架构及其uart 到rfcomm流程
    Android bluetooth介绍(三): 蓝牙扫描(scan)设备分析
    Android bluetooth介绍(四): a2dp connect流程分析


    一、蓝牙扫描常用的方法:
             蓝牙扫描的可以通过两种途径实现:命令行hciitool扫描;Android界面触发,通过JNI、DUBS下发命令。
    1、  命令行hciitool扫描(这部分通过Linux命令操作,跟android没有关系)
    通过bluez的tool发送扫描命令,如:hcitoool scan
    adb shell 下#hcitool  scan扫描结果

    Hcitool扫描逻辑如下所示:

    2、Android界面触发,通过JNI、DUBS下发命令:通过android界面点击搜索设备

    应用扫描触发逻辑流程:自上而下三种颜色,分别代表应用部分、JNI部分、linux blueZ部分。
    二、Hcitool触发逻辑分析
    1、hcitool这部分代码比较简单,实现函数
    idh.codeexternalluetoothluez oolshcitool.c代码大致流程如下:

    通过所带的参数,找到cmd_scan,进入hci_inquriy。这个函数中创建一个BTPROTO_HCI的socket,通过ioctlHCINQUIRY向内核读取数据,保存返回信息。

    2、内核层逻辑:
    当然IOCTL只是其中一项。
    idh.codekernel etluetooth hci_sock.c

    [html] view plaincopy
     
    1. static const struct proto_ops hci_sock_ops = {  
    2. …………  
    3.     .ioctl      = hci_sock_ioctl,  
    4.     .poll       = datagram_poll,  
    5.     .listen     = sock_no_listen,  
    6. …………  
    7. };  



    它的流程就是构造查询命令,放入命令队列,调度队列来发送命令,其中hci_send_frame后面会讲解,这里关键是命令的发送和数据的收集是分开的,所以它在里面会放弃2s的调度,以此来等待数据的收集,收集的数据放在hdev->inq_cache里面,我们来看看这个数据是如何取得的,如下图所示:

    入口点hci_rx_work前面已经详细分析过了,这里就不说了,它里面会根据不同的事件类型做不同的处理,通常情况下,扫描都是带信号强度的扫描,所以走的hci_inquiry_result_with_rssi_evt路线,还有其它几种扫描方式,比如:HCI_EV_INQUIRY_RESULT,HCI_EV_EXTENDED_INQUIRY_RESULT等,处理逻辑都差不多的,里面会hci_inquiry_cache_update来把结果放到hdev->discovery链表里面去,供后面的查询;比如前面调用的inquiry_cache_dump函数就可以从这个链表里面把数据取出来,然后copy到用户层;
    三、Android界面触发,通过JNI、DUBS下发命令
    整体流程如下所示:

    (一)、应用部分:
    1、 idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2.   public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,  
    3.       Preference preference) {  
    4.     …………  
    5.       mLocalAdapter.startScanning(true);  
    6.       return true;  
    7.     }  

    2、 idh.codepackagesappsSettingssrccomandroidsettingsluetoothLocalBluetoothAdapter.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1.  private final BluetoothAdapter mAdapter;  
    2.  void startScanning(boolean force) {  
    3.         // Only start if we're not already scanning  
    4.         if (!mAdapter.isDiscovering()) {  
    5.             if (!force) {  
    6.                 // Don't scan more than frequently than SCAN_EXPIRATION_MS,  
    7.                 // unless forced  
    8.                 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {  
    9.                     return;  
    10.                 }  
    11.                 // If we are playing music, don't scan unless forced.  
    12.                 A2dpProfile a2dp = mProfileManager.getA2dpProfile();  
    13.                 if (a2dp != null && a2dp.isA2dpPlaying()) {  
    14.                     return;  
    15.                 }  
    16.             }  
    17.             if (mAdapter.startDiscovery()) {  
    18.                 mLastScan = System.currentTimeMillis();  
    19.             }  
    20.         }  
    21. }   

    3、idh.codeframeworksasecorejavaandroidluetoothBluetoothAdapter.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public boolean startDiscovery() {  
    2.     if (getState() != STATE_ON) return false;  
    3.     try {  
    4.         return mService.startDiscovery();  
    5.     } catch (RemoteException e) {Log.e(TAG, "", e);}  
    6.     return false;  
    7. }  

    4、JNI函数的调用idh.codeframeworksasecorejavaandroidserverBluetoothService.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private native boolean startDiscoveryNative();//Native函数声明  
    2. public class BluetoothService extends IBluetooth.Stub {  
    3.     private static final String TAG = "BluetoothService";  
    4.     private static final boolean DBG = true;  
    5. …………  
    6. public synchronized boolean startDiscovery() {  
    7. mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,  
    8.         "Need BLUETOOTH_ADMIN permission");  
    9.         if (!isEnabledInternal()) return false;  
    10.         return startDiscoveryNative();  
    11. }  
    12. ………………  
    13. }  

    (二)、JNI部分:
    1、android_server_BluetoothService.cpp中JNI函数的对照表
    idh.codeframeworksasecorejniandroid_server_BluetoothService.cpp

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static JNINativeMethod sMethods[] = {  
    2.      /* name, signature, funcPtr */  
    3.    ………………  
    4.     {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative},  
    5. {"stopDiscoveryNative", "()Z", (void *)stopDiscoveryNative},   
    6. …………  
    7. }  

    2、对应Native函数的实现
    这里面有个知识点DBUS,这个后面我们单独去讲解
    idh.codeframeworksasecorejniandroid_server_BluetoothService.cpp

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #define BLUEZ_DBUS_BASE_IFC       "org.bluez"  
    2. #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"//其实DBUS_ADAPTER_IFACE 也就是 org.bluez.Adapter  
    3. static jboolean startDiscoveryNative(JNIEnv *env, jobject object) {  
    4.   
    5. ………………  
    6.     /* Compose the command */  
    7.     msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,  
    8.                                        get_adapter_path(env, object),  
    9.                                        DBUS_ADAPTER_IFACE, "StartDiscovery");  
    10. …………  
    11. }  
    12. Native函数startDiscoveryNative和字符串StartDiscovery对应。  

    (三)、DBUS部分

    1、DBUS对应方法的实现,这里跟JNI部分比较类似,也是用了函数结构体对应关系。
    idh.codeexternalluetoothluezsrcadapter.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #define ADAPTER_INTERFACE   "org.bluez.Adapter"  
    2. static GDBusMethodTable adapter_methods[] = {  
    3. ………………  
    4.     { "ReleaseSession", "", "", release_session     },  
    5.     { "StartDiscovery", "", "", adapter_start_discovery },  
    6.     { "StopDiscovery",  "", "", adapter_stop_discovery,  
    7.                         G_DBUS_METHOD_FLAG_ASYNC},  
    8. ………………  
    9. }  

    字符StartDiscovery又对应C中的实现函数adapter_start_discovery。
    2、adapter_start_discovery的实现
    idh.codeexternalluetoothluezsrcadapter.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static DBusMessage *adapter_start_discovery(DBusConnection *conn,  
    2.                         DBusMessage *msg, void *data)  
    3. {  
    4. …………  
    5.     err = start_discovery(adapter);  
    6.     if (err 0 && err != -EINPROGRESS)  
    7.         return btd_error_failed(msg, strerror(-err));  
    8.   
    9. done:  
    10.     req = create_session(adapter, conn, msg, 0,  
    11.                 session_owner_exit);  
    12.   
    13.     adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);  
    14.   
    15.     return dbus_message_new_method_return(msg);  
    16. }  

    3、 start_discovery调用
    idh.codeexternalluetoothluezsrcadapter.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. const struct btd_adapter_ops *adapter_ops = NULL;  
    2. static int start_discovery(struct btd_adapter *adapter)  
    3. {  
    4. …………  
    5.     pending_remote_name_cancel(adapter);  
    6.     return adapter_ops->start_discovery(adapter->dev_id);  
    7. }  

    adapter_ops对应结构体btd_adapter_ops中对应函数,如下:上面部分就对应到btd_adapter_ops中的hci_ops结构体。
    4、btd_adapter_ops中的hci_ops结构体
    idh.codeexternalluetoothluezpluginshciops.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static struct btd_adapter_ops hci_ops = {  
    2. …………  
    3.     .set_powered = hciops_set_powered,  
    4.     .set_discoverable = hciops_set_discoverable,  
    5.     .set_pairable = hciops_set_pairable,  
    6.     .set_limited_discoverable = hciops_set_limited_discoverable,  
    7.     .start_discovery = hciops_start_discovery,  
    8.     .stop_discovery = hciops_stop_discovery,  
    9.     ………………  
    10.     .create_bonding = hciops_create_bonding,  
    11.     .cancel_bonding = hciops_cancel_bonding,  
    12.     .read_local_oob_data = hciops_read_local_oob_data,  
    13.     .add_remote_oob_data = hciops_add_remote_oob_data,  
    14.     .remove_remote_oob_data = hciops_remove_remote_oob_data,  
    15.     .set_link_timeout = hciops_set_link_timeout,  
    16.     .retry_authentication = hciops_retry_authentication,  
    17. };  

    5、hciops_start_discovery函数的实现
    idh.codeexternalluetoothluezpluginshciops.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static int hciops_start_discovery(int index)  
    2. {  
    3.     int adapter_type = get_adapter_type(index);  
    4.   
    5.     switch (adapter_type) {  
    6.     case BR_EDR_LE:  
    7.         return hciops_start_inquiry(index, LENGTH_BR_LE_INQ);  
    8.     case BR_EDR: //蓝牙芯片为2.1+EDR的  
    9.         return hciops_start_inquiry(index, LENGTH_BR_INQ);  
    10.     case LE_ONLY:  
    11.         return hciops_start_scanning(index, TIMEOUT_LE_SCAN);  
    12.     default:  
    13.         return -EINVAL;  
    14.     }  
    15. }  

    6、hciops_start_inquiry
    idh.codeexternalluetoothluezpluginshciops.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static int hciops_start_inquiry(int index, uint8_t length)  
    2. {  
    3.     struct dev_info *dev = &devs[index];  
    4.     uint8_t lap[3] = { 0x33, 0x8b, 0x9e };  
    5.     inquiry_cp inq_cp;  
    6.   
    7.     DBG("hci%d length %u", index, length);  
    8.   
    9.     memset(&inq_cp, 0, sizeof(inq_cp));  
    10.     memcpy(&inq_cp.lap, lap, 3);  
    11.     inq_cp.length = length;  
    12.     inq_cp.num_rsp = 0x00;  
    13.   
    14.     if (hci_send_cmd(dev->sk, OGF_LINK_CTL,  
    15.             OCF_INQUIRY, INQUIRY_CP_SIZE, &inq_cp) 0)  
    16.         return -errno;  
    17.   
    18.     return 0;  
    19. }  

    7、idh.codeexternalluetoothluezlibhci.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* HCI functions that require open device  
    2.  * dd - Device descriptor returned by hci_open_dev. */  
    3. dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);  
    4. int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)  
    5. {  
    6. ………………  
    7.     if (plen) {  
    8.         iv[2].iov_base = param;  
    9.         iv[2].iov_len  = plen;  
    10.         ivn = 3;  
    11.     }  
    12.   
    13.     while (writev(dd, iv, ivn) 0) {//writev这里把数据写到socket里面。  
    14.         if (errno == EAGAIN || errno == EINTR)  
    15.             continue;  
    16.         return -1;  
    17.     }  
    18.     return 0;  
    19. }  

    (四)、内核部分:
    1、HCI FILTER的设置
    HCIsocket的类型为BTPROTO_HCI。上层调用setsockopt的时候,触发了内核的hci_sock_setsockopt函数的执行,在这里面设置了socket的filter特性,包括包类型,包括事件类型

    当上层调用setsockopt(sock, SOL_HCI, HCI_FILTER,&flt, sizeof(flt))时,触发相应的内核路径。
    idh.codekernel etluetoothhci_sock.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static const struct proto_ops hci_sock_ops = {  
    2.     .family     = PF_BLUETOOTH,  
    3.     .owner      = THIS_MODULE,  
    4. …………  
    5.     .shutdown   = sock_no_shutdown,  
    6.     .setsockopt = hci_sock_setsockopt,  
    7.     .getsockopt = hci_sock_getsockopt,  
    8.     .connect    = sock_no_connect,  
    9. …………  
    10. };  

    idh.codekernel etluetoothhci_sock.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int len)  
    2. {  
    3. ………………  
    4.     case HCI_FILTER:  
    5.         {  
    6.             struct hci_filter *f = &hci_pi(sk)->filter;  
    7.   
    8.             uf.type_mask = f->type_mask;  
    9.             uf.opcode    = f->opcode;  
    10.             uf.event_mask[0] = *((u32 *) f->event_mask + 0);  
    11.             uf.event_mask[1] = *((u32 *) f->event_mask + 1);  
    12.         }  
    13.     ………………  
    14. }<span style="font-weight: bold;">  
    15. </span>  

    内核这部分就比较统一的数据,通过hci_send_cmd把命令发出去,HCI_FILTER这个地方的处理还没理解,后面补充
    Writev函数通过socket把数据写下去,经过VFS层,调用到内核空间的sendmsg函数。

    (五)、EVENT返回状态

    Controller收到查询命令后,返回一个命令状态
    1、cmd_status
    idh.codeexternalluetoothluezpluginshciops.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1.     switch (eh->evt) {  
    2.     case EVT_CMD_STATUS:  
    3.         cmd_status(index, ptr);  
    4.         break;  
    5. static inline void cmd_status(int index, void *ptr)  
    6. {  
    7.     evt_cmd_status *evt = ptr;  
    8.     uint16_t opcode = btohs(evt->opcode);  
    9.   
    10.     if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))//如果是inquriy做特殊处理;  
    11.         cs_inquiry_evt(index, evt->status);  
    12. }  

    2、cs_inquiry_evt的实现 idh.codeexternalluetoothluezpluginshciops.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static inline void cs_inquiry_evt(int index, uint8_t status)  
    2. {  
    3.     if (status) {//错误信息  
    4.         error("Inquiry Failed with status 0x%02x", status);  
    5.         return;  
    6.     }  
    7.   
    8.     set_state(index, DISCOV_INQ);//设置状态为INQ,向上层回复discoverying的property change  
    9. }  

    3、设置不同的DISCOV 状态 idh.codeexternalluetoothluezpluginshciops.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static void set_state(int index, int state)  
    2. {  
    3.     ………………  
    4.     switch (dev->discov_state) {  
    5.     case DISCOV_HALTED://停止发现;  
    6.         if (adapter_get_state(adapter) == STATE_SUSPENDED)  
    7.             return;  
    8.   
    9.         if (is_resolvname_enabled() &&  
    10.                     adapter_has_discov_sessions(adapter))  
    11.             adapter_set_state(adapter, STATE_RESOLVNAME);  
    12.         else  
    13.             adapter_set_state(adapter, STATE_IDLE);  
    14.         break;  
    15.     case DISCOV_INQ:  
    16.     case DISCOV_SCAN://扫描发现;  
    17.         adapter_set_state(adapter, STATE_DISCOV);  
    18.         break;  
    19.     }  
    20. }  

    4、设置adapter的状态 idh.codeexternalluetoothluezsrcadapter.c

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. idh.codeexternalluetoothluezsrcadapter.c  
    2. #define ADAPTER_INTERFACE   "org.bluez.Adapter"  
    3. void adapter_set_state(struct btd_adapter *adapter, int state)  
    4. {  
    5. …………  
    6.     case STATE_DISCOV:  
    7.         discov_active = TRUE;  
    8. //向上层回复discovering的property change  
    9.         emit_property_changed(connection, path,  
    10.                     ADAPTER_INTERFACE, "Discovering",  
    11.                     DBUS_TYPE_BOOLEAN, &discov_active);  
    12.         break;  
    13. …………  
    14. }  

    emit_property_changed发送PropertyChanged的消息,消息内容为Discovering。通知上层BluetoothEventLoop进行Discovering。
    5、emit_property_changed发送Discovering消息的实现 idh.codeexternalluetoothluezsrcdbus-common.c
    这部分涉及到DBUS内容

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. dbus_bool_t emit_property_changed(DBusConnection *conn,  
    2.                     const char *path,  
    3.                     const char *interface,  
    4.                     const char *name,  
    5.                     int type, void *value)  
    6. {  
    7.     DBusMessage *signal;  
    8.     DBusMessageIter iter;  
    9.     signal = dbus_message_new_signal(path, interface, "PropertyChanged"); // 创建消息对象并标识路径   
    10.     if (!signal) {  
    11.         error("Unable to allocate new %s.PropertyChanged signal",  
    12.                 interface);  
    13.         return FALSE;  
    14.     }  
    15.     dbus_message_iter_init_append(signal, &iter);//把信号相对应的参数压进去  
    16.     dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name);//申请一个首地址,把前面压入的参数传入这个首地址      
    17. append_variant(&iter, type, value);//  
    18.     return g_dbus_send_message(conn, signal);//启动发送调用,并释放发送相关消息信息  
    19. }  

    6、DBUS消息接收的实现 idh.codeframeworksasecorejniandroid_server_BluetoothEventLoop.cpp

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. // Called by dbus during WaitForAndDispatchEventNative()  
    2. static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,  
    3.                                       void *data) {  
    4. …………  
    5. else if (dbus_message_is_signal(msg, "org.bluez.Adapter", "PropertyChanged")) {  
    6.         jobjectArray str_array = parse_adapter_property_change(env, msg);//(1)、对收到消息的解析  
    7.         if (str_array != NULL) {  
    8.             /* Check if bluetoothd has (re)started, if so update the path. */  
    9.             jstring property =(jstring) env->GetObjectArrayElement(str_array, 0);  
    10.             const char *c_property = env->GetStringUTFChars(property, NULL);  
    11.             if (!strncmp(c_property, "Powered", strlen("Powered"))) {  
    12.                 jstring value =  
    13.                     (jstring) env->GetObjectArrayElement(str_array, 1);  
    14.                 const char *c_value = env->GetStringUTFChars(value, NULL);  
    15.                 if (!strncmp(c_value, "true", strlen("true")))  
    16.                     nat->adapter = get_adapter_path(nat->conn);  
    17.                 env->ReleaseStringUTFChars(value, c_value);  
    18.             }  
    19.             env->ReleaseStringUTFChars(property, c_property);  
    20.   
    21.             env->CallVoidMethod(nat->me,  
    22.                               method_onPropertyChanged,//(2)、  
    23. method_onPropertyChanged NATVIE函数的实现  
    24.                               str_array);  
    25.         } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);  
    26.         goto success;  
    27. }  

    (1)、对收到消息的解析 idh.codeframeworksasecorejniandroid_bluetooth_common.cpp 

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg) {  
    2.     return parse_property_change(env, msg, (Properties *) &adapter_properties,  
    3.                     sizeof(adapter_properties) / sizeof(Properties));  
    4. }  

    针对org.bluez.Adapter不同的消息类型
    idh.codeframeworksasecorejniandroid_bluetooth_common.cpp

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static Properties adapter_properties[] = {  
    2.     {"Address", DBUS_TYPE_STRING},  
    3.     {"Name", DBUS_TYPE_STRING},  
    4.     {"Class", DBUS_TYPE_UINT32},  
    5.     {"Powered", DBUS_TYPE_BOOLEAN},  
    6.     {"Discoverable", DBUS_TYPE_BOOLEAN},  
    7.     {"DiscoverableTimeout", DBUS_TYPE_UINT32},  
    8.     {"Pairable", DBUS_TYPE_BOOLEAN},  
    9.     {"PairableTimeout", DBUS_TYPE_UINT32},  
    10.     {"Discovering", DBUS_TYPE_BOOLEAN},  
    11.     {"Devices", DBUS_TYPE_ARRAY},  
    12.     {"UUIDs", DBUS_TYPE_ARRAY},  
    13. };  

    (2)、method_onPropertyChanged NATVIE函数的实现 idh.codeframeworksasecorejniandroid_server_BluetoothEventLoop.cpp

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static void classInitNative(JNIEnv* env, jclass clazz) {  
    2.     ALOGV("%s", __FUNCTION__);  
    3. #ifdef HAVE_BLUETOOTH  
    4.     method_onPropertyChanged = env->GetMethodID(clazz, "onPropertyChanged",  
    5.                                                 "([Ljava/lang/String;)V");  
    6. method_onDevicePropertyChanged = env->GetMethodID(clazz,  
    7.  "onDevicePropertyChanged","(Ljava/lang/String;[Ljava/lang/String;)V");  
    8. …………  
    9. }  

    7、JNI调用onPropertyChanged对应JAVA的实现,在BluetoothEventLoop.java
    idh.codeframeworksasecorejavaandroidserverBluetoothEventLoop.java中

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <pre code_snippet_id="452489" snippet_file_name="blog_20140817_26_3867453" name="code" class="html">   private static native void classInitNative();  
    2. /*package*/ void onPropertyChanged(String[] propValues) {  
    3.  ………………  
    4.         log("Property Changed: " + propValues[0] + " : " + propValues[1]);  
    5.         String name = propValues[0];  
    6.         if (name.equals("Name")) {//获取蓝牙名字;  
    7.             …………  
    8.         } else if (name.equals("Pairable") || name.equals("Discoverable")) {//配对;  
    9.            ………………  
    10.         } else if (name.equals("Discovering")) {//扫描查询;  
    11.             Intent intent;  
    12.             adapterProperties.setProperty(name, propValues[1]);  
    13.             if (propValues[1].equals("true")) {  
    14.                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);  
    15.             } else {  
    16.                 // Stop the discovery.  
    17.                 mBluetoothService.cancelDiscovery();  
    18.                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);  
    19.             }  
    20.             mContext.sendBroadcast(intent, BLUETOOTH_PERM);  
    21.         } else if (name.equals("Devices") || name.equals("UUIDs")) {//Devices、UUID的获取;  
    22.         ………………  
    23.         } else if (name.equals("Powered")) {//蓝牙打开、关闭;  
    24.             mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,  
    25.                 propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));  
    26.         } else if (name.equals("DiscoverableTimeout")) {  
    27.             adapterProperties.setProperty(name, propValues[1]);  
    28.         }  
    29.     } </pre>  

    (1)、看到这份log我们也许会更明白其他功能的由来:

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. D BluetoothEventLoop: Property Changed: Powered : true  
    2. D BluetoothEventLoop: Property Changed: Pairable : true  
    3. D BluetoothEventLoop: Property Changed: Class : 5898764  
    4. D BluetoothEventLoop: Property Changed: Pairable : true  
    5. D BluetoothEventLoop: Property Changed: Discoverable : false  
    6. D BluetoothEventLoop: Property Changed: Discovering : true  
    7. D BluetoothEventLoop: Property Changed: Discovering : false  
    8. D BluetoothEventLoop: Property Changed: Devices : 1  
    9. D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: Connected value: true  
    10. D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: Paired value: true  
    11. D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: UUIDs value: 4  

    (2)、下面我们重点分析Discovering这部分
    idh.codeframeworksasecorejavaandroidserverBluetoothEventLoop.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. else if (name.equals("Discovering")) {  
    2.             Intent intent;  
    3.             adapterProperties.setProperty(name, propValues[1]);  
    4.             if (propValues[1].equals("true")) {//开始扫描  
    5.                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//  
    6.             } else {  
    7.                 // Stop the discovery. //停止扫描  
    8.                 mBluetoothService.cancelDiscovery();  
    9.                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);  
    10.             }  
    11.             mContext.sendBroadcast(intent, BLUETOOTH_PERM);  
    12.         }  
    13. 这样就可以通过broadcast发送ACTION_DISCOVERY_STARTED广播,注册的receiver来响应了。  

    8、ACTION_DISCOVERY_STARTEDACTION_DISCOVERY_FINISHED的receiver分析
    从代码中我们可以看到这个action一共有两个receiver,一个是静态注册的BluetoothDiscoveryReceiver,一个是动态注册是ScanningStateChangedHandler。
    (1)、BluetoothDiscoveryReceiver:
    这个receiver是在settings中的Androidmanifest中静态注册的。用途:主要用于获取扫描开始和终止的时间。
    idh.codepackagesappsSettingsAndroidManifest.xml

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <receiver  
    2.       android:name=".bluetooth.BluetoothDiscoveryReceiver">  
    3.       <intent-filter>  
    4.         <action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />  
    5.         <action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />  
    6.         <category android:name="android.intent.category.DEFAULT" />  
    7.        </intent-filter>  
    8. </receiver>  

    1)、ACTION_DISCOVERY_STARTED、ACTION_DISCOVERY_FINISHED和AndroidManifest.xml文件的联系
    idh.codeframeworksasecorejavaandroidluetoothBluetoothAdapter.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public final class BluetoothAdapter {  
    2.     private static final String TAG = "BluetoothAdapter";  
    3. private static final boolean DBG = false;  
    4. …………  
    5.     public static final String ACTION_DISCOVERY_STARTED =  
    6.             "android.bluetooth.adapter.action.DISCOVERY_STARTED";  
    7.     public static final String ACTION_DISCOVERY_FINISHED =  
    8.             "android.bluetooth.adapter.action.DISCOVERY_FINISHED";  
    9. …………  
    10. }  

    2)、BluetoothAdapter,蓝牙适配器,直到我们建立bluetoothSocket连接之前,都要不断操作它。
    BluetoothAdapter中的动作常量

    ACTION_DISCOVERY_FINISHED

    已完成蓝牙搜索

    ACTION_DISCOVERY_STARTED

    已经开始搜索蓝牙设备

    ACTION_LOCAL_NAME_CHANGED

    更改蓝牙的名字

    ACTION_REQUEST_DISCOVERABLE

    请求能够被搜索

    ACTION_REQUEST_ENABLE

    请求启动蓝牙

    ACTION_SCAN_MODE_CHANGED

    扫描模式已经改变

    ACTION_STATE_CHANGED

    状态已改变

    ACTION_CONNECTION_STATE_CHANGED

    3)、收到广播后函数实现,开始扫描
    Main log中显示的log为DISCOVERY_STARTED
    D BluetoothDiscoveryReceiver: Received:android.bluetooth.adapter.action.DISCOVERY_STARTED
    HCI log 中:

    idh.codepackagesappsSettingssrccomandroidsettingsluetoothBluetoothDiscoveryReceiver.java这个文件中就一个函数,还是比简单

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public final class BluetoothDiscoveryReceiver extends BroadcastReceiver {  
    2.     private static final String TAG = "BluetoothDiscoveryReceiver";  
    3.     private static final boolean DEBUG = Debug.isDebug();  
    4.   
    5.     @Override  
    6.     public void onReceive(Context context, Intent intent) {  
    7.         String action = intent.getAction();  
    8.         if (DEBUG) Log.d(TAG, "Received: " + action);  
    9.   
    10.         if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) ||  
    11.                 action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {      
    12. //共享时间戳,扫描开始和结束的时间。   
    13.    LocalBluetoothPreferences.persistDiscoveringTimestamp(context);  
    14.         }  
    15.     }  
    16. }  

    ScanningStateChangedHandler的注册及用途,要用于开始扫描,和扫描显示界面的控制。
    这个receiver是在idh.codepackagesappsSettingssrccomandroidsettingsluetoothBluetoothEventManager.java动态注册的,如下:

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. BluetoothEventManager(LocalBluetoothAdapter adapter,  
    2.             CachedBluetoothDeviceManager deviceManager, Context context) {  
    3. mLocalAdapter = adapter;  
    4. …………  
    5. // Bluetooth on/off broadcasts  
    6.  addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());  
    7.   
    8. // Discovery broadcastsaddHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));  
    9.         addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));  
    10. …………  
    11. }  

    (1)、ScanningStateChangedHandler函数实现如下:idh.codepackagesappsSettingssrccomandroidsettingsluetoothBluetoothEventManager.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1.  private class ScanningStateChangedHandler implements Handler {  
    2.         private final boolean mStarted;  
    3.   
    4.         ScanningStateChangedHandler(boolean started) {  
    5.             mStarted = started;  
    6.         }  
    7.         public void onReceive(Context context, Intent intent,  
    8.                 BluetoothDevice device) {  
    9.             synchronized (mCallbacks) {//1)、调用注册的callback  
    10. 中的onScanningStateChanged函数。  
    11.                 for (BluetoothCallback callback : mCallbacks) {  
    12.                     callback.onScanningStateChanged(mStarted);  
    13.                 }  
    14.             }  
    15. //2)、这个函数就是把上次扫描到设备、和之前的设备做相应处理;  
    16.             mDeviceManager.onScanningStateChanged(mStarted);  
    17.             LocalBluetoothPreferences.persistDiscoveringTimestamp(context);  
    18.         }  
    19. }  

    1)、调用注册的callback中的callback.onScanningStateChanged(mStarted)函数。
    idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public void onScanningStateChanged(boolean started) {  
    2.     if (started == false) {//《1》、如果扫描结束;  
    3.         removeOutOfRangeDevices();  
    4.     }  
    5.     updateProgressUi(started);// 《2》、UI显示小圆圈扫描;  

    《1》、如果扫描结束;removeOutOfRangeDevices();
    idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private void removeOutOfRangeDevices() {  
    2.     Collection<CachedBluetoothDevicecachedDevices =  
    3.             mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();  
    4.     for (CachedBluetoothDevice cachedDevice : cachedDevices) {  
    5.          if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&  
    6.              cachedDevice.isVisible() == false) {  
    7.              BluetoothDevicePreference preference = mDevicePreferenceMap.get(cachedDevice);  
    8.              if (preference != null) {  
    9.                  mDeviceListGroup.removePreference(preference);  
    10.              }  
    11.              mDevicePreferenceMap.remove(cachedDevice);  
    12.           }  
    13.      }  
    14. }  

    《2》、UI显示小圆圈扫描,updateProgressUi(started);如下图所示:

    idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private void updateProgressUi(boolean start) {  
    2.     if (mDeviceListGroup instanceof ProgressCategory) {  
    3.         ((ProgressCategory) mDeviceListGroup).setProgress(start);  
    4.     }  
    5. }  

    2)、这部分的作用,开始扫描,不显示列表中内容,或把之前列表中没扫描到的设备清除
    mDeviceManager.onScanningStateChanged(mStarted);
    idh.codepackagesappsSettingssrccomandroidsettingsluetoothCachedBluetoothDevice.java

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private void updateProgressUi(boolean start) {  
    2.     if (mDeviceListGroup instanceof ProgressCategory) {  
    3.         ((ProgressCategory) mDeviceListGroup).setProgress(start);  
    4.     }  
    5. }  
    6. 2)、这部分的作用,开始扫描,不显示列表中内容,或把之前列表中没扫描到的设备清除  
    7. mDeviceManager.onScanningStateChanged(mStarted);  
    8. idh.codepackagesappsSettingssrccomandroidsettingsluetooth CachedBluetoothDevice.java  
    9.     public synchronized void onScanningStateChanged(boolean started) {  
    10.         // If starting a new scan, clear old visibility  
    11.         // Iterate in reverse order since devices may be removed.  
    12.         //如果开始新的扫描,清除旧的能见设备,迭代反序因为有的设备可能被删除  
    13.         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {  
    14.             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);  
    15.             if (started) {//如果扫描开始就不显示;  
    16.                 cachedDevice.setVisible(false);  
    17.             } else {//对扫描的结果作出判断,如果之前扫描过,这次没有扫描到,就移除列表。  
    18.                 if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&  
    19.                         cachedDevice.isVisible() == false) {  
    20.                     mCachedDevices.remove(cachedDevice);  
    21.                 }  
    22.             }  
    23.         }  
    24.     }  
















  • 相关阅读:
    网站开发动静分离
    如何前后端分离?
    设置HTML编码为UTF-8
    数据库索引&数据页
    spring中的BeanFactory和FactoryBean的区别与联系
    Java可重入锁与不可重入锁
    abo dto属性验证的坑
    小程序如何去掉button组件的边框
    asp.net core使用gzip
    npm总结
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4330367.html
Copyright © 2011-2022 走看看