Android蓝牙BLE开发
最近正在研究Android的蓝牙BLE开发学习,以下是自己做的个人总结
1.1何为BLE?
首先得说明什么是低功耗蓝牙BLE,BLE的全称为Bluetooth low energy(或称Blooth LE,BLE),从英文全称便可以知晓其是一种低功耗的蓝牙技术,是蓝牙技术联盟设计和销售的一种个人局域网技术,旨在用于医疗保健、运动健身、信标、安防、家庭娱乐等领域的新兴应用。相较经典蓝牙,低功耗蓝牙旨在保持同等通信范围的同时显著降低功耗和成本。而正因为其低功耗的优点,可以让Android APP可以具有与低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备等
1.2基础术语和概念
在正式开发前,要对基本的蓝牙的术语和概念要有个大致的认识,因为我本人学习的也不长,就是个简单的总结先:
Generic Attribute Profile:
简称为GATT,现在的低功耗BLE的连接都是建立在GATT协议之上实现的,蓝牙技术联盟规定了许多低功耗设备的配置文件,配置文件是设备如何在特定的应用程序在工作的规格,而一个设备中可以有多个配置文件。
Generic Access Profile:
Profile可以视为一种规范,一个标准的通信协议,它存在于从机中,蓝牙技术联盟规定了一些标准的profile,例如防丢器 ,心率计等。每个profile中会包含多个service,每个service代表从机的一种能力。
Service:
服务,在BLE从机中,可以有多个服务,例如:电量信息服务,而Service中又有多个Characteristic特征值,而每个具体的特征值才是BLE通信的重点,例如在1电量信息服务中,当前的电量为80%,所以会通过电量的特征值存在从机的profile里,这样主机就可以通过这个特征值来读取80%这个数据
Characteristic:
特征值,ble主从机通信都是通过特征值实现的,类似于标签key,可以通过这个key值来获取信息和数据
UUID:统一识别码,服务和特征值都需要一个唯一的UUID来标识整理,而每个从机都会有一个叫做profile的东西存在,不管是上面的自定义的simpleprofile,还是标准的防丢器profile,他们都是由一些列service组成,然后每个service又包含了多个characteristic,主机和从机之间的通信,均是通过characteristic来实现。
1.3初始化配置
讲完了大概的概念之后便是基本的操作了,以下内容会结合代码和流程图进行展示
1.3.1权限
想要使用BLE开发,就得先获得蓝牙必要的权限,需要先在AndroidManifest.xml中设置权限
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果想声明你的app只为具有BLE的设备提供,在manifest文件中包括:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
除此之外,如果是Android 6.0以上的手机仅仅是添加以上的蓝牙权限是不足的,这样会造成无法扫描到其他设备,因而还需要添加位置权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-feature android:name="android.hardware.location.gps" />
1.3.2是否支持蓝牙BLE
required=true只能是让支持BLE的Android设备上安装运行,不支持的则不行,如果想在Java实现上述功能,可以通过下述代码:
// 手机硬件支持蓝牙
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();finish();
}
1.3.3初始化蓝牙适配器
所有的蓝牙活动都需要蓝牙适配器,BluetoothAdapter代表设备本身的蓝牙适配器。整个系统只有一个蓝牙适配器,而且app需要蓝牙适配器与系统交互。下面的代码片段显示了如何得到适配器。
注意该方法使用getSystemService()返回BluetoothManager,然后将其用于获取适配器的一个实例。Android 4.3(API 18)引入BluetoothManager
final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
elsemBluetoothAdapter = bluetoothManager.getAdapter();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
1.3.4开启蓝牙
要操作蓝牙,必须先在设备中开启蓝牙。如果当前未启用蓝牙,则可以通过触发一个Intent调用系统显示一个对话框来要求用户启用蓝牙权限
// 打开蓝牙权限
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
1.3.5初始化ListView列表适配器
/*** @Description: TODO<自定义适配器Adapter,作为listview的适配器>*/
private class LeDeviceListAdapter extends BaseAdapter {private ArrayList<BluetoothMessage> mLeDevices;private LayoutInflater mInflator;public LeDeviceListAdapter(){super();//rssis = new ArrayList<Integer>();mLeDevices = new ArrayList<BluetoothMessage>();mInflator = getLayoutInflater();}public void addDevice(BluetoothMessage device){for (BluetoothMessage mLeDevice : mLeDevices) {if(mLeDevice.getDevice().getAddress().equals(device.getDevice().getAddress())){return; } } mLeDevices.add(device);//rssis.add(rssi);}public BluetoothMessage getDevice(int position){return mLeDevices.get(position);}public void clear(){mLeDevices.clear();//rssis.clear();}@Overridepublic int getCount(){return mLeDevices.size();}@Overridepublic Object getItem(int i){return mLeDevices.get(i);}@Overridepublic long getItemId(int i){return i;}/** * 重写getview** **/@Overridepublic View getView(int i, View view, ViewGroup viewGroup){// General ListView optimization code.// 加载listview每一项的视图BluetoothMessage bluetoothMessage = mLeDevices.get(i);return view;}
}
1.3.6发现BLE设备(扫描设备)
如果要发现BLE设备,使用startLeScan()方法进行扫描,扫描的话就要传入true执行scanLeDvice(true)方法,然后蓝牙适配器就调用startLeScan()方法进行扫描,LeScanCallback是扫描回调,也就是返回扫描结果。
1.3.6.1扫描结果:
private void scanLeDevice(final boolean enable) {if (mBluetoothAdapter == null){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();else {BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();}}if (mBluetoothLeScanner == null){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();}if (enable) {// Stops scanning after a pre-defined scan period.mHandler.postDelayed(new Runnable(){@Overridepublic void run(){mScanning = false;scan_flag = true;scan_btn.setText("扫描设备");Log.i("SCAN", "stop.....................");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.stopScan(mScanCallback);elsemBluetoothAdapter.stopLeScan(mLeScanCallback);}}, SCAN_PERIOD);/* 开始扫描蓝牙设备,带mLeScanCallback 回调函数 */Log.i("SCAN", "begin.....................");mScanning = true;scan_flag = false;scan_btn.setText("停止扫描");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.startScan(mScanCallback);elsemBluetoothAdapter.startLeScan(mLeScanCallback);} else {Log.i("Stop", "stoping................");mScanning = false;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.stopScan(mScanCallback);elsemBluetoothAdapter.stopLeScan(mLeScanCallback);scan_flag = true;}
}
1.3.6.2回调方法:
在这里回家上面的扫描方法的扫描结果回调,也就是传回来。其中在onLeScan方法是重点,蓝牙扫描成功后的结果会返回此方法中,然后就可以处理BluetoothDevice拿到设备信息,最后展示到前面初始化的ListView列表中:
- 第一个参数device,表示一个远程蓝牙设备,里面有它独有的蓝牙地址Address和Name等,所以后续需要进行连接蓝牙操作也需要用到这里获取的蓝牙Address
- 第二个参数rssi表示扫描到的设备信号强度,这里应该可以用来判断距离的远近。
- 第三个参数scanRecord表示远程设备提供的广告记录的内容。
// 这个是官方demo的源码
// 是扫描的Callback的回调,其中的onLeScan方法,蓝牙扫描成功之后会将结果会返回此方法中
// 然后就可以处理BluetoothDevice拿到设备信息 最后展示到前面初始化的listview列表中
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback()
{@Overridepublic void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord){// TODO Auto-generated method stubrunOnUiThread(new Runnable(){@Overridepublic void run(){// 讲扫描到设备的信息输出到listview的适配器BluetoothMessage bluetoothMessage = new BluetoothMessage(device);mleDeviceListAdapter.addDevice(bluetoothMessage);mleDeviceListAdapter.notifyDataSetChanged();}});}
};
至此已经完成初始化配置、一些设备的判断逻辑和扫描操作了,如果能成功地扫描到设备并展示到界面上的话,下一步如果用户点击了列表,将进行蓝牙连接和相关的读写操作!
1.4创建BluetoothLeService服务类并初始化蓝牙连接
创建了一个BluetoothLeService服务类并继承了Service,用来完成蓝牙设备的初始化、连接、断开连接、读取特征值、写入特征值、设置特征值变化通知以及获取已连接蓝牙的所有服务等操作
1.4.1创建服务
首先,我们得进行第一步,在onCreate()方法中,执行bindService开启一个服务
这里因为是项目需要,使用了一个虚拟按钮来进行初始化的连接
//蓝牙service,负责后台的蓝牙服务
private static BluetoothLeService mBluetoothLeService;
Intent gattServiceIntent;
/*** --------------------------------------------onCreate方法-----------------------------------------------------*/
@Override
public void onCreate(Bundle savedInstanceState)
{.../* 启动蓝牙service */gattServiceIntent = new Intent(this, BluetoothLeService.class);//模拟按键点击事件触发蓝牙连接rev_tv.post(new Runnable() {@Overridepublic void run() {scan_btn.performClick();}});//监听scan_btnscan_btn.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View v){if (scan_flag){mleDeviceListAdapter = new LeDeviceListAdapter();//lv.setAdapter(mleDeviceListAdapter);scanLeDevice(true);} else {scanLeDevice(false);scan_btn.setText("扫描设备");}if (mScanning) {/* 停止扫描设备 */if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.stopScan(mScanCallback);elsemBluetoothAdapter.stopLeScan(mLeScanCallback);mScanning = false;}bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);}});...
}
开启服务成功后,便会一样进行服务回调,当服务回调已经成功连接时,便会获取一个BlueToohtLeService的实例,接着就执行蓝牙连接操作:
/* BluetoothLeService绑定的回调函数 */
// 获取BluetoothLeService的实例,进行蓝牙连接操作
// 以下都是Android官方的demo源码
private final ServiceConnection mServiceConnection = new ServiceConnection()
{@Overridepublic void onServiceConnected(ComponentName componentName,IBinder service){mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();if (!mBluetoothLeService.initialize()){//Log.e(TAG, "Unable to initialize Bluetooth");finish();}mBluetoothLeService.connect(mDeviceAddress);}@Overridepublic void onServiceDisconnected(ComponentName componentName){mBluetoothLeService = null;}
};
这个时候,需要单独创建一个BlueToothService类,因为BlueToohtLeService类既然是服务类,那它父类肯定是继承于Service
public class BluetoothLeService extends Service {...
}
这里的BlueToothService类是BindService服务,用于绑定一个服务。这样当bindService(intent,conn,flags)后,就会绑定一个服务。这样做可以获得这个服务对象本身,而用StartService(intent)的方法只能启动服务。
BindService方法的一般过程:
1.4.1.1开启BindService服务
// bindService区别于startService,用于绑定服务,可以获得这个服务对象本身
public class LocalBinder extends Binder {public BluetoothLeService getService(){return BluetoothLeService.this;}
}
// onBind()是使用bindService开启的服务才会有回调的一个方法
// onBind()方法给MainActivity返回了BluetoothLeService实例
// 用于方便MainActivity后续的连接和读写操作
@Override
public IBinder onBind(Intent intent)
{return mBinder;
}
1.4.1.2关闭BindService,关闭蓝牙
当服务调用unbindService时,服务的生命周期将会进入onUnbind()方法,接着执行了关闭蓝牙的方法
@Override
public boolean onUnbind(Intent intent)
{close();return super.onUnbind(intent);
}private final IBinder mBinder = new LocalBinder();
1.4.2初始化蓝牙
这个方法是BlueToohtLeService服务类创建之后在MainActivity通过拿到BlueToohtLeService实例调用的,也是官方的源码
/* service 中蓝牙初始化 */
public boolean initialize()
{// For API level 18 and above, get a reference to BluetoothAdapter// through// BluetoothManager.if (mBluetoothManager == null){ //获取系统的蓝牙管理器//使用 getSystemService(java.lang.String)与 BLUETOOTH_SERVICE创建一个 BluetoothManager// 然后调用 getAdapter()以获得 BluetoothAdaptermBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);if (mBluetoothManager == null){Log.e(TAG, "Unable to initialize BluetoothManager.");return false;}}//BluetoothManager 的变量调用 getAdapter()以获得 BluetoothAdapter 进而对整体蓝牙进行管理mBluetoothAdapter = mBluetoothManager.getAdapter();if (mBluetoothAdapter == null){Log.e(TAG, "Unable to obtain a BluetoothAdapter.");return false;}return true;
}
1.4.3执行connect()和connectGatt
connect()和connectGatt都是连接BLE设备的方法,但二者用法不同
connectGatt是BluetoothDevice类下的方法,功能是向BLE设备发起连接,然后得到一个BluetoothGatt类型的返回值,利用这个返回值可以进行下一步操作。
connect是BluetoothGatt类下的方法,功能是重新连接。如果BLE设备和APP已经连接过,但是因为设备超出了蓝牙的连接范围而断掉,那么当设备重新回到连接范围内时,可以通过connect()重新连接
// 连接远程蓝牙public boolean connect(final String address){// 适配器为空或者地址为空就会提示if (mBluetoothAdapter == null || address == null){Log.w(TAG,"BluetoothAdapter not initialized or unspecified address.");return false;}// Previously connected device. Try to reconnect.if (mBluetoothDeviceAddress != null&& address.equals(mBluetoothDeviceAddress)&& mBluetoothGatt != null){Log.d(TAG,"Trying to use an existing mBluetoothGatt for connection.");//mBluetoothGatt.connect()表示连接回远程设备//在连接断开后,此方法的功能在于“重新连接到远程设备”//如果设备曾经连接过,但目前不在范围内//则一旦设备回到范围内,则可以通过connect重新连接。if (mBluetoothGatt.connect())//连接蓝牙,其实就是调用BluetoothGatt的连接方法{mConnectionState = STATE_CONNECTING;return true;} else{return false;}}/* 获取远端的蓝牙设备 *///mBluetoothAdapter.getRemoteDevice//蓝牙适配器通过调用getRemoteDevice()方法获取给定的蓝牙硬件地址的BluetoothDevice对象//有效的蓝牙地址必须是6个字节,即使没有找到设备,也得返回有效的6个字节,当然,估计就是6个0final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);if (device == null){Log.w(TAG, "Device not found. Unable to connect.");return false;}// We want to directly connect to the device, so we are setting the// autoConnect// parameter to false./* 调用device中的connectGatt连接到远程设备 *///connectGatt是BluetoothDevice类下的方法//功能是向BLE设备发起连接,然后得到一个BluetoothGatt类型的返回值,利用这个返回值可以进行下一步操作//connectGatt方法往往是和BluetoothGatt类的connect方法一起使用//两个方法的运行逻辑是://先使用connectGatt方法发起连接,连接状态的改变会回调callback对象中的onConnectionStateChange// (需要自己定义一个BluetoothGattCallBack对象并重写onConnectionStateChange)// 并返回一个BluetoothGatt对象,这时BluetoothGatt已经实例化,下一次连接可以调用connect重新连接。mBluetoothGatt = device.connectGatt(this, false, mGattCallback);Log.d(TAG, "Trying to create a new connection.");mBluetoothDeviceAddress = address;mConnectionState = STATE_CONNECTING;System.out.println("device.getBondState==" + device.getBondState());return true;}
取消连接
/*** @Title: disconnect* @Description: TODO(取消蓝牙连接)* @return void* @throws*/
public void disconnect()
{if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.disconnect();}
1.4.4 BluetoothGattCallback 回调
这个回调十分重要,主要对BluetoothGatt的蓝牙连接、断开、读、写、特征值变化等的回调监听,然后我们可以将这些回调信息通过广播机制传播回给广播监听器
/* 连接远程设备的回调函数 */
// BluetoothGattCallback是一个抽象类,目的是用于实现 BluetoothGatt的回调
// 用于将结果传递给用户,例如连接状态等,以及任何进一步对GATT客户端的操作
// 因为BluetoothGattCallback是一个抽象类,因此需要对里面的方法进行重写
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback()
{// 连接状态变化时回调,用来检测蓝牙是否连接成功与否,成功失败两种情况的操作在这里设置@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status,int newState){String intentAction;if (newState == BluetoothProfile.STATE_CONNECTED)//连接成功{ // 连接成功后的操作intentAction = ACTION_GATT_CONNECTED; // 连接外设成功(GATT服务端)mConnectionState = STATE_CONNECTED; // 设备连接完毕/* 通过广播更新连接状态 */broadcastUpdate(intentAction); // 广播更新,查看是否有数据更新Log.i(TAG, "Connected to GATT server.");// Attempts to discover services after successful connection.Log.i(TAG, "Attempting to start service discovery:"+ mBluetoothGatt.discoverServices());} else if (newState == BluetoothProfile.STATE_DISCONNECTED)//连接失败{ //连接失败后的操作intentAction = ACTION_GATT_DISCONNECTED;// 连接外设失败(GATT服务端)mConnectionState = STATE_DISCONNECTED; // 设备无法连接Log.i(TAG, "Disconnected from GATT server.");broadcastUpdate(intentAction);}}/** 重写onServicesDiscovered,发现蓝牙服务 会在蓝牙连接的时候调用** */@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status){ // GATT_SUCCESS表示GATT操作完成if (status == BluetoothGatt.GATT_SUCCESS)//发现蓝牙服务成功{broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);Log.i(TAG, "--onServicesDiscovered called--");} else{Log.w(TAG, "onServicesDiscovered received: " + status);System.out.println("onServicesDiscovered received: " + status);}}/** 特征值的读写* */@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status){if (status == BluetoothGatt.GATT_SUCCESS){Log.i(TAG, "--onCharacteristicRead called--");//从特征值读取数据// characteristic是特征值,而特征值是用16bit或者128bit,16bit是官方认证过的,128bit是可以自定义的// 这里的两步操作第一步获得二进制的特征值,第二步将其变成字符串byte[] sucString = characteristic.getValue();String string = new String(sucString);//将数据通过广播到Ble_Activity//broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);}}/** 特征值的改变* */@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic){System.out.println("++++++++++++++++");broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);// ACTION_DATA_AVAILABLE: 接受来自设备的数据,可以通过读或通知操作获得}/** 特征值的写* */@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);if (status == BluetoothGatt.GATT_SUCCESS) {//发送完成mSendState = true;Log.d("AppRun"+getClass().getSimpleName(),"发送完成");}}/** 读描述值* */@Overridepublic void onDescriptorRead(BluetoothGatt gatt,BluetoothGattDescriptor descriptor, int status){// TODO Auto-generated method stub// super.onDescriptorRead(gatt, descriptor, status);Log.w(TAG, "----onDescriptorRead status: " + status);byte[] desc = descriptor.getValue();if (desc != null){Log.w(TAG, "----onDescriptorRead value: " + new String(desc));}}/** 写描述值* */@Overridepublic void onDescriptorWrite(BluetoothGatt gatt,BluetoothGattDescriptor descriptor, int status){// TODO Auto-generated method stub// super.onDescriptorWrite(gatt, descriptor, status);Log.w(TAG, "--onDescriptorWrite--: " + status);}/** 读写蓝牙信号值* */// Rssi是蓝牙的接受信号强度@Overridepublic void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status){// TODO Auto-generated method stub// super.onReadRemoteRssi(gatt, rssi, status);Log.w(TAG, "--onReadRemoteRssi--: " + status);broadcastUpdate(ACTION_DATA_AVAILABLE, rssi);}@Overridepublic void onReliableWriteCompleted(BluetoothGatt gatt, int status){// TODO Auto-generated method stub// super.onReliableWriteCompleted(gatt, status);Log.w(TAG, "--onReliableWriteCompleted--: " + status);}};
1.4.5设置特征值变化通知
为了让手机APP接收蓝牙设备发送的数据,必须要设置这个setCharacteristicNotification()方法,这个十分重要。否则,手机APP将无法接受蓝牙设备的数据
MainActivity代码
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
BluetoothLeService类的代码
/*** @Title: setCharacteristicNotification* @Description: TODO(设置特征值通变化通知)* @param @param characteristic(特征值)* @param @param enabled (使能)* @return void* @throws*/ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));// 其中UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"是HC-08蓝牙设备的监听UUIDif (enabled){clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);} else{clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);}mBluetoothGatt.writeDescriptor(clientConfig); }
1.4.6读取特征值
开启对特征值的读
MainActivity代码
mBluetoothGatt.readCharacteristic(characteristic);
BluetoothLeService类的代码
/*** @Title: readCharacteristic* @Description: TODO(读取特征值)* @param @param characteristic(要读的特征值)* @return void 返回类型* @throws*/ public void readCharacteristic(BluetoothGattCharacteristic characteristic) {if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.readCharacteristic(characteristic);}
在蓝牙设备连接成功后自动读一次特征值,如果读成功,将返回BluetoothLeService类中的OnDataAvailableListener接口,并进入如下的方法
public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status);
1.4.7写入特征值
开启对特征值的写,也就是向蓝牙外设写入数据
MainActivity代码
mBluetoothLeService.writeCharacteristic(gattCharacteristic);
BluetoothLeService类的代码
// 写入特征值 public void writeCharacteristic(byte[] bytes) {if (mBluetoothAdapter == null || mBluetoothGatt == null) {Log.w(TAG, "BluetoothAdapter not initialized");return;}mList.add(bytes);//mBluetoothGatt.writeCharacteristic(characteristic); }
这是完成手机APP向蓝牙设备写数据的操作
1.4.8获取已连接蓝牙的所有服务
/*** @Title: getSupportedGattServices* @Description: TODO(得到蓝牙的所有服务)* @param @return 无* @return List<BluetoothGattService>* @throws*/
public List<BluetoothGattService> getSupportedGattServices()
{if (mBluetoothGatt == null)return null;return mBluetoothGatt.getServices();}
返回已经连接蓝牙设备的所有服务
1.4.9读取蓝牙设备的RSSI值
该方法返回的是已连接的蓝牙设备的信号值(RSSI
),而RSSI值是蓝牙的信号值,离得越远信号越小,反之亦然
// 读取RSSi
public void readRssi()
{if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.readRemoteRssi();
}
该方法返回的是已连接的蓝牙设备的信号值(RSSI
)
1.4.10发送广播
通过广播的形式将数据发出去,在MainActivity中通过设置过滤器接收对应的广播
//广播意图
private void broadcastUpdate(final String action, int rssi)
{final Intent intent = new Intent(action);intent.putExtra(EXTRA_DATA, String.valueOf(rssi));sendBroadcast(intent);
}
//广播意图
private void broadcastUpdate(final String action)
{final Intent intent = new Intent(action);sendBroadcast(intent);
}/* 广播远程发送过来的数据 */
public void broadcastUpdate(final String action,final BluetoothGattCharacteristic characteristic)
{final Intent intent = new Intent(action);//从特征值获取数据final byte[] data = characteristic.getValue();MainActivity.revDataForCharacteristic =data;if (data != null && data.length > 0){final StringBuilder stringBuilder = new StringBuilder(data.length);for (byte byteChar : data){stringBuilder.append(String.format("%02X ", byteChar));Log.i(TAG, "***broadcastUpdate: byteChar = " + byteChar);}Log.e("AppRunTime","测试一");intent.putExtra("BLE_BYTE_DATA", data);intent.putExtra(EXTRA_DATA, new String(data));System.out.println("broadcastUpdate for read data:"+ new String(data));}sendBroadcast(intent);
}
1.5广播监听器:
广播的目的:
- 让别人能发现自己,对于一个不广播的设备,周围设备感觉不到其存在的,因此,要让别的设备能发现,则必须向外广播,在广播中可以带上丰富的数据,比如设备的能力,设备名字以及其他自定义的数据,这也就有了第二种可能
- 给不需要建立连接的应用广播数据,比如一个BLE温度计,其本身可以不接收任何连接,而可以选择通过广播将温度发送出去。检测者只要监听广播就能获取当前的温度
扫描者只有在收到广播数据后,才能去与广播者建立连接。广播是周期性的将广播数据从广播通道上发送出去
意图过滤器:
IntentFilter翻译成中文就是“意图过滤器”,主要用来过滤隐式意图。当用户进行一项操作的时候,Android系统会根据配置的 “意图过滤器” 来寻找可以响应该操作的组件,服务。这里的意图过滤器就是让用户对服务端也就是硬件外设进行操作
1.5.1注册/取消注册广播监听
在官方Demo中,便用了广播来作为activity和service之间的数据传递;MainActivity开启了前面的服务之后,就在MainActivity中注册了这个mGattUpdateReceiver广播,以下代码是MainActivity中的
// 取消注册广播和IntentFilter
@Override
protected void onDestroy()
{super.onDestroy();//解除广播接收器unregisterReceiver(mGattUpdateReceiver);mBluetoothLeService = null;
}// Activity出来时候,绑定广播接收器,监听蓝牙连接服务传过来的事件
// 在官方demo中,广播接收器也叫广播监听器,用广播实现activity和service的数据传递
// 在MainActivity执行了bindService,开启了蓝牙服务
// 而在这里,就通过registerReceiver注册了mGattUpdateReceiver广播和IntentFilter
@Override
protected void onResume()
{super.onResume();//绑定广播接收器registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());if (mBluetoothLeService != null){//根据蓝牙地址,建立连接final boolean result = mBluetoothLeService.connect(mDeviceAddress);}
}
/* 意图过滤器 */
private static IntentFilter makeGattUpdateIntentFilter()
{final IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);return intentFilter;
}
上述代码利用registerReceiver()和unregisterReceiver()方法完成注册和取消注册广播,下面的代码是设置广播接收和过滤器
1.5.2广播回调监听
广播回调监听,便是MainActivity接收从Service发送过来的信息,上面说到的上文有说到BluetoothService类的方法BluetoothGattCallback,就是从这里发送广播的
/*** 广播接收器,负责接收BluetoothLeService类发送的数据*/
// 下面是对前面注册的广播的回调监听,作用是接受从Service发送回来的信息
// 从BluetoothGattCallback中发送广播,这里接受信息
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver()
{@Overridepublic void onReceive(Context context, Intent intent){final String action = intent.getAction();if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action))//Gatt连接成功{mConnected = true;//status = "connected";//更新连接状态//updateConnectionState(status);System.out.println("BroadcastReceiver :" + "device connected");} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED//Gatt连接失败.equals(action)){mConnected = false;//status = "disconnected";//更新连接状态//updateConnectionState(status);System.out.println("BroadcastReceiver :"+ "device disconnected");} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED//发现GATT服务器.equals(action)){// Show all the supported services and characteristics on the// user interface.//获取设备的所有蓝牙服务//这里留意一下:当连接成功后,首先service那边会发现服务特征值,通过广播传输回来,然后执行下面的方法displayGattServices(mBluetoothLeService.getSupportedGattServices());System.out.println("BroadcastReceiver :"+ "device SERVICES_DISCOVERED");} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action))//有效数据{//处理发送过来的数据try {if (intent.getExtras().getString(BluetoothLeService.EXTRA_DATA)!=null) {displayData(intent.getExtras().getString(BluetoothLeService.EXTRA_DATA), intent);System.out.println("BroadcastReceiver onData:"+ intent.getStringExtra(BluetoothLeService.EXTRA_DATA));}}catch (Exception e){e.printStackTrace();}}}
};
在接收广播的代码中,displayGattServices()是进一步的完成发现服务,而displayData()则是进一步的完成数据接收处理
1.5.3处理数据的输入和获取
private static BluetoothGattCharacteristic target_chara = null;
//蓝牙service,负责后台的蓝牙服务
private static BluetoothLeService mBluetoothLeService;
private Handler mhandler = new Handler();
/*** @Title: displayGattServices* @Description: TODO(处理蓝牙服务)* @param* @return void* @throws*/
// 处理数据的输入和获取
private void displayGattServices(List<BluetoothGattService> gattServices)
{if (gattServices == null)return;String uuid = null;String unknownServiceString = "unknown_service";String unknownCharaString = "unknown_characteristic";// 服务数据,可扩展下拉列表的第一级数据ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();// 特征数据(隶属于某一级服务下面的特征值集合)ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>();// 部分层次,所有特征值集合mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();// Loops through available GATT Services.for (BluetoothGattService gattService : gattServices){// 获取服务列表HashMap<String, String> currentServiceData = new HashMap<String, String>();uuid = gattService.getUuid().toString();// 查表,根据该uuid获取对应的服务名称。SampleGattAttributes这个表需要自定义。gattServiceData.add(currentServiceData);System.out.println("Service uuid:" + uuid);ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>();// 从当前循环所指向的服务中读取特征值列表List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>();// Loops through available Characteristics.// 对于当前循环所指向的服务中的每一个特征值for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){charas.add(gattCharacteristic);HashMap<String, String> currentCharaData = new HashMap<String, String>();uuid = gattCharacteristic.getUuid().toString();if (gattCharacteristic.getUuid().toString().equals(HEART_RATE_MEASUREMENT)){// 测试读取当前Characteristic数据,会触发mOnDataAvailable.onCharacteristicRead()mhandler.postDelayed(new Runnable(){@Overridepublic void run(){// TODO Auto-generated method stubmBluetoothLeService.readCharacteristic(gattCharacteristic);}}, 200);// 接受Characteristic被写的通知,收到蓝牙模块的数据后会触发mOnDataAvailable.onCharacteristicWrite()mBluetoothLeService.setCharacteristicNotification(gattCharacteristic, true);target_chara = gattCharacteristic;// 设置数据内容// 往蓝牙模块写入数据// mBluetoothLeService.writeCharacteristic(gattCharacteristic);}List<BluetoothGattDescriptor> descriptors = gattCharacteristic.getDescriptors();for (BluetoothGattDescriptor descriptor : descriptors){System.out.println("---descriptor UUID:"+ descriptor.getUuid());// 获取特征值的描述mBluetoothLeService.getCharacteristicDescriptor(descriptor);// mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,// true);}gattCharacteristicGroupData.add(currentCharaData);}// 按先后顺序,分层次放入特征值集合中,只有特征值mGattCharacteristics.add(charas);// 构件第二级扩展列表(服务下面的特征值)gattCharacteristicData.add(gattCharacteristicGroupData);}}
1.5.4数据的接收
下面的代码完成数据的接收,将其显示到scrollview中
/*** @Title: displayData* @Description: TODO(接收到的数据在scrollview上显示)* @param @param rev_string(接受的数据)* @return void* @throws*/
private void displayData(String rev_string, Intent intent)
{try {byte[] data = intent.getByteArrayExtra("BLE_BYTE_DATA");if(data==null)System.out.println("data is null!!!!!!");if (receptionHex)rev_string = bytesToHexString(data);elserev_string = new String(data, 0, data.length, "GB2312");//GB2312编码} catch (UnsupportedEncodingException e) {// TODO Auto-generated catch blocke.printStackTrace();}bluedata = rev_string;//更新UIrunOnUiThread(new Runnable(){@Overridepublic void run(){// rev_tv.setText(bluedata);if(bluedata.length() == RFID ){rfid = bluedata;rev_tv.setText(rfid);}else if(bluedata.length() == DISTANCE ){Log.d("data","*"+bluedata+"*");int a = bluedata.charAt(1) - '0';int b = bluedata.charAt(4) - '0';int c = bluedata.charAt(7) - '0';distance = a * 100 + b * 10 + c;if(distance>100 && distance < 500 )takephoto = false;rev_tv.setText(new Integer(distance).toString());}}});}
1.5.5发送数据
下面的代码是在BluetoothLeService类中,用于给外设的蓝牙模块写入数据的方法
// 发送数据
public void startSend(final BluetoothGattCharacteristic characteristic){if (mBluetoothAdapter == null || mBluetoothGatt == null) {Log.w(TAG, "BluetoothAdapter not initialized");return;}Log.d("AppRun"+getClass().getSimpleName(),"添加完成,开始发送");if (mList.size() != 0){new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0;i<mList.size();) {try {if (mSendState) {Thread.sleep(5);characteristic.setValue(mList.get(i));mSendState = false;mBluetoothGatt.writeCharacteristic(characteristic);++i;} else {Log.d("AppRun"+getClass().getSimpleName(),"等待中..");Thread.sleep(20);}}catch (Exception e){e.printStackTrace();}}Log.d("AppRun"+getClass().getSimpleName(),"发送完毕..");mList.clear();}}).start();}
}
下面是将数据进行分包,因为每次蓝牙传输数据只能是20个字节,如果一个数据超过20个字节,必须要分成n个存放20字节的包
/*** 将数据分包** **/
public int[] dataSeparate(int len)
{int[] lens = new int[2];lens[0]=len/20;lens[1]=len%20;return lens;
}/*** 将16进制字符串转换为byte[]*/
public static byte[] hexString2ByteArray(String bs) {if (bs == null) {return null;}int bsLength = bs.length();if (bsLength % 2 != 0) {bs = "0"+bs;bsLength = bs.length();}byte[] cs = new byte[bsLength / 2];String st;for (int i = 0; i < bsLength; i = i + 2) {st = bs.substring(i, i + 2);cs[i / 2] = (byte) Integer.parseInt(st, 16);}return cs;
}//byte数组转String
public static String bytesToHexString(byte[] bArray) {StringBuffer sb = new StringBuffer(bArray.length);String sTemp;for (int i = 0; i < bArray.length; i++) {sTemp = Integer.toHexString(0xFF & bArray[i]);if (sTemp.length() < 2)sb.append(0);sb.append(sTemp.toUpperCase());}int length = sb.length();if (length == 1||length == 0){return sb.toString();}if (length%2==1){sb.insert(length-1," ");length= length-1;}for (int i = length;i>0;i=i-2){sb.insert(i," ");}return sb.toString();
}
Android蓝牙BLE开发相关推荐
- Android 蓝牙BLE开发详解
Android 蓝牙BLE开发详解 由于年初接手了个有关蓝牙BLE的项目,开始了对蓝牙ble的学习,经过长时间的慢慢学习(学得太慢,太拖了),终于了解了该怎么写蓝牙BLE,现在就给大家分享一下. 一. ...
- Android蓝牙BLE开发(一)-基本原理
公司有需求要做蓝牙BLE传输,经查阅后发现关于BLE开发的知识还真不多.对于BLE开发的同学来说,我的建议是先快速了解一下BLE的基本原理,磨刀不误砍柴工. 什么是BLE BLE全称Bluetooth ...
- Android低功耗蓝牙(BLE)开发(二)
在上一篇文章Android低功耗蓝牙(BLE)开发(一)中我们了解了BLE的相关概念,这里我们来实际用代码演示安卓进行BLE连接和通讯的功能.本文代码基于Android5.0以上(API 21) 1. ...
- 微信小程序蓝牙BLE开发实战——案例(二)
微信小程序蓝牙BLE开发实战(二) 上篇主要介绍在开发过程中应用到相关API操作.接下来介绍个人在项目开发中应用蓝牙BLE一些事情. 由于时间比较仓促, 有些注释没那么详细.请理解~写的不好欢迎各位大 ...
- Android蓝牙BLE的详细讲解
我今天分享的主题是 Android 上低功耗蓝牙的实践.这个主题比较小众.我在过去的一年多的时间里,主要是在做低功耗蓝牙相关的开发.接触过程中发现,BLE 的开发和通常的 Android APP 的开 ...
- android蓝牙BLE 有源码 有视频
前序 Google在android 4.3(API Level 18)的android版本中引入了低功耗蓝牙BLE核心API.低功耗蓝牙BLE也就是我们经常说的蓝牙4.0, 该技术拥有极低的运行和 ...
- 微信小程序蓝牙BLE开发——关于进制转换(四)
微信小程序蓝牙BLE开发--进制转换 这段时间开发共享设备,对接蓝牙BLE设备通信协议,过程中用到一些进制转换, 记录下方便使用. 有些参考大神们,感谢分享. 文章目录 微信小程序蓝牙BLE开发--进 ...
- Android 蓝牙通信开发
Android 蓝牙通信开发 Receiver的设置 一.Receiver1(蓝牙状态的改变通过广播接收) 二.Receiver2(蓝牙搜索到设备.绑定设备(配对)通过广播接收) 服务端代码 客户端代 ...
- Android 蓝牙 bluetoothle 开发
前段时间项目中用到了bluetoothle 方面的开发,项目结束后总结一下,开发的流程与一些思路: 主要步骤 一:注册蓝牙所需权限 二:Android 6.0 以上权限获取定位权限 三:开启蓝牙 四: ...
最新文章
- iOS App Launch Option
- 实战案例丨小型企业如何从IPv4迁移至IPv6
- 【IT资讯】全新编程语言V发布
- RabbitMQ预取值
- js获取datagrid行,但是行改变了肿么办?
- layui导航栏页面滚动固定_网站建设页面导航如何降低用户寻找的时间
- Java 多线程 —— ReentrantLock 与 Condition
- python子图之间的距离_python与图论的桥梁——igraph
- spark java foreach_Spark Java使用DataFrame的foreach/foreachPartition
- Atitit mybatis 翻页解决法 目录 1.1. 翻页模式还有js翻页前端翻页更加简单	1 1.2. 逻辑分页使用类RowBounds vs 物理分页 offset模式	1 1.3.
- python作业代做_CSC1001作业代做、代写Programming Methodology作业、代做Python实验作业、Python程序设计作业调试...
- Word | 图片被文字遮挡
- freeswitch实战六(呼叫转移)
- Java实现 LeetCode 517 超级洗衣机
- 【哈密顿图】算法分析
- 《Linux命令行与shell脚本大全》笔记
- 数据库事物和分布式事物
- VSCode下载慢的问题解决
- 冯诺依曼 计算机名言,约翰·冯·诺伊曼留给我们的名言之一
- 使用 xrdp 远程登录ubuntu出现黑屏或者花屏