Dorisoy.BLERemote
一款将 Android 手机变为蓝牙遥控器,支持 HID 协议控制 Android TV / Android 设备,并内置 BLE GATT 透传调试终端。

功能概览
蓝牙 HID 遥控
- 方向键 / D-pad:TV 导航按键(上下左右、确认、返回、主页)
- 媒体控制:播放/暂停、上一首/下一首、快进/快退、音量加减、静音、亮度
- TV 专属:频道切换、待机、彩色功能键(红/绿/蓝/黄)、字幕
- 鼠标模式:触控板手势控制(左/右/中键、滚轮;可选陀螺仪控制)
- 软键盘:支持 17 种语言布局(含高级键盘和虚拟键盘),发送文字到 TV
设备管理
- 显示已配对蓝牙设备,支持收藏和自动连接
- 扫描并配对新设备(含从远程设备发起配对指南)
- 手动输入蓝牙 MAC 地址连接
BLE GATT 透传终端
- 扫描并连接 BLE 设备(支持 HM-10、JDY 等 FFE0/FFE1/FFE2 透传模块)
- 双 Tab 界面:
蓝牙服务:展示已连接设备所有 GATT 服务和特征,显示属性(读/写/通知)
实时日志:时间戳日志,支持过滤、HEX/ASCII 切换、统计(RX/TX 包数和字节数)
- 数据收发:ASCII / HEX 两种输入格式,支持循环定时发送
- 写入类型选择:Default / No Response / Signed
- 字符编码:UTF-8 优先(支持中文),回退 Latin-1 逐字节显示
- BLE MTU 分包自动合并(50ms 超时缓冲)
应用小部件 & 通知
- 桌面小部件快捷连接(Glance AppWidget)
- 连接状态持久通知,通知栏快捷遥控按钮
个性化设置
- 主题:跟随系统 / 浅色 / 深色 / 纯黑(OLED 省电)
- 动态颜色(Android 12+)
- 导航模式:D-pad 或触控板
- 音量键自定义操作、全屏模式、陀螺仪鼠标等
国际化
- 支持两套语言资源:英文(默认)、简体中文
- 系统语言自动切换,无需手动选择
技术架构
架构模式
采用 MVVM + Clean Architecture 三层结构:
presentation/ UI 层(Activity、ViewModel、Screen)
├── activities/ MainActivity
├── viewmodel/ BleViewModel、RemoteViewModel 等
├── services/ 蓝牙 HID 后台服务、通知服务
└── glance/ 桌面小部件
domain/ 业务层(纯 Kotlin,无 Android 依赖)
├── entities/ BleLogEntry、BleServiceInfo、BleStats、BleWriteType 等
├── repositories/ Repository 接口定义
├── usecases/ BleUseCase、RemoteUseCase 等
└── resources/ 通用 Result 封装
data/ 数据层(具体实现)
├── bluetooth/ 经典蓝牙 HID 实现
│ └── ble/ BleGattManager(GATT 连接、扫描)
├── repositories/ Repository 实现类
├── dataStore/ 设置持久化(DataStore Preferences)
└── sensor/ 陀螺仪传感器
依赖注入
使用 Koin 4.x,在 common/injections/Module.kt 集中注册所有依赖:
// 数据层
single { BleGattManager(get()) }
single<BleRepository> { BleRepositoryImpl(get()) }
// 业务层
single { BleUseCase(get()) }
// 表现层
viewModel { BleViewModel(get()) }
状态管理
- 所有状态通过
StateFlow 暴露,UI 使用 collectAsStateWithLifecycle() 收集
BleGattManager 持有:connectionState、logEntries、services、stats、connectedDeviceName、connectedDeviceAddress
技术栈
| 类别 |
库 / 版本 |
| 语言 |
Kotlin 2.1.10 |
| 最低 SDK |
API 28(Android 9) |
| 目标 SDK |
API 36(Android 16) |
| UI |
Jetpack Compose + Material3 |
| Compose BOM |
2025.09.01 |
| 导航 |
Navigation Compose 2.8.9 |
| 依赖注入 |
Koin AndroidX Compose 4.1.1 |
| 序列化 |
kotlinx.serialization 1.8.1 |
| 持久化 |
DataStore Preferences 1.1.3 |
| 小部件 |
Glance AppWidget 1.1.1 |
| 后台任务 |
WorkManager 2.10.0 |
| 构建工具 |
AGP 8.9.1 |
项目结构
Dorisoy.BLERemote/
├── app/src/main/
│ ├── java/com/atharok/btremote/
│ │ ├── common/
│ │ │ ├── extensions/ Kotlin 扩展函数
│ │ │ ├── injections/ Koin 模块
│ │ │ └── utils/ 工具类、常量
│ │ ├── data/
│ │ │ ├── bluetooth/ 经典蓝牙 HID 实现
│ │ │ │ └── ble/ BleGattManager、BleScanner
│ │ │ ├── dataStore/ 设置存储
│ │ │ ├── repositories/ 仓库实现
│ │ │ └── sensor/ 陀螺仪
│ │ ├── domain/
│ │ │ ├── entities/ 数据实体
│ │ │ ├── repositories/ 仓库接口
│ │ │ ├── usecases/ 业务用例
│ │ │ └── resources/ Result 类型
│ │ ├── presentation/
│ │ │ ├── activities/ MainActivity
│ │ │ ├── viewmodel/ 各 ViewModel
│ │ │ ├── services/ 后台蓝牙服务
│ │ │ └── glance/ 桌面小部件
│ │ └── ui/
│ │ ├── app/ 应用入口 Composable
│ │ ├── components/ 通用 UI 组件
│ │ ├── navigation/ 导航路由定义
│ │ ├── screens/ 各页面(BleTerminalScreen 等)
│ │ ├── theme/ 颜色、字体、主题
│ │ └── views/ 遥控、键盘、鼠标等子视图
│ └── res/
│ ├── values/ 英文字符串资源
│ ├── values-zh-rCN/ 简体中文字符串资源
│ └── values-v31/ API 31+ 主题配置
├── gradle/
│ └── libs.versions.toml 版本目录
└── build.gradle.kts 根构建脚本
开发向导
环境要求
| 工具 |
版本要求 |
| Android Studio |
Hedgehog 或更高 |
| JDK |
17+ |
| Gradle |
8.x(由 Wrapper 自动管理) |
| Android SDK |
API 28–36 |
克隆与构建
# 克隆仓库
git clone <repo-url>
cd BtRemote
# 调试构建(default flavor)
./gradlew assembleDefaultDebug
# Google Play 风味构建
./gradlew assembleGplayDebug
# Release 构建(需配置签名环境变量)
./gradlew assembleDefaultRelease
签名配置(Release)
通过环境变量配置,无需修改代码:
export ANDROID_KEY_STORE_FILE=/path/to/keystore.jks
export ANDROID_KEY_STORE_PASSWORD=your_store_password
export ANDROID_KEY_ALIAS=your_key_alias
export ANDROID_KEY_PASSWORD=your_key_password
Product Flavors
| Flavor |
包名 |
用途 |
default |
com.atharok.btremote |
F-Droid / 自托管发布 |
gplay |
com.atharok.btremote.gplay |
Google Play 发布 |
新增国际化字符串
- 在
res/values/strings.xml 添加英文字符串
- 在
res/values-zh-rCN/strings.xml 添加对应中文翻译
- 代码中通过
context.getString(R.string.xxx) 或 Compose stringResource(R.string.xxx) 引用
BLE GATT 透传模块开发说明
核心文件
| 文件 |
职责 |
BleGattManager.kt |
GATT 连接、服务发现、数据收发、日志、统计 |
BleScanner.kt |
BLE 扫描 |
BleRepository.kt |
仓库接口 |
BleRepositoryImpl.kt |
仓库实现 |
BleUseCase.kt |
业务用例 |
BleViewModel.kt |
UI 状态管理、循环发送 |
BleTerminalScreen.kt |
双 Tab 终端界面 |
BleScanScreen.kt |
BLE 扫描界面 |
关键实体
// 日志条目(sealed class)
BleLogEntry.ConnectionEvent // 连接事件
BleLogEntry.DataReceived // 接收数据
BleLogEntry.DataSent // 发送数据
// 服务/特征信息
BleServiceInfo(uuid, characteristics: List<BleCharInfo>)
BleCharInfo(uuid, propertiesStr, canRead, canWrite, canNotify, ...)
// 统计
BleStats(rxPackets, rxBytes, txSuccessPackets, txSuccessBytes, ...)
// 写入类型
BleWriteType.DEFAULT / NO_RESPONSE / SIGNED
数据流
BLE Notify 回调
→ bufferReceivedBytes()(50ms 合包)
→ appendReceivedMessage(bytes)
→ bytesToText()(UTF-8 优先解码)
→ BleLogEntry.DataReceived → _logEntries StateFlow
→ BleTerminalScreen(LazyColumn 实时更新)
文本解码策略
// BleGattManager.bytesToText()
fun bytesToText(bytes: ByteArray): String {
val utf8 = bytes.toString(Charsets.UTF_8)
if (!utf8.contains('\uFFFD')) return utf8 // UTF-8 有效(中文等)
// 回退:逐字节 Latin-1,控制字符显示为 '.'
return bytes.map { b ->
val c = b.toInt() and 0xFF
when {
c == 0x09 || c == 0x0A || c == 0x0D -> c.toChar()
c < 0x20 || c == 0x7F -> '.'
else -> c.toChar()
}
}.joinToString("")
}
版本历史
当前版本:v1.0.0(versionCode 10)
许可证
本项目基于开源协议发布,详见 LICENSE。
评论