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

分类: Android的原生应用分析 2013-05-31 22:03 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 == null) return 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. return startDiscoveryNative();
  5. }

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

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

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

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

下面代码直接跳转就可以找到,路径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. BTIF_TRACE_EVENT1("%s", __FUNCTION__);
  6. /* TODO: Do we need to handle multiple inquiries at the same time? */
  7. /* Set inquiry params and call API */
  8. #if (BLE_INCLUDED == TRUE)
  9. inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;
  10. #else
  11. inq_params.mode = BTA_DM_GENERAL_INQUIRY;
  12. #endif
  13. inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;
  14. inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;
  15. inq_params.report_dup = TRUE;
  16. inq_params.filter_type = BTA_DM_INQ_CLR;
  17. /* TODO: Filter device by BDA needs to be implemented here */
  18. /* Will be enabled to TRUE once inquiry busy level has been received */
  19. btif_dm_inquiry_in_progress = FALSE;
  20. /* find nearby devices */
  21. BTA_DmSearch(&inq_params, services, bte_search_devices_evt);
  22. return BT_STATUS_SUCCESS;
  23. }

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. if (p_data)
  4. param_len += sizeof(tBTA_DM_SEARCH);
  5. /* Allocate buffer to hold the pointers (deep copy). The pointers will point to the end of the tBTA_DM_SEARCH */
  6. switch (event)
  7. {
  8. case BTA_DM_INQ_RES_EVT:
  9. {
  10. if (p_data->inq_res.p_eir)
  11. param_len += HCI_EXT_INQ_RESPONSE_LEN;
  12. }
  13. break;
  14. ..............................
  15. }
  16. BTIF_TRACE_DEBUG3("%s event=%s param_len=%d", __FUNCTION__, dump_dm_search_event(event), param_len);
  17. /* if remote name is available in EIR, set teh flag so that stack doesnt trigger RNR */
  18. if (event == BTA_DM_INQ_RES_EVT)
  19. p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);
  20. btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len,
  21. (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);
  22. }

在上面的这个函数里又有这个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. tBTA_DM_SEARCH *p_search_data;
  3. BTIF_TRACE_EVENT2("%s event=%s", __FUNCTION__, dump_dm_search_event(event));
  4. switch (event)
  5. {
  6. case BTA_DM_DISC_RES_EVT:
  7. {
  8. p_search_data = (tBTA_DM_SEARCH *)p_param;
  9. /* Remote name update */
  10. if (strlen((const char *) p_search_data->disc_res.bd_name))
  11. {
  12. bt_property_t properties[1];
  13. bt_bdaddr_t bdaddr;
  14. bt_status_t status;
  15. properties[0].type = BT_PROPERTY_BDNAME;
  16. properties[0].val = p_search_data->disc_res.bd_name;
  17. properties[0].len = strlen((char *)p_search_data->disc_res.bd_name);
  18. bdcpy(bdaddr.address, p_search_data->disc_res.bd_addr);
  19. status = btif_storage_set_remote_device_property(&bdaddr, &properties[0]);
  20. ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device property", status);
  21. HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,
  22. status, &bdaddr, 1, properties);
  23. }
  24. /* TODO: Services? */
  25. }
  26. 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. p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;
  7. memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));
  8. p_msg->services = services;
  9. p_msg->p_cback = p_cback;
  10. p_msg->rs_res  = BTA_DM_RS_NONE;
  11. bta_sys_sendmsg(p_msg);
  12. }
  13. }

看了上面方法后我们 要回去了看看,代码通过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. Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
  12. intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
  13. intent.putExtra(BluetoothDevice.EXTRA_CLASS,
  14. new BluetoothClass(Integer.valueOf(deviceProp.mBluetoothClass)));
  15. intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
  16. intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
  17. mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
  18. }

到这里就是给界面发广播,应用层收到广播显示出来,通过这个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. Handler handler = mHandlerMap.get(action);
  9. if (handler != null) {
  10. handler.onReceive(context, intent, device);
  11. }
  12. }
  13. };

这里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. // Prevent updates while the list shows one of the state messages
  6. if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
  7. if (mFilter.matches(cachedDevice.getDevice())) {
  8. createDevicePreference(cachedDevice);
  9. }
  10. }

上面代码中最后一个分支就是界面显示要做的事了,从settings界面开始再到settings界面显示出搜索到蓝牙结束,后面的代码不再写了,本文关心的东东到此结束。

  1. void createDevicePreference(CachedBluetoothDevice cachedDevice) {
  2. BluetoothDevicePreference preference = new BluetoothDevicePreference(
  3. getActivity(), cachedDevice);
  4. initDevicePreference(preference);
  5. mDeviceListGroup.addPreference(preference);
  6. mDevicePreferenceMap.put(cachedDevice, preference);
  7. }

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

--------------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往后面的调用更像是公用方法,里面涉及线程同步等操作,后续肯定是还要看的

ZT android -- 蓝牙 bluetooth (三)搜索蓝牙的更多相关文章

  1. Bluetooth LE(低功耗蓝牙) - 第三部分

    回顾 在本系列的前两篇文章中,我们已经了解了一些关于Bluetooth LE的背景并建立一个简单的Activity / Service框架.   在这篇文章中,我们将探讨Bluetooth LE的细节 ...

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

    接上篇打开蓝牙继续,来一起看下蓝牙搜索的流程,触发蓝牙搜索的条件形式上有两种,一是在蓝牙设置界面开启蓝牙会直接开始搜索,另一个是先打开蓝牙开关在进入蓝牙设置界面也会触发搜索,也可能还有其它触发方式,但 ...

  3. ZT android -- 蓝牙 bluetooth (四)OPP文件传输

    android -- 蓝牙 bluetooth (四)OPP文件传输 分类: Android的原生应用分析 2013-06-22 21:51 2599人阅读 评论(19) 收藏 举报 4.2源码AND ...

  4. ZT android -- 蓝牙 bluetooth (二) 打开蓝牙

    android -- 蓝牙 bluetooth (二) 打开蓝牙 分类: Android的原生应用分析 2013-05-23 23:57 4773人阅读 评论(20) 收藏 举报 androidblu ...

  5. ZT android -- 蓝牙 bluetooth (五)接电话与听音乐

    android -- 蓝牙 bluetooth (五)接电话与听音乐 分类: Android的原生应用分析 2013-07-13 20:53 2165人阅读 评论(9) 收藏 举报 蓝牙android ...

  6. ZT android -- 蓝牙 bluetooth (一) 入门

    android -- 蓝牙 bluetooth (一) 入门 分类: Android的原生应用分析 2013-05-19 21:44 4543人阅读 评论(37) 收藏 举报 bluetooth4.2 ...

  7. 深入了解Android蓝牙Bluetooth——《进阶篇》

    在 [深入了解Android蓝牙Bluetooth--<基础篇>](http://blog.csdn.net/androidstarjack/article/details/6046846 ...

  8. Android 蓝牙开发之搜索、配对、连接、通信大全

            蓝牙( Bluetooth®):是一种无线技术标准,可实现固定设备.移动设备和楼宇个人域网之间的短距离数据 交换(使用2.4-2.485GHz的ISM波段的UHF无线电波).蓝牙设备最 ...

  9. 深入了解Android蓝牙Bluetooth ——《总结篇》

    在我的上两篇博文中解说了有关android蓝牙的认识以及API的相关的介绍,蓝牙BLE的搜索,连接以及读取. 没有了解的童鞋们请參考: 深入了解Android蓝牙Bluetooth--<基础篇& ...

随机推荐

  1. free 和 delete 把指针怎么了

    使用free或delete之后,只是把指针所指的内容给释放掉,但是指针并没有被干掉,还是指向原来位置(并不是执行NULL),此时指针指向的内容为垃圾,被称为“野指针”. 举例说明几个重要容易迷糊的特征 ...

  2. 码表的理解(ASCII,GBK,Unicode,UTF-8等)。

    以下任何言论都完全是个人的理解,如有雷同纯属巧合,如有错误,希望大家多多指出,共同学习!谢谢! 笔者是一个理解能力偏慢.稍钻牛角尖的程序员,什么东西都要从最基础理解起,一步一步向上理解,因此讲述时也是 ...

  3. EntityFrameworkCode 操作MySql 相关问题

    近段时间,由于工作原因,使用到了EntityFrameworkCore 操作MySql数据库,使用中遇到一些问题,特此记录 系统环境 Win10 1805,VS 2017,Framework:Asp. ...

  4. Angular2 不明真相第一个Demo例子

    如果不是去年换工作接触到AngularJS,估计是不会花时间去学习这个框架的,毕竟是前端的框架,不是自己熟悉的领域.但是为了混得下去,去年就学习了AngularJS的一些用法,当时还整理了一些积累 & ...

  5. 记录一次nginx502/504问题解决过程

    最近自己在阿里云购买有段时间的服务器,一访问就出现nginx 504 Gateway Time-out. 想起以前是可以访问的,细想最近改动的配置应该不会涉及到这块啊. 就网上各种百度谷歌,最后终于找 ...

  6. WordPress主题开发:为文章添加自定义栏目

    开启自定义栏目:点击头顶的“显示选项”,勾选“自定义栏目” 然后编辑文章时,即可看见 实验: 定义名称为:play_url ,值为:http://www.xiami.com/widget/635357 ...

  7. 第5章 scrapy爬取知名问答网站

    第五章感觉是第四章的练习项目,无非就是多了一个模拟登录. 不分小节记录了,直接上知识点,可能比较乱. 1.常见的httpcode: 2.怎么找post参数? 先找到登录的页面,打开firebug,输入 ...

  8. code EINTEGRITY,npm安装时候报错

    解决方法: 1.如果有package-lock.json文件,就删掉 2.管理员权限进入cmd 3.执行npm cache clean --force 4.之后再npm install 有时候网不好也 ...

  9. [转]如何选择Html.RenderPartial和Html.RenderAction

    Html.RenderPartial与Html.RenderAction这两个方法都是用来在界面上嵌入用户控件的. Html.RenderPartial是直接将用户控件嵌入到界面上: <%Htm ...

  10. MySQL (一)(未完成)

    并发控制 读写锁 读锁: 共享锁 写锁: 排它锁 颗粒度 表锁,MySQL中开销最小的锁 行锁,MySQL中开销最大的锁 事务 ACID特性 原子性(Automatic) 隔离性(Isolation) ...