最近一段时间在写支持BLE蓝牙的Android应用。是时候总结一下了。
1、什么是BLE。(总得先知道BLE是什么吧~~~)
Bluetooth Low Energy(低功耗蓝牙),缩写为Bluetooth LE,或BLE,作为蓝牙4.0 (有时称为蓝牙智能)规范的一部分,并针对上述的这些具体问题而被引入。就提高电池寿命而言,许多制造商声称一些传感器能维持数月甚至数年的时间(我必须承认我有点怀疑制造商的估计一般是基于最好的情况下,而不涉及实际的使用情况)。Google在Android
4.3(API 18)中加入了对BLE的支持。
* 这里附带提一下传统的蓝牙Bluetooth。传统的蓝牙采用的是类似于Socket的通信机制:Socket的基本原理就是有ServerSocket、ClientSocket,服务器端和客户端都指定一下网络地址、端口,然后服务器端accept(),处于等待客户端发起连接的状态;客户端Socket对象创建后,就可以拿到里面的一些输入输出流了,发起请求,然后实现各种数据传递业务逻辑。
而,BLE的机制不是这样的!BLE的机制是中心周边机制,以传输属性类型的原子数据为特征。(这句话是不是很绕。举个例子来说:Android Wear是有一个支持BLE的设备,可以测你的心率;Android
phone上有个心率App,两者通过蓝牙连接。Android phone(接收心率数据)就是BLE中心,Android Wear(发送心率数据)就是BLE周边。传输的数据是心率,可能是 60 次/分 ,显然有单位。所以说这个数据是一个属性数据;这个数据是以数据传输的最小单元的形式发送的,是个原子数据。好难解释。大概是这样理解的~~~)
可以这么认为:接收数据的一端(Android phone)认为是BLE中心(BluetoothGatt),其他低能耗蓝牙设备(比如说带蓝牙的温度传感器、心率传感器等等)。
-----------------------------------------------华丽丽的分割线-------------------------------------------------------------------------------------------------------------
2、进行BLE蓝牙操作的准备工作(工欲善其事,必先利其器!)
* 先啥也别想,把所有需要的权限加上。
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
2.1 Service 与 Activity 之间的通信
为啥要知道这个? 后面就知道了。先按下不表~~~(贱人就是矫情,还和我卖关子!)
记得Service 在清单配置文件AndroidManifest.xml中要写<service/>
<service android:name="com.yudichina.bluetoothbyoidtestdemo.BluetoothLeService" android:exported="true" android:enabled="true"> </service>
首先,建立一个Activity并绑定Service,它将使我们能够把所有的蓝牙操作从UI中解耦,同时让我们从BLE接收到数据后更新UI。
使用Messenger模式。它能够帮助我们不通过任何直接的方法调用而实现两个组件之间的通信。 Messenger模式要求每个组件来实现自身的Messenger实现:当类的实例被创建后处理传入的Message对象。
/** * 此类用来建立一个蓝牙BLE的服务,借此将蓝牙操作和UI更新解耦 * 继承Service服务类,需要在清单配置文件中注册 * 实现蓝牙适配器的回调接口LeScanCallback实现扫描周边支持蓝牙BLE协议的设备 */ @SuppressLint("NewApi") public class BluetoothLeService extends Service implements LeScanCallback{ // BluetoothLeService 一个标记 public static final String TAG = "BluetoothLeService"; //注册BleService public static final int MSG_REGISTER = 0x00010000; //解除注册BleService public static final int MSG_UNREGISTER = 0x00020000; //开始扫描BLE蓝牙设备 public static final int MSG_START_SCAN = 0x00200000; // BLE蓝牙设备被发现 public static final int MSG_DEVICE_FOUND = 0x00400000; // BLE蓝牙不存在 public static final int MSG_NO_BLUETOOTH = 0x00800000; // BLE蓝牙设备状态改变 public static final int MSG_STATE_CHANGED = 0x00040000; // BLE蓝牙设备连接 public static final int MSG_DEVICE_CONNECT = 0x00080000; // 接收BLE蓝牙设备数据 public static final int MSG_DEVICE_DATA = 0x00002000; // BLE蓝牙设备断开 public static final int MSG_DEVICE_DISCONNECT = 0x00004000; // 开启蓝牙 public static final int MSG_OPEN_BLUETOOTH = 0x00008000; // 更新蓝牙列表 public static final int MSG_UPDATE_BLUETOOOTHLIST = 0x00000200; //BleService的Messenger对象 Messenger.send(Message)方法表示将Message发送给Messenger对象 //Message由Messenger关联的Handler处理 private Messenger mMessengerOfService; private Handler mHandler; //MainActivity的Messenger对象 private Messenger mMessengerOfActivity; //1.蓝牙管理器对象 //2.蓝牙适配器对象 private BluetoothAdapter mBluetoothAdapter; //3.蓝牙中央 private BluetoothGatt mBluetoothGatt; //4.蓝牙周边的服务集合 private List<BluetoothGattService> mListOfBTService; //5.蓝牙周边的特性集合 private List<BluetoothGattCharacteristic> mListOfBTCharac; //连接标记 private boolean mConnected; public BluetoothLeService() { super(); mConnected = false; } @Override public void onCreate() { BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if(mBluetoothManager != null)mBluetoothAdapter = mBluetoothManager.getAdapter(); mHandler = new IncomingHandler(this); mMessengerOfService = new Messenger(mHandler); super.onCreate(); } @Override public IBinder onBind(Intent intent) { //建立MainActivity与BleService之间的绑定 return mMessengerOfService.getBinder(); } /** * 此处不要使用非静态内部类,否则会导致内存泄漏 * @author 沈煜 * */ @SuppressLint("HandlerLeak") private class IncomingHandler extends Handler{ private final WeakReference<BluetoothLeService> mService; public IncomingHandler(BluetoothLeService service) { super(); mService = new WeakReference<BluetoothLeService>(service); } @Override public void handleMessage(Message msg) { BluetoothLeService service = mService.get(); mMessengerOfActivity = msg.replyTo; if(service != null){ switch (msg.what) { case MSG_START_SCAN: //用户设备没有蓝牙硬件,不支持蓝牙功能 if(mBluetoothAdapter == null){ try { Message msg1 = Message.obtain(null, BluetoothLeService.MSG_NO_BLUETOOTH); msg1.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg1); } catch (RemoteException e) { e.printStackTrace(); } break; } //判断如果蓝牙适配器为空或者蓝牙适配器不可用(说明蓝牙适配器没有打开),使用蓝牙隐式意图打开用户的蓝牙 if(!mBluetoothAdapter.isEnabled()){ try { //This is an asynchronous call-->阻塞是方法 //mBluetoothAdapter.enable(); Message msg2 = Message.obtain(null, MSG_OPEN_BLUETOOTH); msg2.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg2); break; } catch (RemoteException e) { e.printStackTrace(); break; } } //开始扫描 scanLeDevice(true); break; case MSG_DEVICE_CONNECT: //TODO 建立连接 break; case MSG_DEVICE_DISCONNECT: //TODO 断开连接 break; case MSG_DEVICE_FOUND: //TODO 发现设备 break; case MSG_STATE_CHANGED: //TODO 改变状态 break; default: super.handleMessage(msg); break; } } } } @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { // TODO 具体执行扫描的方法 } @SuppressWarnings("deprecation") public void scanLeDevice(final boolean enable){ // TODO 开始扫描 为什么有 onLeScan方法还要此方法,此方法中设置超时停止扫描,给mScanning设置状态等 } private BluetoothGattCallback callback = new BluetoothGattCallback() { @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { //何时执行发现服务的方法 Log.d("TEST", "onServicesDiscovered"); } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { //何时执行连接状态改变的方法 } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.d("TEST", "onCharacteristicChanged"); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { } }; }
解释一下这段代码。(妈蛋,神经病吧,上来一段这么长的代码,你以为有注释我就能看懂吗?天真)
1.前面的一大段全局变量声明就不纠结了,反正是一堆常量,接收消息或者广播用的;然后是BluetoothManager(蓝牙管理器) 、BluetoothAdapter(蓝牙适配器)、BluetoothGatt(蓝牙中心)、BluetoothGattCallback(蓝牙周边回调) 这几个重要对象的声明。
2.然后就是Messenger对象了,Messenger对象是一个可以跨进程传递消息、数据的信使。 我这里用的是Messenger(Handler handler)这个构造方法,在Messenger上绑定了当前进程的Handler对象。Service的Messenger绑定Service的,Activity绑定Activity的(写在Acitivity那边了), 主要还是有个Message的
replyTo属性,表示的是接到此Message后回复的对象是谁?,例如:msg.replyTo = mMessengerOfService;mMessengerOfActivity.send(msg);表示消息发给了Activity,Activity的回复对象是Service,实际上就是,Service发消息给Activity! 而Message对象的msg中设置好要传递的数据,Activity就能收到了!
再看看Activity那一段吧。
@SuppressLint("NewApi") public class MainActivity extends Activity implements OnClickListener, OnItemClickListener, OnLongClickListener{ private static final String TAG = "BluetoothLE"; private Messenger mMessengerOfActivity; private Intent mServiceIntent; private Messenger mMessengerOfService; private boolean BIND_SUCCESS = false; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { BIND_SUCCESS = false; mMessengerOfService = null; } /** * 此方法在Activity与Service绑定成功是被调用 */ @Override public void onServiceConnected(ComponentName name, IBinder service) { BIND_SUCCESS = true; // 使用一个IBinder对象作为目标,获得Service端的Messenger对象 mMessengerOfService = new Messenger(service); try { Message msg = Message.obtain(null, BluetoothLeService.MSG_REGISTER); if (msg != null){ msg.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg); } else { mMessengerOfService = null; } } catch (Exception e) { e.printStackTrace(); } } }; private MyBroadcastReceiver mMyBroadcastReceiver; private TextView NameTV; private TextView AddressTV; private View list1111; private View list2222; @SuppressLint("HandlerLeak") @Override protected void onCreate(Bundle savedInstanceState) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化各种View onInit(); //初始化Activity的Messenger对象 //关联了Activity的Handler对象,用来处理来自Service的消息 mMessengerOfActivity = new Messenger(new IncomingHandler(this)); //意图对象,用来绑定Service的 mServiceIntent = new Intent(this, BluetoothLeService.class); } @Override protected void onStart() { //绑定BluetoothLeService来进行低能耗蓝牙扫描、蓝牙连接、蓝牙数据传输!--->实现蓝牙操作与UI操作之间的解耦 if(mServiceIntent != null && mConnection != null)bindService(mServiceIntent, mConnection, BIND_AUTO_CREATE); super.onStart(); } @Override protected void onStop() { if (mMessengerOfService != null) { try { Message msg = Message.obtain(null, BluetoothLeService.MSG_UNREGISTER); if (msg != null) { msg.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg); } } catch (Exception e) { Log.w(TAG, "Error unregistering with BleService",e); mMessengerOfService = null; } } super.onStop(); } /** * 此类是一个实例内部类,用来实现MainActivity与BluetoothLeService之间的数据传递 * 此类继承了Handler类,是一个消息处理类 * @author 沈煜 * */ @SuppressLint("HandlerLeak") private class IncomingHandler extends Handler { //使用弱引用关联当前的Activity,避免Activity被强引用关联无法销毁 private final WeakReference<MainActivity> mActivity; //在构造方法中初始化Activity public IncomingHandler(MainActivity activity) { mActivity = new WeakReference<MainActivity>(activity); } //重写handleMessage方法 @Override public void handleMessage(Message msg) { MainActivity activity = mActivity.get(); if (activity != null) { //TODO 此处应该处理来自Service的消息。 switch (msg.what) { case BluetoothLeService.MSG_REGISTER: if(BuildConfig.DEBUG)Log.i("MainActivity.IncomingHandler", "Service与Activity绑定成功!Activity收到来自Service的MSG_REGISTER消息"); break; case BluetoothLeService.MSG_UNREGISTER: if(BuildConfig.DEBUG)Log.i("MainActivity.IncomingHandler", "Service与Activity解除绑定!Activity收到来自Service的MSG_UNREGISTER消息"); unbindService(mConnection); break; case BluetoothLeService.MSG_NO_BLUETOOTH: if(BuildConfig.DEBUG)Log.i("MainActivity.IncomingHandler", "不支持BLE蓝牙连接!Activity收到来自Service的MSG_NO_BLUETOOTH消息"); Toast.makeText(getApplicationContext(), "亲,此设备不支持BLE蓝牙连接", Toast.LENGTH_SHORT).show(); break; case BluetoothLeService.MSG_OPEN_BLUETOOTH: Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enabler, Constants.REQUEST_ENABLE_BLUETOOTH); break; case BluetoothLeService.MSG_UPDATE_BLUETOOOTHLIST: break; case BluetoothLeService.MSG_DEVICE_DATA: break; default: super.handleMessage(msg); break; } } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == Constants.REQUEST_ENABLE_BLUETOOTH) { Log.i("test", "resultCode="+resultCode); //三星打印resultCode=0,华为打印resultCode=-1 //-1表示允许打开蓝牙,是用户点确认后返回的值,三星等厂商自定义了蓝牙开启系统界面,默认返回0,另外有对话框让用户确认是否开启蓝牙 if(resultCode == RESULT_OK) { Toast.makeText(this, "蓝牙已开启", Toast.LENGTH_SHORT).show(); startScan(); } else if(resultCode == RESULT_CANCELED) { Toast.makeText(this, "未允许开启蓝牙", Toast.LENGTH_LONG).show(); } } super.onActivityResult(requestCode, resultCode, data); } }
主要体会一下,Service和Activity之间怎么通讯就OK了,蓝牙操作后面在说。有其他好方法的可以跳过此段。
现在应该可以看出来,将蓝牙操作全部封装在一个Service中的好处了,那就是更新UI和蓝牙操作解耦合了,这样思路会清晰一些,再加以完善后可以做个小框架了。
2.2 蓝牙连接中心、周边、扫描、数据传输的基础知识。
先看看Android BLE SDK的四个关键类(class):
a) BluetoothGattServer作为周边来提供数据;BluetoothGattServerCallback返回周边的状态。
b) BluetoothGatt作为中央来使用和处理数据;BluetoothGattCallback返回中央的状态和周边提供的数据。
1、蓝牙中心的创建:
// 1.获得蓝牙管理器 BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); // 2.获得蓝牙适配器 if(mBluetoothManager != null)mBluetoothAdapter = mBluetoothManager.getAdapter(); // 3.使用蓝牙适配器扫描 mBluetoothAdapter.startLeScan(this); // 4.使用扫描到的蓝牙设备获得蓝牙中心 mBluetoothGatt = mDeviceHaveChoose.connectGatt(BluetoothLeService.this, false, callback);
2. 在蓝牙回调中获得数据,使用UUID
一个中央可以与多个周边连接,一个周边由多个Service组成,每个Service由多个Charicteristic组成,不同的周边由BluetoothDevice的MAC地址区分,不同的Service、Charicteristic之间由UUID 唯一标识符区分。特定的 Characteristic 中存储你需要的数据。
--------------------------------------------------看到这里时,说明你已经准备好了,下一集具体的BLE蓝牙操作。----------------------------------------------
特别谢鸣:
http://blog.stylingandroid.com/archives/2408
http://blog.csdn.net/jimoduwu/article/details/21604215