Android功能记录(六) ------ Android手机通过蓝牙(BLE)与硬件进行通信
程序员文章站
2024-03-24 23:14:10
...
2018.8.30修正
本文只是作为记录,不是最终的版本,因为本文只用了vivo手机测试,如果各位想要修正后的,请点击此处
- 参考1:http://www.jcodecraeer.com/a/opensource/2017/1106/8705.html
- 参考2:https://blog.csdn.net/qq_30552993/article/details/51822726
好不容易折腾出来了,一把辛酸泪啊,参考2方法试了不行,但是我参照参考2的结构写的,参考1是可以用的,包括连接/发送/接收,在此把完整代码放出来,其中掺杂我自己项目要用的代码
主要工具类BleUtil (基本都注释了):
package ylcx.com.blue;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.vise.baseble.ViseBle;
import com.vise.baseble.callback.IBleCallback;
import com.vise.baseble.callback.IConnectCallback;
import com.vise.baseble.callback.scan.IScanCallback;
import com.vise.baseble.callback.scan.ScanCallback;
import com.vise.baseble.common.PropertyType;
import com.vise.baseble.core.BluetoothGattChannel;
import com.vise.baseble.core.DeviceMirror;
import com.vise.baseble.core.DeviceMirrorPool;
import com.vise.baseble.exception.BleException;
import com.vise.baseble.model.BluetoothLeDevice;
import com.vise.baseble.model.BluetoothLeDeviceStore;
import com.vise.baseble.model.resolver.GattAttributeResolver;
import com.vise.baseble.utils.HexUtil;
import com.vise.log.ViseLog;
import com.vise.xsnow.cache.SpCache;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import static com.vise.utils.handler.HandlerUtil.runOnUiThread;
public class BleUtil {
private static final String TAG = "BleUtil";
public static String WriteUUID = "00008877-0000-1000-8000-00805f9b34fb"; //APP发送命令
public static String ReadUUID = "00008888-0000-1000-8000-00805f9b34fb"; //BLE用于回复命令
private static final String LIST_NAME = "NAME";
private static final String LIST_UUID = "UUID";
public static final String WRITE_CHARACTERISTI_UUID_KEY = "write_uuid_key";
public static final String WRITE_DATA_KEY = "write_data_key";
private Context mContext;
private static BleUtil mInstance;
private DeviceMirror deviceMirror;
private SpCache mSpCache;
private BTUtilListener mListener;
private BluetoothLeDevice mCurDevice;
private List<BluetoothLeDevice> listDevice;
private List<BluetoothGattService> mGattServices;//服务
// 设备特征值集合
private List<List<BluetoothGattCharacteristic>> mGattCharacteristics = new ArrayList<>();
public boolean isConnect = false;
private DeviceMirrorPool mDeviceMirrorPool;
private DataManage dataManage;
public static synchronized BleUtil getInstance() {
if (mInstance == null) {
mInstance = new BleUtil();
}
return mInstance;
}
//初始化
public void setContext(Context context) {
mContext = context;
init();
}
public void init() {
listDevice = new ArrayList<>();
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
showToast("BLE不支持此设备!");
((Activity) mContext).finish();
}
if (mContext == null) {
return;
}
//蓝牙相关配置修改
ViseBle.config()
.setScanTimeout(-1)//扫描超时时间,这里设置为永久扫描
//.setScanRepeatInterval(5 * 1000)//扫描间隔5秒
.setConnectTimeout(10 * 1000)//连接超时时间
.setOperateTimeout(5 * 1000)//设置数据操作超时时间
.setConnectRetryCount(3)//设置连接失败重试次数
.setConnectRetryInterval(1000)//设置连接失败重试间隔时间
.setOperateRetryCount(3)//设置数据操作失败重试次数
.setOperateRetryInterval(1000)//设置数据操作失败重试间隔时间
.setMaxConnectCount(3);//设置最大连接设备数量
//蓝牙信息初始化,全局唯一,必须在应用初始化时调用
ViseBle.getInstance().init(mContext.getApplicationContext());
mDeviceMirrorPool = ViseBle.getInstance().getDeviceMirrorPool();
mSpCache = new SpCache(mContext);
scanLeDevice(true);
}
private ScanCallback mLeScanCallback = new ScanCallback(new IScanCallback() {
@Override
//发现设备
public void onDeviceFound(final BluetoothLeDeviceStore bluetoothLeDeviceStore) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//扫描到后与Rockrt进行比较
List<BluetoothLeDevice> BlueList = bluetoothLeDeviceStore.getDeviceList();
for (int i = 0; i < BlueList.size(); i++) {
if (!listDevice.contains(BlueList.get(i))) {
//不重复添加
listDevice.add(BlueList.get(i));
}
}
mListener.onLeScanDevices(listDevice);
}
});
}
@Override
public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) {
ViseLog.i("scan finish " + bluetoothLeDeviceStore);
}
@Override
public void onScanTimeout() {
ViseLog.i("scan timeout");
}
});
//扫描/停止扫描设备
public void scanLeDevice(final boolean enable) {
if (enable) {
startScan();
} else {
stopScan();
}
}
//开始扫描BLE设备
private void startScan() {
ViseBle.getInstance().startScan(mLeScanCallback);
mListener.onLeScanStart();
}
//停止扫描BLE设备
private void stopScan() {
ViseBle.getInstance().stopScan(mLeScanCallback);
mListener.onLeScanStop();
}
/**
* 连接回调
*/
private IConnectCallback mGattCallback = new IConnectCallback() {
@Override
public void onConnectSuccess(final DeviceMirror devicemirror) {
deviceMirror = devicemirror;
ConnectResult(true);
mListener.onConnected(mCurDevice);
}
@Override
public void onConnectFailure(BleException exception) {
showToast("连接失败");
ConnectResult(false);
}
@Override
public void onDisconnect(boolean isActive) {
showToast("断开连接");
ConnectResult(false);
mListener.onDisConnected(mCurDevice);
}
};
/**
* 操作数据回调,下面读写操作绑定的回调就是这个
*/
private IBleCallback bleCallback = new IBleCallback() {
@Override
public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) {
if (data == null) {
return;
}
if(bluetoothGattInfo != null && (bluetoothGattInfo.getPropertyType() == PropertyType.PROPERTY_READ))
{
Log.d(TAG, "Data: " +HexUtil.encodeHexStr(data) );
receiveData(HexUtil.encodeHexStr(data));
}
mCurDevice = bluetoothLeDevice;
if(bluetoothGattInfo != null && (bluetoothGattInfo.getPropertyType() == PropertyType.PROPERTY_WRITE))
{
BindReadChannel(); //因为我这里是根据发送的命令返回,所以当写成功时重新绑定读通道
}
}
@Override
public void onFailure(BleException exception) {
if (exception == null) {
return;
}
Log.d(TAG,"callback fail:" + exception.getDescription());
}
};
//连接后的处理
public void ConnectResult(boolean isSuccess) {
if (isSuccess) {
Log.d(TAG, "连接成功");
if(deviceMirror != null && deviceMirror.getBluetoothGatt() != null)
{
isConnect = true;
GetServicesAndCharacteristic(deviceMirror.getBluetoothGatt().getServices()); //获取设备根据GATT服务显示该服务下的所有特征值
BindWriteChannel();
BindReadChannel();
}
}else{
isConnect = false;
//重新发起连接
Connect();
}
}
//绑定通道
public void BindWriteChannel()
{
if(deviceMirror != null && deviceMirror.getBluetoothGatt() != null)
{
setPro(WriteUUID);
GattServicesW();
}
}
public void BindReadChannel(){
if(deviceMirror != null && deviceMirror.getBluetoothGatt() != null)
{
setPro(ReadUUID); //定位该特征为在所有特征值里面的绝对位置
GattServicesR();
}
}
//获取设备根据GATT服务显示该服务下的所有特征值
private void GetServicesAndCharacteristic(final List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
String uuid;
final String unknownServiceString = "unknown_service";
final String unknownCharaString = "unknown_characteristic";
final List<Map<String, String>> gattServiceData = new ArrayList<>();
final List<List<Map<String, String>>> gattCharacteristicData = new ArrayList<>();
mGattServices = new ArrayList<>();
mGattCharacteristics = new ArrayList<>();
// 循环遍历services
for (final BluetoothGattService gattService : gattServices) {
final Map<String, String> currentServiceData = new HashMap<>();
uuid = gattService.getUuid().toString();
currentServiceData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
final List<Map<String, String>> gattCharacteristicGroupData = new ArrayList<>();
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
final List<BluetoothGattCharacteristic> charas = new ArrayList<>();
// Loops through available Characteristics.
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
final Map<String, String> currentCharaData = new HashMap<>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattServices.add(gattService);
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
}
//定位读写特征值的坐标
private int prox=0; //定位读写操作的坐标
private int proy=0; //定位读写操作的坐标
private void setPro(String uuid){
int x=0; int y=0;
//BluetoothGattService是GATT数据的大集合,
for (BluetoothGattService goo : mGattServices) {
//List是BluetoothGattService里面的子集,包含一部分数据
List<BluetoothGattCharacteristic> lt2 = goo.getCharacteristics();
for (BluetoothGattCharacteristic goo2 : goo.getCharacteristics()) {
if (goo2.getUuid().toString().equals(uuid)) {
prox = x; //BluetoothGattService第一层数据
proy = y; //BluetoothGattService第二层数据
}
y++;
}
x++; y=0;
}
}
//绑定通道
private void GattServicesW() {
//获取特定的服务
final BluetoothGattService service = mGattServices.get(prox);
//获取特定的特征
final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(prox).get(proy);
//负责设置操作
mSpCache.put(WRITE_CHARACTERISTI_UUID_KEY + mCurDevice.getAddress(), characteristic.getUuid().toString());
DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(mCurDevice);
if (deviceMirror != null) {
BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
.setBluetoothGatt(deviceMirror.getBluetoothGatt())
.setPropertyType(PropertyType.PROPERTY_WRITE)
.setServiceUUID(service.getUuid())
.setCharacteristicUUID(characteristic.getUuid())
.setDescriptorUUID(null)
.builder();
deviceMirror.bindChannel(bleCallback, bluetoothGattChannel);
}
}
private void GattServicesR() {
//获取特定的服务
final BluetoothGattService service = mGattServices.get(prox);
//获取特定的特征
final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(prox).get(proy);
final int charaProp = characteristic.getProperties();
DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(mCurDevice);
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
.setBluetoothGatt(deviceMirror.getBluetoothGatt())
.setPropertyType(PropertyType.PROPERTY_READ)
.setServiceUUID(service.getUuid())
.setCharacteristicUUID(characteristic.getUuid())
.setDescriptorUUID(null)
.builder();
deviceMirror.bindChannel(bleCallback, bluetoothGattChannel);
deviceMirror.readData();
}
}
//接收数据,对其进行处理
private void receiveData( String data) {
dataManage.addMessageAndShow(data,"read");
}
//连接设备
public void connectLeDevice(int devicePos) {
scanLeDevice(false); //停止扫描
mCurDevice = listDevice.get(devicePos);
Connect();
}
//连接/重连设备
private void Connect() {
if (!ViseBle.getInstance().isConnect(mCurDevice)) {
Log.d(TAG, "开始连接:" + mCurDevice);
ViseBle.getInstance().connect(mCurDevice, mGattCallback); //开始连接
mListener.onConnecting(mCurDevice);
}
}
// 断开设备连接
private void disConnGatt() {
if (ViseBle.getInstance().isConnect(mCurDevice)) {
ViseBle.getInstance().disconnect(mCurDevice);
listDevice = new ArrayList<>();
mListener.onDisConnected(mCurDevice);
}
}
private Queue<byte[]> dataInfoQueue = new LinkedList<>();
//发送数据
public void sendMsg(String s) {
if(isConnect)
{
mSpCache.put(WRITE_DATA_KEY + mCurDevice.getAddress(), s); //尚不清楚,反正就有
if (dataInfoQueue != null) {
dataInfoQueue.clear();
dataInfoQueue = splitPacketFor20Byte(HexUtil.decodeHex(s.toCharArray()));
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
send(mCurDevice);
}
});
}
}
}
private void send(final BluetoothLeDevice bluetoothLeDevice) {
if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
if (dataInfoQueue.peek() != null && deviceMirror != null) {
deviceMirror.writeData(dataInfoQueue.poll());
}
if (dataInfoQueue.peek() != null) {
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
send(bluetoothLeDevice);
}
}, 100);
}
}
}
//数据分包
private Queue<byte[]> splitPacketFor20Byte(byte[] data) {
Queue<byte[]> dataInfoQueue = new LinkedList<>();
if (data != null) {
int index = 0;
do {
byte[] surplusData = new byte[data.length - index];
byte[] currentData;
System.arraycopy(data, index, surplusData, 0, data.length - index);
if (surplusData.length <= 20) {
currentData = new byte[surplusData.length];
System.arraycopy(surplusData, 0, currentData, 0, surplusData.length);
index += surplusData.length;
} else {
currentData = new byte[20];
System.arraycopy(data, index, currentData, 0, 20);
index += 20;
}
dataInfoQueue.offer(currentData);
} while (index < data.length);
}
return dataInfoQueue;
}
private void showToast(String message) {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
public void setBTUtilListener(BTUtilListener listener) {
mListener = listener;
}
public void setDataManage(DataManage dataMan)
{
dataManage = dataMan;
}
public interface BTUtilListener {
String bluName = "Rocket";
void onLeScanStart(); // 扫描开始
void onLeScanStop(); // 扫描停止
void onLeScanDevices(List<BluetoothLeDevice> listDevice); //扫描得到的设备
void onConnected(BluetoothLeDevice mCurDevice); //设备的连接
void onDisConnected(BluetoothLeDevice mCurDevice); //设备断开连接
void onConnecting(BluetoothLeDevice mCurDevice); //设备连接中
void onDisConnecting(BluetoothLeDevice mCurDevice); //设备连接失败
}
}
BluListen 类:
package ylcx.com.blue;
import com.vise.baseble.model.BluetoothLeDevice;
import java.util.List;
public class BluListen implements BleUtil.BTUtilListener {
public String TAG = "BluListen";
@Override
public void onLeScanStart() {
}
@Override
public void onLeScanStop() {
}
@Override
public void onLeScanDevices(List<BluetoothLeDevice> listDevice) {
for(int i = 0;i<listDevice.size();i++)
{
if(listDevice.get(i).getName() != null && listDevice.get(i).getName().equals(bluName))
{
BleUtil.getInstance().connectLeDevice(i);
}
}
}
@Override
public void onConnected(BluetoothLeDevice mCurDevice) {
}
@Override
public void onDisConnected(BluetoothLeDevice mCurDevice) {
}
@Override
public void onConnecting(BluetoothLeDevice mCurDevice) {
}
@Override
public void onDisConnecting(BluetoothLeDevice mCurDevice) {
}
}
接收数据处理类DataManage:
package ylcx.com.blue;
import android.content.Context;
import android.os.Handler;
import java.util.ArrayList;
import java.util.List;
public class DataManage {
public MainActivity mainActivity;
public Context mContext;
public List<String> message;
public void setMainActivity(Context context,MainActivity activity)
{
mContext = context;
message = new ArrayList<>();
mainActivity = activity;
}
public void addMessageAndShow(final String msg, final String isSendOrWrite)
{
message.add(msg);
new Handler(mContext.getMainLooper()).post(new Runnable() {
@Override
public void run() {
mainActivity.setDataText(msg,isSendOrWrite);
}
});
}
}
MainActivity调用:
package ylcx.com.blue;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public Context mContext;
public Button send;
public EditText str;
public TextView receive;
public Button sendRegister;
public Button sendLight;
public Button sendClock;
public String TAG ="MainActivity";
public DataManage dataManage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
send = findViewById(R.id.send);
str = findViewById(R.id.sendStr);
receive = findViewById(R.id.receive);
receive.setMovementMethod(ScrollingMovementMethod.getInstance());//滚动
turnOnBlueTooth(this,0);
BleUtil.getInstance().setBTUtilListener(new BluListen());
BleUtil.getInstance().setContext(mContext);
dataManage = new DataManage();
dataManage.setMainActivity(this,this);
BleUtil.getInstance().setDataManage(dataManage);
}
@Override
protected void onResume() {
super.onResume();
}
/**
* 打开蓝牙
*/
public void turnOnBlueTooth(Activity activity, int requestCode) {
//通过Intent获取蓝牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//获取返回结果
activity.startActivityForResult(intent, requestCode);
}
public void setDataText(String msg,String isSendOrWrite)
{
String str = receive.getText().toString();
str += "\n";
str += isSendOrWrite+":"+msg;
receive.setText(str);
}
}
AndroidManifest权限申请:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ylcx.com.blue">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
补充 : 如果你当前发送的message是没有返回值的,那么不应该BindReadChannel(绑定读通道),还没试过notice方式,用到了再补充吧
2018/7/10 补充notice方式:
private void GattServicesR() {
//获取特定的服务
final BluetoothGattService service = mGattServices.get(prox);
//获取特定的特征
final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(prox).get(proy);
final int charaProp = characteristic.getProperties();
DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(mCurDevice);
//使用notify方式注册
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
.setBluetoothGatt(deviceMirror.getBluetoothGatt())
.setPropertyType(PropertyType.PROPERTY_NOTIFY)
.setServiceUUID(service.getUuid())
.setCharacteristicUUID(characteristic.getUuid())
.setDescriptorUUID(null)
.builder();
deviceMirror.bindChannel(NotifyCallback, bluetoothGattChannel);
deviceMirror.registerNotify(false);
deviceMirror.setNotifyListener(bluetoothGattChannel.getGattInfoKey(), new IBleCallback() {
@Override
public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {
Log.d(TAG, "Data: " +HexUtil.encodeHexStr(data) );
receiveData(HexUtil.encodeHexStr(data));
}
@Override
public void onFailure(BleException exception) {
}
});
}
}
监听蓝牙断开以及开启:
private BroadcastReceiver mStatusReceive = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch(intent.getAction()){
case BluetoothAdapter.ACTION_STATE_CHANGED:
int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch(blueState){
case BluetoothAdapter.STATE_TURNING_ON:
break;
case BluetoothAdapter.STATE_ON:
Log.d(TAG, "onReceive: STATE_ON");
//开始扫描
startScan();
break;
case BluetoothAdapter.STATE_TURNING_OFF:
break;
case BluetoothAdapter.STATE_OFF:
break;
case BluetoothAdapter.ERROR:
Log.d(TAG, "onReceive: ERROR");
break;
case BluetoothAdapter.STATE_DISCONNECTED:
Log.d(TAG, "onReceive: disconnect");
break;
}
break;
}
}
};
在蓝牙连接成功那里注册:
IntentFilter statusFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mStatusReceive, statusFilter);