MUI——使用Android插件形式进行BLE开发

一、BLE是什么

BLE低功耗蓝牙。

二、Android开发BLE前的准备

  • Eclipse或者其他编辑器
  • [email protected]_20181226-1
  • Android手机一部
  • BLE设备一个(无需配对的设备)

三、基本概念

  • 通用属性配置文件 (GATT)
    GATT 配置文件是一种通用规范,内容针对在 BLE 链路上发送和接收称为“属性”的简短数据片段。目前所有低功耗应用配置文件均以 GATT 为基础。

  • 服务Service
    服务是一个特征对象的集合,通常UUID为16字节的数。
    一个Service可以包含多个特征对象。

  • 特征对象character
    服务中的细粒度的操作对象,例如指定可读、可写等

四、权限的引入和说明

  • 如果需要使用BLE
<uses-permission android:name="android.permission.BLUETOOTH"/>
  • 想要手机能操控蓝牙
<!-- 引入此项权限前,必须使用BLUETOOTH权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
  • 需要声明手机只支持BLE设备
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
  • Android 6.0开始,扫描周围BLE设备需要位置权限信息

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  • APP需要适配Android 9(API 28)或更低版本
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

ACCESS_COARSE_LOCATION访问CellIDWiFi,只要当前设备可以接收到基站的服务信号,便可获得位置信息。(COARSE英文原意为:粗略的,可以理解为这种方式获得的位置信息是相对粗略的数据)。

如果您的应用适配 Android 9(API 级别 28)或更低版本,则您可以声明 ACCESS_COARSE_LOCATION 权限而非 ACCESS_FINE_LOCATION 权限。

五、开发步骤

5.1、获取 BletoothAdapter

获取手机端BLE适配器对象。

获取 BletoothAdapter对象之前,需要判断手机是否支持BLE!

Android版本在4.2,也就是API 17 才能支持BLE,但目前绝大多数的手机都不止这个版本了。

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1
				|| !activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
	Log.i("checkBLEState()-->", "您的手机不支持BLE");
	//提示手机端不支持BLE设备
	JSUtil.execCallback(BLEWebview, checkBLECallBackID, "PHONE_NOT_BLE", JSUtil.OK, true);
	return;
}

要获取到 BletoothAdapter 操作对象之前,需要优先获取设备管理器对象 BluetoothManager

// 蓝牙管理,这是系统服务可以通过getSystemService(BLUETOOTH_SERVICE)的方法获取实例
BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Activity.BLUETOOTH_SERVICE);

获取后,就可以采取管理器对象获取到手机端蓝牙适配器对象了。

BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();

如果手机BLE未开启,判断并自动开启。

if (!mBluetoothAdapter.isEnabled()) {
	Log.i("mBluetoothAdapter-->", "蓝牙未开启,提示用户开启蓝牙");
	//自动开启BLE
	mBluetoothAdapter.enable();
	JSUtil.execCallback(BLEWebview, checkBLECallBackID, "PROMPT_USER_CLICK_BTN", JSUtil.OK, true);
	return;
}

开启BLE是需要时间的,在源码中有这方面的说明,这里需要对此注意点。

5.2、扫描周围BLE设备

成功获取到 BluetoothAdapter对象,且BLE为开启状态时,就可以扫描周围的设备了。

开启扫描操作后,需要起一个定时器,达到超过指定时间关闭扫描的功能。
因为扫描是一个极耗电的操作。

	/**
	 * 执行扫描操作
	 * 
	 * @param scantimes
	 *            扫描时间
	 */
	public void startScan(final int scantimes) {
		// 用户可能在扫描中,关闭蓝牙
		if (mBluetoothAdapter == null) {
			Log.i("startScan-->", "蓝牙未开启");
			return;
		}
		// 正在扫描跳出扫描
		if (isScanning) {
			Log.i("startScan-->", "正在扫描");
			return;
		}
		// 此项方法只会执行一次 避免多次造成资源浪费
		new Timer().schedule(new TimerTask() {
			@Override
			public void run() {
				isScanning = false;
				if (mBluetoothAdapter != null) {
				    mBluetoothAdapter.stopLeScan(scanAroundBleDeviceCallback);
				}
				Log.i("startScan-->", "定时线程");
			}
		}, scantimes);

//除了调用扫描流程外,还需要增加一个扫描操作的回执监听
	/**
	 * 扫描操作 扫描到设备后的回调操作
	 */
	public BluetoothAdapter.LeScanCallback scanAroundBleDeviceCallback = new LeScanCallback() {

		@Override
		public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
			Log.i("scanAroundBleDeviceCallback -- onLeScan -- mac -->", String.valueOf(device.getAddress()));
			Log.i("scanAroundBleDeviceCallback -- onLeScan -- rssi -->", String.valueOf(rssi));
		}	
	};

虽然有定时关闭扫描操作的逻辑,不过在扫描和连接操作通常都是很迅速的,在扫描到设备后,需要及时的关闭扫描操作,避免资源的消耗。

mBluetoothAdapter.stopLeScan(scanAroundBleDeviceCallback);

5.3、连接操作

当扫描到设备,需要进行连接操作。

try {
	BluetoothGatt mBluetoothGatt = mBluetoothDevice.connectGatt(activity, false, gattcallback);
} catch (Exception e) {
	e.printStackTrace();
}

//连接操作也需要一个回执监听
// GATT连接的回调函数
	public BluetoothGattCallback gattcallback = new BluetoothGattCallback() {
		/**
		 * 蓝牙连接状态改变后调用 此回调 (断开,连接)
		 * 
		 * @param gatt
		 * @param status
		 * @param newState
		 */
		@Override
		public void onConnectionStateChange(final BluetoothGatt gatt, int status, final int newState) {
			super.onConnectionStateChange(gatt, status, newState);
			Log.i("onConnectionStateChange--status->", String.valueOf(status));
			Log.i("onConnectionStateChange--newState->", String.valueOf(newState));
			if (newState == BluetoothProfile.STATE_CONNECTED && mBluetoothDevice != null && mBluetoothGatt != null) {// 连接成功
				Log.i("onConnectionStateChange-->", "连接成功");

			} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 连接失败 或者连接断开都会调用此方法
				Log.i("连接回调-->", "getConnectedState()");
				if (getConnectedState() == BluetoothProfile.STATE_CONNECTED) {
					Log.i("onConnectionStateChange-->", "连接伪失败");
					return;
				}
				Log.i("onConnectionStateChange-->", "连接失败");

		}

		/**
		 * 连接成功后开启服务的回调
		 * 
		 * @param gatt
		 * @param status
		 */
		@Override
		public void onServicesDiscovered(BluetoothGatt gatt, int status) {
			super.onServicesDiscovered(gatt, status);
			// 寻找服务时
			if (status == BluetoothGatt.GATT_SUCCESS) {
				Log.i("onServicesDiscovered-->", "服务开启成功");

			} else {// 未发现该设备的服务
		}

		/**
		 * 读特征值的回调
		 */
		@Override
		public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic character_v1, int status) {
			Log.i("onCharacteristicRead--status-->", String.valueOf(status));
			// 读取到值,在这里读数据
			if (status == BluetoothGatt.GATT_SUCCESS) {// 0
			} else if (status == BluetoothGatt.GATT_FAILURE) {
				Log.i("onCharacteristicRead-->", "读取数据失败");
			}
		}

		/**
		 * 给指定Characteristic发送数据后的回调
		 * 
		 * @param gatt
		 * @param characteristic
		 * @param status
		 */
		@Override
		public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
			if (status == BluetoothGatt.GATT_SUCCESS) {// 写入成功
				Log.i("onCharacteristicWrite-->", "写入数据成功");
				

			} else if (status == BluetoothGatt.GATT_FAILURE) {// 写入失败
				Log.i("onCharacteristicWrite-->", "写入数据失败");
				
			} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {// 没有写入的权限

			}
		}

		// 订阅了远端设备的Characteristic信息后,
		// 当远端设备的Characteristic信息发生改变后,回调此方法
		@Override
		public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
			super.onCharacteristicChanged(gatt, characteristic);

		}

		@Override
		public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
			super.onDescriptorRead(gatt, descriptor, status);
		}

		@Override
		public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
			super.onDescriptorWrite(gatt, descriptor, status);
		}

		@Override
		public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
			super.onReliableWriteCompleted(gatt, status);
		}

		@Override
		public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
			super.onReadRemoteRssi(gatt, rssi, status);
		}

	};

连接操作执行后,可能会出现如下的现象:

在日志中得到连接失败的回执信息,但是设备却和手机端是正常连接的。
此时则需要判断连接伪状态

使用如下代码判断其是否为连接状态:

	/**
	 * 获取app和ble设备的连接状态
	 * 
	 * @return 100表示未执行次方法;0disConnected;1connecting;2connected;3disConnecting
	 * 
	 */
	public int getConnectedState() {
		int state = 100;
		if (lock_bluetoothManager != null && (lock_mac != null | lock_mac == "") && mBluetoothAdapter != null
				&& mBluetoothAdapter.isEnabled()) {
			BluetoothDevice bdevice = mBluetoothAdapter.getRemoteDevice(lock_mac);
			// 蓝牙连接状态为0,1,2,3 为了避免出问题初始化为100
			/**
			 * STATE_DISCONNECTED 0 STATE_CONNECTING 1 STATE_CONNECTED 2 STATE_DISCONNECTING
			 * 3
			 */
			state = lock_bluetoothManager.getConnectionState(bdevice, BluetoothProfile.GATT);
		}
		Log.i("getConnectedState()方法调用,返回数据为-->", String.valueOf(state));
		return state;
	}

5.4、开启通信服务

连接成功后,至进行正常的数据通信操作前,需要优先开通通信服务。否则出现数据无法发送的问题。

new Timer().schedule(new TimerTask() {
	@Override
	public void run() {
		Log.i("定时开启服务--->", "开启服务");
		try {
			// 连接成功后去发现该连接的设备的服务
			mBluetoothGatt.discoverServices();
		} catch (NullPointerException e) {
			e.printStackTrace();
			Log.i("discoverServices()-->", "捕获到空指针异常   执行退出操作");
			// 这里只是使用FIND_NO_SERVICE 让按钮状态值能够回复
			JSUtil.execCallback(BLEWebview, checkBLECallBackID, "FIND_NO_SERVICE", JSUtil.OK, true);
			quit();
		}
	}
}, discoverServicesTimes);

连接成功后,不是说立即就能开启通信服务的,连接是一个过程信息,还需要等待系统连接稳定,通过多次的试验,此处的discoverServicesTimes参数值最少为600毫秒

当开启通信服务后,成功或者失败的回执信息,会出现在 BluetoothGattCallback中的onServicesDiscovered中。

5.5、手机和设备之间的通信

开启通信服务后,往往就可以根据公司的通信协议进行通信操作测试了。
由于需要进行通信操作,此时则需要根据ServiceCharcter获取通信操作对象信息,实现数据的读取发送

	/**
	 * 获取服务封装方法
	 * 
	 * @param uuid
	 *            每个service的唯一标识信息
	 * @return
	 */
	public BluetoothGattService getService(UUID uuid) {
		Log.i("getService-->", "getConnectedState()");
		if (mBluetoothGatt != null && getConnectedState() == BluetoothProfile.STATE_CONNECTED) {
			return mBluetoothGatt.getService(uuid);
		} else {
			return null;
		}
	}

	/**
	 * 获取特征封装方法
	 * 
	 * @param serviceUUID
	 *            指定的service标识信息
	 * @param characteristicUUID
	 *            每个service中都包含多个characteristic对象信息
	 * @return
	 */
	public BluetoothGattCharacteristic getCharacteristic(String serviceUUID, String characteristicUUID) {
		BluetoothGattService service = getService(UUID.fromString(serviceUUID));
		if (service == null) {
			return null;
		}
		final BluetoothGattCharacteristic gattCharacteristic = service
				.getCharacteristic(UUID.fromString(characteristicUUID));
		if (gattCharacteristic != null) {
			return gattCharacteristic;
		} else {
			return null;
		}
	}

本次的案例中采取 读/写 方式进行,后续退出 notify 类型。

发送指定的信息:

	/**
	 * * 向指定的特征对象中发送相关的信息
	 * 
	 * @param mBluetoothGattCharacteristic
	 *            发送信息的目标的对象地址
	 * @param sendByteVal
	 *            发送的数据信息
	 * @param mBluetoothGattCharacteristic
	 * @param sendByteVal
	 * @return true 发送执行成功 false发送执行失败
	 */
	public boolean writeToChar(BluetoothGattCharacteristic mBluetoothGattCharacteristic, byte[] sendByteVal) {
		boolean writeToCharSuccess = false;
		// 只有在状态为连接中,才能进行数据的发送
		if (mBluetoothGattCharacteristic != null && mBluetoothGatt != null
				&& getConnectedState() == BluetoothProfile.STATE_CONNECTED) {
			mBluetoothGattCharacteristic.setValue(sendByteVal);
			writeToCharSuccess = mBluetoothGatt.writeCharacteristic(mBluetoothGattCharacteristic);
		}
		return writeToCharSuccess;
	}

读取指定的信息:

	/**
	 * 读取特征值数据函数封装
	 */
	public void READBLEDATA() {
		// 虽然做了拦截操作 但为了保证安全健壮性
		if (readCharf3 == null) {
			Log.i("FIND_SERVICE-->", "获取F3特征对象失败");
			JSUtil.execCallback(BLEWebview, checkBLECallBackID, "GET_CHAR_FAIL", JSUtil.OK, true);
			quit();
			return;
		}
		// 对象存在 则进行读取特征值数据操作
		readCharacteristic(readCharf3);
	}

	/**
	 * 读取指定特征对象中的数据操作
	 * 
	 * @param characteristic
	 *            具体的特征对象
	 */
	public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
		if (mBluetoothAdapter == null || mBluetoothGatt == null) {
			return;
		}
		mBluetoothGatt.readCharacteristic(characteristic);
	}

六、注意事项

  • Android 6.0及其以后,需要追加位置权限,否则无法扫描到指定的对象
  • 蓝牙的断开连接操作,需要先尝试断开,等待断开操作回执后,再次断开释放资源
//尝试断开
mBluetoothGatt.disconnect();
//等待回执连接状态为断开连接后,再次释放资源
mBluetoothGatt.close();

虽然直接调用 mBluetoothGatt.close();也能达到断开的目的,但多次操作后,会造成Android手机蓝牙的死机事件!
mBluetoothGatt.close();是释放资源操作。
mBluetoothGatt.disconnect();才是断开连接操作,但是断开连接操作动作过于缓慢!

  • 写操作每次最大长度为20字节数,超过此长度命令需要做分包处理。
  • BLE蓝牙的频率和WIFI、微波炉等大部分设备相似,环境复杂时,会出现获取特征对象失败或扫描不到(扫描到错误)设备信息。

七、参考资料

Android BLE 蓝牙编程(一)
低功耗蓝牙官网文档

猜你喜欢

转载自blog.csdn.net/qq_38322527/article/details/106661856
今日推荐