最近正在研究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开发相关推荐

  1. Android 蓝牙BLE开发详解

    Android 蓝牙BLE开发详解 由于年初接手了个有关蓝牙BLE的项目,开始了对蓝牙ble的学习,经过长时间的慢慢学习(学得太慢,太拖了),终于了解了该怎么写蓝牙BLE,现在就给大家分享一下. 一. ...

  2. Android蓝牙BLE开发(一)-基本原理

    公司有需求要做蓝牙BLE传输,经查阅后发现关于BLE开发的知识还真不多.对于BLE开发的同学来说,我的建议是先快速了解一下BLE的基本原理,磨刀不误砍柴工. 什么是BLE BLE全称Bluetooth ...

  3. Android低功耗蓝牙(BLE)开发(二)

    在上一篇文章Android低功耗蓝牙(BLE)开发(一)中我们了解了BLE的相关概念,这里我们来实际用代码演示安卓进行BLE连接和通讯的功能.本文代码基于Android5.0以上(API 21) 1. ...

  4. 微信小程序蓝牙BLE开发实战——案例(二)

    微信小程序蓝牙BLE开发实战(二) 上篇主要介绍在开发过程中应用到相关API操作.接下来介绍个人在项目开发中应用蓝牙BLE一些事情. 由于时间比较仓促, 有些注释没那么详细.请理解~写的不好欢迎各位大 ...

  5. Android蓝牙BLE的详细讲解

    我今天分享的主题是 Android 上低功耗蓝牙的实践.这个主题比较小众.我在过去的一年多的时间里,主要是在做低功耗蓝牙相关的开发.接触过程中发现,BLE 的开发和通常的 Android APP 的开 ...

  6. android蓝牙BLE 有源码 有视频

    前序 ​ Google在android 4.3(API Level 18)的android版本中引入了低功耗蓝牙BLE核心API.低功耗蓝牙BLE也就是我们经常说的蓝牙4.0, 该技术拥有极低的运行和 ...

  7. 微信小程序蓝牙BLE开发——关于进制转换(四)

    微信小程序蓝牙BLE开发--进制转换 这段时间开发共享设备,对接蓝牙BLE设备通信协议,过程中用到一些进制转换, 记录下方便使用. 有些参考大神们,感谢分享. 文章目录 微信小程序蓝牙BLE开发--进 ...

  8. Android 蓝牙通信开发

    Android 蓝牙通信开发 Receiver的设置 一.Receiver1(蓝牙状态的改变通过广播接收) 二.Receiver2(蓝牙搜索到设备.绑定设备(配对)通过广播接收) 服务端代码 客户端代 ...

  9. Android 蓝牙 bluetoothle 开发

    前段时间项目中用到了bluetoothle 方面的开发,项目结束后总结一下,开发的流程与一些思路: 主要步骤 一:注册蓝牙所需权限 二:Android 6.0 以上权限获取定位权限 三:开启蓝牙 四: ...

最新文章

  1. iOS App Launch Option
  2. 实战案例丨小型企业如何从IPv4迁移至IPv6
  3. 【IT资讯】全新编程语言V发布
  4. RabbitMQ预取值
  5. js获取datagrid行,但是行改变了肿么办?
  6. layui导航栏页面滚动固定_网站建设页面导航如何降低用户寻找的时间
  7. Java 多线程 —— ReentrantLock 与 Condition
  8. python子图之间的距离_python与图论的桥梁——igraph
  9. spark java foreach_Spark Java使用DataFrame的foreach/foreachPartition
  10. Atitit mybatis 翻页解决法 目录 1.1. 翻页模式还有js翻页前端翻页更加简单 1 1.2. 逻辑分页使用类RowBounds vs 物理分页 offset模式 1 1.3.
  11. python作业代做_CSC1001作业代做、代写Programming Methodology作业、代做Python实验作业、Python程序设计作业调试...
  12. Word | 图片被文字遮挡
  13. freeswitch实战六(呼叫转移)
  14. Java实现 LeetCode 517 超级洗衣机
  15. 【哈密顿图】算法分析
  16. 《Linux命令行与shell脚本大全》笔记
  17. 数据库事物和分布式事物
  18. VSCode下载慢的问题解决
  19. 冯诺依曼 计算机名言,约翰·冯·诺伊曼留给我们的名言之一
  20. 使用 xrdp 远程登录ubuntu出现黑屏或者花屏

热门文章

  1. Greenplum【异常 02】使用Navicat的服务器监控工具解决Greenplum数据库锁表问题
  2. SQL 如何让数据库数据以倒序输出
  3. GitHub上24.3kStar的js进度条插件,确定不关注一下?
  4. 界面超漂亮的桌面时钟
  5. 成为一名网络安全工程师,你应该学习什么?
  6. MFC二次有理Bezier曲线升阶
  7. 阿里往事:阿里20周年年会侧记
  8. QT输入窗口之QDial(仪表盘)
  9. linux 4g大小文件夹,Linux: 如何分割文件,不再被 4G 大小限制了
  10. 终于弄明白了ThreadLocal