zoukankan      html  css  js  c++  java
  • ZT android -- 蓝牙 bluetooth (三)搜索蓝牙

    android -- 蓝牙 bluetooth (三)搜索蓝牙

    分类: Android的原生应用分析 2192人阅读 评论(8) 收藏 举报


            接上篇打开蓝牙继续,来一起看下蓝牙搜索的流程,触发蓝牙搜索的条件形式上有两种,一是在蓝牙设置界面开启蓝牙会直接开始搜索,另一个是先打开蓝牙开关在 进入蓝牙设置界面也会触发搜索,也可能还有其它触发方式,但最后都要来到BluetoothSettngs.java的startScanning(),我们分析的起点也从这里开始,起步代码如下

     

    1. private void updateContent(int bluetoothState, boolean scanState) {  
    2.     if (numberOfPairedDevices == 0) {  
    3.          preferenceScreen.removePreference(mPairedDevicesCategory);  
    4.          if (scanState == true) {  
    5.              mActivityStarted = false;  
    6.              startScanning();  
    7.         } else<span style="font-family: Arial, Helvetica, sans-serif;">    ........</span>  
    8. }         
    9. private void startScanning() {  
    10.         if (!mAvailableDevicesCategoryIsPresent) {  
    11.             getPreferenceScreen().addPreference(mAvailableDevicesCategory);  
    12.         }  
    13.         mLocalAdapter.startScanning(true);  
    14.     }  

            其实在这里蓝牙搜索和打开流程是结构上是一致的,利用LocalBluetoothAdapter.java过渡到 BluetoothAdapter.java再跳转至AdapterService.java要稍微留意下的是在这个过渡中startScaning() 方法变成了startDiscovery()方法,看下代码:packages/apps/Settings/src/com/android /settings/bluetooth/LocalBluetoothAdapter.java

    1. void startScanning(boolean force) {  
    2. if (!mAdapter.isDiscovering()) {  
    3.      if (!force) {  
    4.          // Don't scan more than frequently than SCAN_EXPIRATION_MS,  
    5.          // unless forced  
    6.          if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {  
    7.              return;  
    8.          }  
    9.          // If we are playing music, don't scan unless forced.  
    10.          A2dpProfile a2dp = mProfileManager.getA2dpProfile();  
    11.          if (a2dp != null && a2dp.isA2dpPlaying()) {  
    12.              return;  
    13.          }  
    14.      }  
    15. //这里才是我们最关注的,前面限制条件关注一下就行了  
    16.      if (mAdapter.startDiscovery()) {  
    17.          mLastScan = System.currentTimeMillis();  
    18.      }  
    19. }  
           BluetoothAdapter.java的那一段,路径 /frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java
    1. public boolean startDiscovery() {  
    2.     .............................  
    3.     AdapterService service = getService();  
    4.     if (service == nullreturn false;  
    5.     return service.startDiscovery();  
    6. }  
          这个service代码写得很明白AdapterService,转了一圈从framework又回到packages了,

          下面的代码路径自然是 :packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java,

    1. boolean startDiscovery() {  
    2.     enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,  
    3.             "Need BLUETOOTH ADMIN permission");  
    4.   
    5.     return startDiscoveryNative();  
    6. }  

          和打开蓝牙根本就是一个套路,上面的流程略过一小步,很简单的不写了,下面要怎么走,估计大家也都猜到了,JNI应该出场了,

           路径:/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp

    1. static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {                                                     
    2.     ALOGV("%s:",__FUNCTION__);                                  
    3.                                                                 
    4.     jboolean result = JNI_FALSE;                                
    5.     if (!sBluetoothInterface) return result;                    
    6.                                                                 
    7.     int ret = sBluetoothInterface->start_discovery();           
    8.     result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
    9.     return result;                                              
    10. }   

          在下面要去哪?稍微要动下脑筋,不过我们在上一篇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

    1. static int start_discovery(void)  
    2. {  
    3.     /* sanity check */  
    4.     if (interface_ready() == FALSE)  
    5.         return BT_STATUS_NOT_READY;  
    6.   
    7.     return btif_dm_start_discovery();  
    8. }     

            下面代码直接跳转就可以找到,路径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,一个头文件,大部分字符串和结构体的定义都在这了,多少还有些注释。

    1. bt_status_t btif_dm_start_discovery(void)                                    
    2. {                                                                            
    3.     tBTA_DM_INQ inq_params;                                                  
    4.     tBTA_SERVICE_MASK services = 0;                                          
    5.                                                                              
    6.     BTIF_TRACE_EVENT1("%s", __FUNCTION__);                                   
    7.     /* TODO: Do we need to handle multiple inquiries at the same time? */    
    8.                                                                              
    9.     /* Set inquiry params and call API */                                    
    10. #if (BLE_INCLUDED == TRUE)                                                   
    11.     inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;        
    12. #else                                                                        
    13.     inq_params.mode = BTA_DM_GENERAL_INQUIRY;                                
    14. #endif                                                                       
    15.     inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;                  
    16.                                                                              
    17.     inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;                  
    18.     inq_params.report_dup = TRUE;                                            
    19.                                                                              
    20.     inq_params.filter_type = BTA_DM_INQ_CLR;                                 
    21.     /* TODO: Filter device by BDA needs to be implemented here */            
    22.                                                                              
    23.     /* Will be enabled to TRUE once inquiry busy level has been received */  
    24.     btif_dm_inquiry_in_progress = FALSE;                                     
    25.     /* find nearby devices */                                                
    26.     BTA_DmSearch(&inq_params, services, bte_search_devices_evt);             
    27.                                                                              
    28.     return BT_STATUS_SUCCESS;                                                
    29. }   

          BTA_DmSearch()方法是看起来是要搜索了,不过里面这个家伙bte_search_devices_evt才是真正干活的主力,所以我们先看它,在这个函数里
    1. static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)                                                                      {                                                                                                                    
    2.        UINT16 param_len = 0;                                                                                           
    3.                                                                                                                         
    4.        if (p_data)                                                                                                      
    5.            param_len += sizeof(tBTA_DM_SEARCH);                                                                         
    6.        /* Allocate buffer to hold the pointers (deep copy). The pointers will point to the end of the tBTA_DM_SEARCH */  
    7.        switch (event)                                                                                                   
    8.        {                                                                                                                
    9.            case BTA_DM_INQ_RES_EVT:                                                                                     
    10.            {                                                                                                            
    11.                if (p_data->inq_res.p_eir)                                                                               
    12.                    param_len += HCI_EXT_INQ_RESPONSE_LEN;                                                               
    13.            }                                                                                                            
    14.            break;                                                                                                              
    15.           ..............................                                                                                                
    16.        }                                                                                                                
    17.        BTIF_TRACE_DEBUG3("%s event=%s param_len=%d", __FUNCTION__, dump_dm_search_event(event), param_len);             
    18.                                                                                                                         
    19.        /* if remote name is available in EIR, set teh flag so that stack doesnt trigger RNR */                          
    20.        if (event == BTA_DM_INQ_RES_EVT)                                                                                 
    21.            p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);                          
    22.                                                                                                                         
    23.        btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len,                   
    24.            (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);                                       
    25.    }     
             在上面的这个函数里又有这个bte_search_devices_evt,在它里我们能看一个 HAL_CBACK,这是要往回发消息了,看下这个函 数的全貌,说是全貌,不过还是只贴出一个case分支,太长了,大家还是自行还源码吧。到这里已经可以知道扫描到蓝牙设备的mac地址和设备名,那个 bdcpy函数就是在解析mac地址,有了这些,蓝牙搜索是到应该在界面展示成果的时候了,开始回调,忘记代码路径了,这个函数都在这个文件里:  /external/bluetooth/bluedroid/btif/src/btif_dm.c
    1. static void btif_dm_search_devices_evt (UINT16 event, char *p_param)  
    2.   
    3.    tBTA_DM_SEARCH *p_search_data;  
    4.    BTIF_TRACE_EVENT2("%s event=%s", __FUNCTION__, dump_dm_search_event(event));  
    5.   
    6.    switch (event)  
    7.    {  
    8.        case BTA_DM_DISC_RES_EVT:  
    9.        {  
    10.            p_search_data = (tBTA_DM_SEARCH *)p_param;  
    11.            /* Remote name update */  
    12.            if (strlen((const char *) p_search_data->disc_res.bd_name))  
    13.            {  
    14.                bt_property_t properties[1];  
    15.                bt_bdaddr_t bdaddr;  
    16.                bt_status_t status;  
    17.   
    18.                properties[0].type = BT_PROPERTY_BDNAME;  
    19.                properties[0].val = p_search_data->disc_res.bd_name;  
    20.                properties[0].len = strlen((char *)p_search_data->disc_res.bd_name);  
    21.                bdcpy(bdaddr.address, p_search_data->disc_res.bd_addr);  
    22.   
    23.                status = btif_storage_set_remote_device_property(&bdaddr, &properties[0]);  
    24.                ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device property", status);  
    25.                HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,  
    26.                                 status, &bdaddr, 1, properties);  
    27.            }  
    28.            /* TODO: Services? */  
    29.        }  
    30.        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,这个函数具体流程并没有看多少,当工具方法看了,有时间看看还是没坏处的。
    1. void BTA_DmSearch(tBTA_DM_INQ *p_dm_inq, tBTA_SERVICE_MASK services, tBTA_DM_SEARCH_CBACK *p_cback)  
    2. {  tBTA_DM_API_SEARCH    *p_msg;  
    3.     if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)  
    4.     {  
    5.         memset(p_msg, 0, sizeof(tBTA_DM_API_SEARCH));  
    6.   
    7.         p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;  
    8.         memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));  
    9.         p_msg->services = services;  
    10.         p_msg->p_cback = p_cback;  
    11.         p_msg->rs_res  = BTA_DM_RS_NONE;  
    12.         bta_sys_sendmsg(p_msg);  
    13.     }  
    14. }  
          看了上面方法后我们 要回去了看看,代码通过JNI下来的,回去也是看JNI的回调方法
    1. method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback""([B)V");  
    deviceFoundCallback方法最后会来java层的/packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java
    1. void deviceFoundCallback(byte[] address) {  
    2.        // The device properties are already registered - we can send the intent  
    3.        // now  
    4.        BluetoothDevice device = getDevice(address);  
    5.        debugLog("deviceFoundCallback: Remote Address is:" + device);  
    6.        DeviceProperties deviceProp = getDeviceProperties(device);  
    7.        if (deviceProp == null) {  
    8.            errorLog("Device Properties is null for Device:" + device);  
    9.            return;  
    10.        }  
    11.   
    12.        Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);  
    13.        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);  
    14.        intent.putExtra(BluetoothDevice.EXTRA_CLASS,  
    15.                new BluetoothClass(Integer.valueOf(deviceProp.mBluetoothClass)));  
    16.        intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);  
    17.        intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);  
    18.   
    19.        mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);  
    20.    }  
            到这里就是给界面发广播,应用层收到广播显示出来,通过这个handle,这个handle可以在BluetoothEventManager.java的构造函数里找到,
    1. addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());  
    2.     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {  
    3.     @Override  
    4.     public void onReceive(Context context, Intent intent) {  
    5.         String action = intent.getAction();  
    6.         BluetoothDevice device = intent  
    7.                 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
    8.   
    9.         Handler handler = mHandlerMap.get(action);  
    10.         if (handler != null) {  
    11.             handler.onReceive(context, intent, device);  
    12.         }  
    13.     }  
    14. };  
             这里handle对应要看DeviceFoundHandler,也就是下面贴出来的代码,
    1.  private class DeviceFoundHandler implements Handler {  
    2.     public void onReceive(Context context, Intent intent,  
    3.             BluetoothDevice device) {  
    4.        ........................  
    5.         // TODO Pick up UUID. They should be available for 2.1 devices.  
    6.         // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.  
    7.         CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);  
    8.         if (cachedDevice == null) {  
    9.             cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);  
    10.             Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "  
    11.                     + cachedDevice);  
    12.             // callback to UI to create Preference for new device  
    13.             dispatchDeviceAdded(cachedDevice);  
    14.         }  
    15.       ......................  
    16.     }  
    17. }  
            在if语句中dispatchDeviceAdded()向界面分发消息,最后处理消息的地方在这里,已经到settings应用里了
    1. public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {                   
    2.    if (mDevicePreferenceMap.get(cachedDevice) != null) {                          
    3.        return;                                                                    
    4.    }                                                                              
    5.                                                                                   
    6.    // Prevent updates while the list shows one of the state messages              
    7.    if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;    
    8.                                                                                   
    9.    if (mFilter.matches(cachedDevice.getDevice())) {                               
    10.        createDevicePreference(cachedDevice);                                      
    11.    }                                                                              
    12. }                     
             上面代码中最后一个分支就是界面显示要做的事了,从settings界面开始再到settings界面显示出搜索到蓝牙结束,后面的代码不再写了,本文关心的东东到此结束。
    1.   void createDevicePreference(CachedBluetoothDevice cachedDevice) {  
    2.     BluetoothDevicePreference preference = new BluetoothDevicePreference(  
    3.             getActivity(), cachedDevice);  
    4.   
    5.     initDevicePreference(preference);  
    6.     mDeviceListGroup.addPreference(preference);  
    7.     mDevicePreferenceMap.put(cachedDevice, preference);  
    8. }  

            到目前为止,包括前面的打开流程分析,还仅是针对代码流程做的分析,对于蓝牙协议方面东西还没有涉及,比如蓝牙是如何发现其它蓝牙设备,这个流程究竟是怎 么工作还不是很清楚,后续会尽量关注这些问题,估计看起来就没那么容易,欢迎有经验的朋友指点一二,当然对于本文不足,欢迎拍砖讨论。分享是快乐的,谢 谢!


    --------------modify 2013.6.2 21:08--------

    更新蓝牙搜索返回后的跳转代码

    更多 0
     
    查看评论
    5楼 何逊青 2013-11-04 16:24发表 [回复]
    写的非常清晰,楼主能分析以下ACL、rfcomm、sco连接建立的过程么?
    4楼 xyp5299 2013-08-16 18:22发表 [回复]
    awesome
    3楼 liangtiancai 2013-07-18 23:26发表 [回复]
    通过学习能把基本流程搞清楚,期待版主继续
    2楼 andger032 2013-06-05 08:43发表 [回复]
    恩,我是看了但没有看懂,呵呵功力尚浅阿。期待版主后续文章。
    1楼 andger032 2013-06-04 11:24发表 [回复]
    写 的不错,持续围观。不过有个问题问下:BTA_DmSearch-》bta_sys_sendmsg-》GKI_send_msg-》 GKI_send_event-》pthread_cond_signal。然后跑哪里去了呀?上面你写的好像没有深入下去,直接看回调了,不过思路还是 很清晰的,谢谢。
    Re: youmingyu123 2013-10-11 10:14发表 [回复]
    回复andger032:这是激活另外一个等待的线程btu_task,具体参考
    http://blog.csdn.net/yinlijun2004/article/details/9724347 蓝牙关闭过程
    Re: andger032 2013-11-12 08:43发表 [回复]
    回复youmingyu123:非常感谢。
    Re: balmy 2013-06-04 23:11发表 [回复]
    回复andger032:呵呵,你提的这个确实没太关注,当时跟了一下感觉还不是很清晰就直接先略过了,bta_sys_sendmsg往后面的调用更像是公用方法,里面涉及线程同步等操作,后续肯定是还要看的
  • 相关阅读:
    关于利用注射点判断数据库web是否分离
    springmvc中使用MockMvc测试controller
    springmvc中使用MockMvc测试controller
    小哥哥教你100%安装Win10专业版永久激活版(全网独一无二)
    Python爬虫入门教程 62-100 30岁了,想找点文献提高自己,还被反爬了,Python搞起,反爬第2篇
    服务器Servlet返回信息在浏览器无法显示
    nacos 系列
    如何用产品经理的思维设计移动报表
    【讲师专访】Oracle ACE 总监侯圣文:不懂开发的运维终将被淘汰
    我非要捅穿这 Neutron(四)Open vSwitch in Neutron
  • 原文地址:https://www.cnblogs.com/jeanschen/p/3528255.html
Copyright © 2011-2022 走看看