如何在单台Android设备创建虚拟BLE GATT Server并实现数据本地存储?
单台Android设备实现BLE虚拟设备模拟与数据本地保存方案
核心思路
在单台Android设备上同时运行BLE GATT Server(模拟目标打印外设)和BLE GATT Client(作为中央设备连接自身模拟的外设),拦截并持久化目标APK发送给BLE设备的打印数据。
分步实现
1. 模拟匹配目标设备的BLE GATT Server
- 先用
nRF Connect等工具扫描原BLE打印设备,获取其服务UUID、特征UUID及设备名称,确保虚拟设备的参数与原设备完全一致,否则目标APK无法识别。 - 实现GATT Server,注册对应服务和可写入特征,在特征写入回调中捕获数据并写入本地文件。
2. 本地BLE Client连接自身模拟的GATT Server
- 启动BLE扫描,过滤自身模拟的设备名称或UUID,扫描到后发起连接并完成服务发现,让目标APK能正常连接到虚拟设备。
3. 关键代码示例
GATT Server初始化与数据捕获
private var gattServer: BluetoothGattServer? = null private val gattServerCallback = object : GattServerCallback() { override fun onCharacteristicWriteRequest( device: BluetoothDevice?, requestId: Int, characteristic: BluetoothGattCharacteristic?, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray? ) { super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) // 捕获打印数据并写入本地文件 value?.takeIf { it.isNotEmpty() }?.let { data -> val outputFile = File(getExternalFilesDir(null), "ble_print_data.txt") FileOutputStream(outputFile, true).use { fos -> fos.write(data) fos.write("\n--- 分割线 ---\n".toByteArray()) } } // 向客户端发送成功响应 gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null) } } // 初始化GATT Server fun initGattServer(context: Context) { val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter gattServer = bluetoothAdapter.openGattServer(context, gattServerCallback) // 替换为原BLE打印设备的服务和特征UUID val serviceUuid = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb") val charUuid = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb") val service = BluetoothGattService(serviceUuid, BluetoothGattService.SERVICE_TYPE_PRIMARY) val writeChar = BluetoothGattCharacteristic( charUuid, BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE ) service.addCharacteristic(writeChar) gattServer?.addService(service) }
本地BLE Client扫描与连接
private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult?) { super.onScanResult(callbackType, result) result?.device?.takeIf { it.name == "VirtualBLEPrinter" }?.let { virtualDevice -> bluetoothAdapter.bluetoothLeScanner.stopScan(this) // 连接到自身模拟的GATT Server virtualDevice.connectGatt(context, false, object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { super.onConnectionStateChange(gatt, status, newState) if (newState == BluetoothProfile.STATE_CONNECTED) { gatt?.discoverServices() } } }) } } } // 启动扫描 fun startLocalScan(bluetoothAdapter: BluetoothAdapter) { val scanFilters = listOf(ScanFilter.Builder().setDeviceName("VirtualBLEPrinter").build()) val scanSettings = ScanSettings.Builder().build() bluetoothAdapter.bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback) }
4. 权限配置
在AndroidManifest.xml中声明必要权限,并在运行时动态申请:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 12+ 新增权限 --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
5. 注意事项
- 部分Android设备存在自身BLE Client无法连接自身GATT Server的限制,可尝试修改虚拟设备名称或使用随机UUID测试。
- 确保目标APK连接BLE设备的逻辑无特殊绑定(如MAC地址锁定),否则需额外处理。
- 数据写入路径建议使用
getExternalFilesDir(null),避免Android 10+的存储权限限制。
内容的提问来源于stack exchange,提问作者Eonassis Oliveira Santos
相关产品推荐
相关产品推荐

