安卓蓝牙4.0和蓝牙多通道
1.首先连接蓝牙的时候需要在配置文件manifest中增加权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
这个时候我们才算正式开始进入主题
2.判断你的移动设备是否支持蓝牙
如果你想声明你的应用程序只能在支持BLE的设备上运行,可以将下面声明包含进你的应用程序manifest文<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
想让你的应用程序也能够在不支持BLE的设备上运行,你就应该将上面标签中的属性设置为required=”false”。然后在运行的过程中使用PackageManager.hasSystemFeature()方法来判断设备是否支持BLE:
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
Toast.makeText(this, “没有可提供的蓝牙设备”, Toast.LENGTH_SHORT).show();
finish();
}
3.获取蓝牙适配器 BluetoothAdapter
先声明 private BluetoothAdapter mBluetoothAdapter;
然后
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//如果蓝牙设配器里面没有数据的话就让它返回
if(mBluetoothAdapter == null){
Toast.makeText(this, “没有可提供的蓝牙设备”, Toast.LENGTH_SHORT).show();
finish();
return;
}
4.搜索蓝牙
/**
* 1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
*/
//我这里使用了需要连接多台蓝牙设备 所有放在list里面
private LinkedList mDeviceContainer = new LinkedList();
private ArrayList mDeviceList = new ArrayList();
private void scanLeDevice(final boolean enable){
if(enable){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(SCAN_PERIOD);
if(mScanning){ //10秒钟后停止搜索
mScanning = false; //状态标记为false
mBluetoothAdapter.stopLeScan(mLeScanCallback);
invalidateOptionsMenu();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索
}else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback); //停止搜索
}
invalidateOptionsMenu();
}
5.搜到设备的时候有一个回调接口
private BluetoothAdapter.LeScanCallback mLeScanCallback = new LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(!mDeviceContainer.isEmpty()){
if(!isEquals(device)){
connectBle(device);
}
}else{
connectBle(device);
}
}
});
}
};
6.启用蓝牙模块
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
mBluetoothLeService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBluetoothLeService = ((TemperatureServices.LocalBinder)service).getService();
if(!mBluetoothLeService.initialize()){
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
Log.e(TAG, "mBluetoothLeService is okay");
}
};
/**
*接收从BlutoothLeService发送过来的广播
包扣 连接 未连接 以及发送过来的数据
*/
private final BroadcastReceiver mGattUpdateRecevicer = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Bundle extras = intent.getExtras();
if(TemperatureServices.ACTION_GATT_CONNECTED.equals(action)){
Log.i(TAG, "Only gatt, just wait");
}else if(TemperatureServices.ACTION_GATT_DISCONNECTED.equals(action)){
if(!mDeviceList.isEmpty()){
String strAddress = intent.getStringExtra("DEVICE_ADDRESS");
if(removeDevice(strAddress)){
int deviceNum = mDeviceList.size() - 1;
numDevice.setText(deviceText+deviceNum);
}
}
invalidateOptionsMenu();
}else if(TemperatureServices.ACTION_GATT_SERVICES_DISCOVERED.equals(action)){
if(!mDeviceContainer.isEmpty()){
String strAddress =intent.getStringExtra("DEVICE_ADDRESS");
for(BluetoothDevice bluetoothDevice:mDeviceContainer){
if(bluetoothDevice.getAddress().equals(strAddress)){
mDeviceList.add(bluetoothDevice);
}
}
}
numDevice.setText(deviceText+mDeviceList.size());
Log.e(TAG, "Discover GATT Services");
invalidateOptionsMenu();
}else if(TemperatureServices.ACTION_DATA_AVAILABLE.equals(action)){
Log.i(TAG, "ACTION_DATA_AVAILABLE");
String data = intent.getStringExtra(TemperatureServices.EXTRA_DATA_TEMP);
if(extras.containsKey(TemperatureServices.EXTRA_DATA_TEMP)){
if (data != null) {
if (mDataField.length() > 500) {
mDataField.setText("");
}
mDataField.append(data);
Log.i("==temp_data==", data); // 打印温度
}
}else if (extras.containsKey(TemperatureService.EXTRA_DATA)) {
// 如果是蓝牙的读取通知到显示
}
}
}
};
/**
* 连接蓝牙
* @param device
*/
private void connectBle(BluetoothDevice device){
mDeviceContainer.add(device);
while (true) {
if(mBluetoothLeService!=null){
mBluetoothLeService.connect(device.getAddress(), this);
break;
}else{
try{
Thread.sleep(250);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
7.最主要的 服务
从上面代码中我们可以看到 有一个mBluetoothLeSerivice 它正是我们创建的BluetoothLeService他继承了Service。接下来这个类我详细讲解。
public class BluetoothLeService extends Service{
private static final String TAG="BluetoothLeService"; //这里是用来打印的
private String mBluetoothDeviceAddress; //蓝牙地址
private BluetoothManager mBluetoothManager; //通过BluetoothManager来获取BluetoothAdapter
private BluetoothGatt mBluetoothGatt; //通过BluetoothGatt可以连接设备(connect),发现服务(discoverServices),并把相应地属性返回到BluetoothGattCallback
//常用的广播
public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; //连接
public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; //断开链接
public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; //发现设备
public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; //官方的demo里面有这四个
//这2个是我自己需要读取的所以增加了
public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; //有数据的时候传输数据
public final static String EXTRA_DATA_TEMP = "com.example.bluetooth.le.EXTRA_DATA_TEMP"; //温度类型变量
private BluetoothAdapter mBluetoothAdapter = null;
private ArrayList<BluetoothGatt> connectionQueue = new ArrayList<BluetoothGatt>(); //创建一个BluetoothGatt的队列
//相当于一个数据类型,它包括一个value和0~n个value的描述(BluetoothGattDescriptor)
public BluetoothGattCharacteristic mNotifyCharacteristic;
private Context mContext; //上下文
//查找服务
public void findService(BluetoothGatt gatt){
List<BluetoothGattService> gattservices = gatt.getServices();
Log.i(TAG,"Count is:"+gattservices.size());
for(BluetoothGattService gattservice : gattservices){
Log.i(TAG,gattservice.getUuid().toString());
//如果服务要等于温度服务
if(gattservice.getUuid().toString().equalsIgnoreCase(GattAttributes.HEALTH_THERMO_SERVICE)){
List<BluetoothGattCharacteristic> gattCharacteristics = gattservice.getCharacteristics();
Log.i(TAG,"Count is:"+gattCharacteristics.size());
for(BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){
String uuidchara = gattCharacteristic.getUuid().toString();
//如果是温度计的类型的话
/**
注意:这里的GattAttributes 这个值相当于
//实际用你们自己的
public static final String HEALTH_TEMP_MEASUREMENT = "0000xxxx-0000-1000-8000-00805f9b34fb";
*/
if(uuidchara.equalsIgnoreCase(GattAttributes.TEMPERATURE_TYPE)){
prepareBroadcastDataRead(gattCharacteristic);
}
//如果是温度计的度数的时候
else if(uuidchara.equalsIgnoreCase(GattAttributes.HEALTH_TEMP_MEASUREMENT)){
prepareBroadcastDataIndicate(gattCharacteristic);
//发现服务获取设备的地址
brocastUpdate(ACTION_GATT_SERVICES_DISCOVERED, gatt.getDevice().getAddress());
}
}
}
}
}
/**
* 最重要的回调
*/
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt,int status,int newState){
String initAction;
Log.i(TAG,"oldStatus = "+status+"NewStates"+newState); //打印出来
if(status == BluetoothGatt.GATT_SUCCESS){ //这个值一般返回的为0
//如果连接成功
if(newState == BluetoothProfile.STATE_CONNECTED){
initAction = ACTION_GATT_CONNECTED;
brocastUpdate(initAction);
//连接之后马上去发现服务
gatt.discoverServices();
}
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){ //断开连接
initAction = ACTION_GATT_DISCONNECTED;
brocastUpdate(initAction, gatt.getDevice().getAddress());
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if(status == BluetoothGatt.GATT_SUCCESS){
Log.w(TAG, "onServicesDiscovered received: " + status);
findService(gatt);
}else{
if(gatt.getDevice().getUuids() == null){
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
};
//这个是读取数据
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if(status == BluetoothGatt.GATT_SUCCESS){
brocastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
//当数据发生改变的时候
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
brocastUpdate(ACTION_DATA_AVAILABLE, characteristic);
};
//写入数据
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
System.out.println("onDescriptorWriteonDescriptorWrite = " + status + ", descriptor =" + descriptor.getUuid().toString());
}
};
};
/**
* 接收广播可读的特征
*/
public void prepareBroadcastDataRead(BluetoothGattCharacteristic gattCharacteristic){
if((gattCharacteristic.getProperties()|BluetoothGattCharacteristic.PROPERTY_READ)>0){
readCharacteristic(gattCharacteristic); //当特征为可读的特征的时候就直接可以读取
}
}
/**
* 接收广播通知特性
*/
public void prepareBroadcastDataIndicate(BluetoothGattCharacteristic gattCharacteristic){
if((gattCharacteristic.getProperties()|BluetoothGattCharacteristic.PROPERTY_INDICATE)>0){
setCharacteristicIndication(gattCharacteristic,
true); //当特征为不可读的时候哪么就是为通知
}
}
/**
* 处理连接蓝牙后进入这里面操作
*/
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder{
public TemperatureServices getService(){
return TemperatureServices.this;
}
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
/**
* 对外事件处理类
* 更新广播事件
*/
private void brocastUpdate(final String action){
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
/**
* 传地址广播
* @param action
* @param strAddress
*/
private void brocastUpdate(final String action,final String strAddress){
final Intent intent = new Intent(action);
intent.putExtra("DEVICE_ADDRESS", strAddress);
sendBroadcast(intent);
}
private void brocastUpdate(final String action,final BluetoothGattCharacteristic characteristic){
final Intent intent = new Intent(action);
//当温度计为温度类型的时候
if(characteristic.getUuid().equals(UUIDDatabase.UUID_HEALTH_THERMOMETER_SENSOR_LOCATION)){
String a = HTMParser.getHealthThermoSensorLocation(characteristic, mContext);
intent.putExtra(EXTRA_DATA, a);
}
else if(characteristic.getUuid().equals(UUIDDatabase.UUID_HEALTH_THERMOMETER)){
String health_temp = HTMParser.getHealthThermo(characteristic, mContext);
intent.putExtra(EXTRA_DATA_TEMP, health_temp);
}
sendBroadcast(intent);
}
/**
* 初始化一个参考本地蓝牙适配器
* @return
*/
public boolean initialize(){
if(mBluetoothManager == null){
mBluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
if(mBluetoothManager == null){
Log.e(TAG,"Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if(mBluetoothAdapter == null){
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
/**
* 连接代码
* @param address
* @param context
* @return
*/
public boolean connect(final String address,Context context){
mContext = context;
if(mBluetoothAdapter == null && address == null){
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if(device == null){
Log.w(TAG,"Device not found. Unable to connect.");
return false;
}
BluetoothGatt bluetoothGatt;
bluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
if(checkGatt(bluetoothGatt)){
connectionQueue.add(bluetoothGatt);
}
Log.d(TAG, "Trying to create a new connection.");
return true;
}
/**
* 检查是否加入了相同的特征 如果相同就移除掉
* @param bluetoothGatt
* @return
*/
private boolean checkGatt(BluetoothGatt bluetoothGatt){
if(!connectionQueue.isEmpty()){
for(BluetoothGatt btg:connectionQueue){
if(btg.equals(bluetoothGatt)){
return false;
}
}
}
return true;
}
/**
* 断开蓝牙连接 一按断开是全部都断开
*/
public void disConnect(){
if(mBluetoothAdapter == null && connectionQueue.isEmpty()){
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
for(BluetoothGatt bluetoothGatt:connectionQueue){
bluetoothGatt.disconnect();
}
}
public void close(){
if(connectionQueue.isEmpty()){
return;
}
listClose(null);
}
/**
* 清除连接蓝牙的数据
* 我这里用的是连接多台的设备,所以清除的时候需要一个一个的移除掉
* @param gatt
*/
private synchronized void listClose(BluetoothGatt gatt){
if(!connectionQueue.isEmpty()){
if(gatt!=null){
for(final BluetoothGatt bluetoothGatt:connectionQueue){
if(bluetoothGatt.equals(gatt)){
bluetoothGatt.close();
new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(250);
connectionQueue.remove(bluetoothGatt);
}catch(Exception ex){
ex.printStackTrace();
}
}
}).start();
}
}
}else{
for(BluetoothGatt bluetoothGatt:connectionQueue){
bluetoothGatt.close();
}
connectionQueue.clear();
}
}
}
/**
* 读取蓝牙数据
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || connectionQueue.isEmpty()) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
for (BluetoothGatt bluetoothGatt : connectionQueue) {
bluetoothGatt.readCharacteristic(characteristic);
}
}
/**
* Enables or disables notification on a give characteristic.
*
* @param characteristic Characteristic to act on.
* @param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || connectionQueue.isEmpty()) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
for(BluetoothGatt bluetoothGatt:connectionQueue){
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
}
/**
* 蓝牙温度计最关键的一步 有些特征只能通过这个 读取 并启用通知更新数据
* Enables or disables indications on a give characteristic.
* 启用或禁用一个给定特性的指示
* @param characteristic Characteristic to act on.
* @param enabled If true, enable indications. False otherwise.
*/
public void setCharacteristicIndication(
BluetoothGattCharacteristic characteristic, boolean enabled) {
String serviceUUID = characteristic.getService().getUuid().toString();
String characteristicUUID = characteristic.getUuid().toString();
Log.i("==TAG==",serviceUUID+" "+characteristicUUID);
for(BluetoothGatt mBluetoothGatt:connectionQueue){
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
return;
}
if (characteristic.getDescriptor(UUID
.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG)) != null) {
if (enabled == true) {
BluetoothGattDescriptor descriptor = characteristic
.getDescriptor(UUID
.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor
.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
} else {
BluetoothGattDescriptor descriptor = characteristic
.getDescriptor(UUID
.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor
.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
}
}
其中我里面用了一些外部的方法代码如下
/**
* Class used for parsing Health temperature related information
* 用于解析温度相关信息的类
*/
public class HTMParser {
private static ArrayList<String> mTempInfo = new ArrayList<String>();
//Byte character format
private static final String BYTE_CHAR_FORMAT = "%02X ";
//Switch case Constants
private static final int CASE_ARMPIT = 1;
private static final int CASE_BODY = 2;
private static final int CASE_EAR_LOBE = 3;
private static final int CASE_FINGER = 4;
private static final int CASE_GIT = 5;
private static final int CASE_MOUTH = 6;
private static final int CASE_RECTUM = 7;
private static final int CASE_TYMPANUM = 8;
private static final int CASE_TOE = 9;
private static final int CASE_TOE_REP = 10;
/**
* Get the thermometer reading
* 温度计的读数
*
* @param characteristic
* @return
*/
public static String getHealthThermo(
BluetoothGattCharacteristic characteristic, Context context) {
String tempUnit = "";
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
byte flagByte = data[0];
if ((flagByte & 0x01) != 0) {
tempUnit = context.getString(R.string.tt_fahren_heit);
} else {
tempUnit = context.getString(R.string.tt_celcius);
}
for (byte byteChar : data)
stringBuilder.append(String.format(BYTE_CHAR_FORMAT, byteChar));
}
final float temperature = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_FLOAT, 1);
//Logger.i("tempRate " + temperature);
String ss = temperature+","+tempUnit;
//mTempInfo.add(1, tempUnit);
return ss;
}
/**
* Get the thermometer sensor location
*
* @param characteristic
* @return
*/
public static String getHealthThermoSensorLocation(
BluetoothGattCharacteristic characteristic, Context context) {
String healthTherSensorLocation = "";
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for (byte byteChar : data)
stringBuilder.append(String.format(BYTE_CHAR_FORMAT, byteChar));
int healthBodySensor = Integer.valueOf(stringBuilder.toString()
.trim());
switch (healthBodySensor) {
case CASE_ARMPIT:
//这个R.string.armpit 里面就是这个airpit的英文意思
healthTherSensorLocation = context.getString(R.string.armpit);
break;
case CASE_BODY:
healthTherSensorLocation = context.getString(R.string.body);
break;
case CASE_EAR_LOBE:
healthTherSensorLocation = context.getString(R.string.ear);
break;
case CASE_FINGER:
healthTherSensorLocation = context.getString(R.string.finger);
break;
case CASE_GIT:
healthTherSensorLocation = context.getString(R.string.intestine);
break;
case CASE_MOUTH:
healthTherSensorLocation = context.getString(R.string.mouth);
break;
case CASE_RECTUM:
healthTherSensorLocation = context.getString(R.string.rectum);
break;
case CASE_TYMPANUM:
healthTherSensorLocation = context.getString(R.string.tympanum);
break;
case CASE_TOE:
healthTherSensorLocation = context.getString(R.string.toe_1);
break;
case CASE_TOE_REP:
healthTherSensorLocation = context.getString(R.string.toe_2);
break;
default:
healthTherSensorLocation = context.getString(R.string.reserverd);
break;
}
}
return healthTherSensorLocation;
}
}
8.总结:
蓝牙连接最主要的是要掌握 BluetoothLeService这个类里面的内容,主要包扣BluetoothGattCallback 回调
这里面是处理蓝牙数据的核心,包扣连接设备回调 断开设备回调 发现服务回调 发现数据回调。多台连接蓝牙设备的时候 把BluetoothGatt 放到一个List里面去。断开的时候也要移次的移除掉 list里面的BluetoothGatt数据。