<div id="article_content" class="article_content csdn-tracking-statistics" data-mod="popu_519" data-dsm="post" style="overflow: hidden;">
                
<p></p>
<p style="margin-top:0px; margin-bottom:0.75em; font-size:16px; line-height:27.200000762939453px; text-indent:1em; color:rgb(51,51,51); font-family:'Helvetica Neue',Helvetica,Tahoma,Arial,STXihei,'Microsoft YaHei',微软雅黑,sans-serif">
这段时间在做低功耗蓝牙 (BLE) 应用的开发(并不涉及蓝牙协议栈)。总体感觉 Android BLE 还是不太稳定,开发起来也是各种痛苦。这里记录一些杂项和开发中遇到的问题及其解决方法,避免大家踩坑。本文说的问题有些没有得到官方文档的验证,不过也有一些论坛帖子的支持,也可以算是有一定根据。</p>
<ol style="padding:0px; margin:0px 0px 0.75em 25px; font-size:16px; line-height:27.200000762939453px; color:rgb(51,51,51); font-family:'Helvetica Neue',Helvetica,Tahoma,Arial,STXihei,'Microsoft YaHei',微软雅黑,sans-serif">
<li style="line-height:1.7em">
<p class="no-text-indent" style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em">
Android 从 4.3(API Level 18) 开始支持低功耗蓝牙,但是只支持作为中心设备 (Central) 模式,这就意味着 Android 设备只能主动扫描和链接其他外围设备 (Peripheral)。从Android 5.0(API Level 21)开始两种模式都支持。BLE 官方文档在&nbsp;&nbsp;<a target="_blank" href="https://developer.android.com/guide/topics/connectivity/bluetooth-le.html" rel="nofollow,noindex" style="">这里</a>&nbsp;。&nbsp;</p>
</li><li style="line-height:1.7em">
<p class="no-text-indent" style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em">
在&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothAdapter.startLeScan()</code>&nbsp;的时候,在&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothAdapter.LeScanCallback.onLeScan()</code>&nbsp;中不能做太多事情,特别是周围的BLE设备多的时候,非常容易导致出现如下错误:&nbsp;</p>
<p class="no-text-indent" style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em">
</p>
<div style="line-height:1.7em">
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI&nbsp;&nbsp;<em>LINUX(17741): ##### ERROR : GKI</em>&nbsp;exception: GKI&nbsp;&nbsp;<em>exception(): Task State Table E/GKI</em>&nbsp;LINUX(17741): #####&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI&nbsp;&nbsp;<em>LINUX(17741): ##### ERROR : GKI</em>&nbsp;exception: TASK ID [0] task name [BTU] state [1]&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI&nbsp;<em></em></p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
<em>LINUX(17741): #####</em></p>
<em></em>LINUX(17741): ##### ERROR : GKI&nbsp;<em></em>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
exception: TASK ID [1] task name [BTIF] state [1]</p>
LINUX(17741): #####&nbsp;
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI&nbsp;&nbsp;<em>LINUX(17741): ##### ERROR : GKI</em>&nbsp;exception: TASK ID [2] task name [A2DP-MEDIA] state [1]&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI&nbsp;<em></em></p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
<em>LINUX(17741): #####</em></p>
<em></em>LINUX(17741): ##### ERROR : GKI&nbsp;&nbsp;<em>exception: GKI</em>&nbsp;exception 65524 getbuf: out of buffers#####&nbsp;
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI&nbsp;&nbsp;<em>LINUX(17741): ##### ERROR : GKI</em>&nbsp;exception:&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
E/GKI_LINUX(17741):&nbsp;&nbsp;<span style="text-indent:0px"><em>*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*&nbsp;*</em>&nbsp;*&nbsp;</span></p>
</div>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
</p>
<p class="no-text-indent" style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em">
开发建议:在&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">onLeScan()</code>&nbsp;回调中只做尽量少的工作,可以把扫描到的设备,扔到另外一个线程中去处理,让&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">onLeScan()</code>&nbsp;尽快返回。&nbsp;&nbsp;<a target="_blank" href="https://code.google.com/p/android/issues/detail?id=65455" rel="nofollow,noindex" style="">[&nbsp;&nbsp;<em>参考帖子</em>&nbsp;]&nbsp;</a></p>
</li><li style="line-height:1.7em">
<div style="line-height:1.7em">
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
在使用&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothDevice.connectGatt()</code>&nbsp;或者&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt.connect()</code>&nbsp;等建立&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt</code>&nbsp;连接的时候,在任何时刻都只能最多一个设备在尝试建立连接。如果同时对多个蓝牙设备发起建立
 Gatt 连接请求。如果前面的设备连接失败了,后面的设备请求会被永远阻塞住,不会有任何连接回调。&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
开发建议:如果要对多个设备发起连接请求,最好是有一个同一个的设备连接管理,把发起连接请求序列化起来。前一个设备请求建立连接,后面请求在队列中等待。如果连接成功了,就处理下一个连接请求。如果连接失败了(例如出错,或者连接超时失败),就马上调用&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt.disconnect()</code>&nbsp;来释放建立连接请求,然后处理下一个设备连接请求。&nbsp;&nbsp;<a target="_blank" href="https://code.google.com/p/android-developer-preview/issues/detail?id=223#c16" rel="nofollow,noindex" style="">[&nbsp;&nbsp;<em>参考帖子</em>&nbsp;]&nbsp;</a></p>
</div>
</li><li style="line-height:1.7em">
<div style="line-height:1.7em">
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
对 BluetoothGatt 操作&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">(read/write)Characteristic()</code>&nbsp;,&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">(read/write)Descriptor()</code>&nbsp;和&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">readRemoteRssi()</code>&nbsp;都是异步操作。需要特别注意的是,同时只能有一个操作(有些贴这说只能同时有一个&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">writeCharacteristic()</code>&nbsp;,这个我并没有严格验证),也就是等上一个操作回调(例如&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">onCharacteristicWrite()</code>&nbsp;)以后,再进行下一个操作。&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
开发建议:把这写操作都封装成同步操作,一个操作回调之前,阻塞主其他调用。&nbsp;&nbsp;<a target="_blank" href="http://stackoverflow.com/a/24178815/1260562" rel="nofollow,noindex" style="">[&nbsp;&nbsp;<em>参考帖子</em>&nbsp;]&nbsp;</a></p>
</div>
</li><li style="line-height:1.7em">
<div style="line-height:1.7em">
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
BLE 设备的建立和断开连接的操作,例如&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothDevice.connectGatt()</code>&nbsp;,&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt.connect()</code>&nbsp;,&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt.disconnect()</code>&nbsp;等操作最好都放在主线程中,否则你会遇到很多意想不到的麻烦。&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
开发建议:对&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt</code>&nbsp;的连接和断开请求,都通过发送消息到 Android 的主线程中,让主线程来执行具体的操作。例如创建一个&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">new
 Handler(context.getMainLooper());</code>&nbsp;,把消息发送到这个&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">Handler</code>中。&nbsp;&nbsp;<a target="_blank" href="http://stackoverflow.com/a/20507449/1260562" rel="nofollow,noindex" style="">[&nbsp;&nbsp;<em>参考帖子</em>&nbsp;]&nbsp;</a></p>
</div>
</li><li style="line-height:1.7em">
<div style="line-height:1.7em">
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
如果你在开发 BLE 应用的时候,有时候会发现系统的功耗明显增加了,查看电量使用情况,蓝牙功耗占比非常高,好像低功耗是徒有虚名。使用&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">adb bugreport</code>&nbsp;获取的了系统信息,分析发现一个名叫&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothRemoteDevices</code>&nbsp;的&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">WakeLock</code>&nbsp;锁持有时间非常长,导致系统进入不了休眠。分析源代码发现,在连接
 BLE 设备的过程中,系统会持有 (Aquire) 这个&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">WakeLock</code>&nbsp;,直到连接上或者主动断开连接(调用&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">disconnect()</code>&nbsp;)才会释放。如果BLE设备不在范围内,这个超时时间大约为30s,而这时你可能又要尝试重新连接,这个&nbsp;&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">WakeLock</code>&nbsp;有被重新持有,这样系统就永远不能休眠了。&nbsp;</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
开发建议:对BLE设备连接,连接过程要尽量短,如果连接不上,不要盲目进行重连,否这你的电池会很快被消耗掉。这个情况,实际上对传统蓝牙设备连接也是一样。&nbsp;&nbsp;<a target="_blank" href="https://code.google.com/p/android/issues/detail?id=50751" rel="nofollow,noindex" style="">[&nbsp;&nbsp;<em>参考帖子</em>&nbsp;]&nbsp;</a></p>
</div>
</li><li style="line-height:1.7em">
<div style="line-height:1.7em">
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
Android 作为中心设备,最多只能同时连接 6 个 BLE 外围设备(可能不同的设备这个数字不一样),超过 6 个,就会连接不上了。现在 BLE 设备越来越多,其实并不够用,所以在开发的过程中,需要特别的谨慎使用。</p>
<p style="margin-top:0px; margin-bottom:0.75em; line-height:1.7em; text-indent:1em">
开发建议:按照需要连接设备,如果设备使用完了,应该马上释放连接(调用&nbsp;<code style="padding:2px 4px; font-family:Monaco,Menlo,Consolas,'Courier New',monospace; background-color:rgb(247,247,249); border:none">BluetoothGatt.close()</code>&nbsp;),腾出系统资源给其他可能的设备连接。&nbsp;&nbsp;<a target="_blank" href="http://stackoverflow.com/a/25364567/1260562" rel="nofollow,noindex" style="">[&nbsp;&nbsp;<em>参考帖子</em>&nbsp;]&nbsp;</a></p>
</div>
</li></ol>
<p style="margin-top:0px; margin-bottom:0.75em; font-size:16px; line-height:27.200000762939453px; text-indent:1em; color:rgb(51,51,51); font-family:'Helvetica Neue',Helvetica,Tahoma,Arial,STXihei,'Microsoft YaHei',微软雅黑,sans-serif">
本文只是一些经验之谈,观点也比较琐碎。这里很多问题都看起来是蓝牙协议栈不完善导致的,或许在后面 Android 升级中会修复这些问题,我这里说的可能不适用了。</p>
<br>
<p>From:&nbsp;http://www.tuicool.com/articles/aqyyayZ</p>
<p><br>
</p>
<link rel="stylesheet" href="">
            </div>