Lập trình Bluetooth Low Energy (BLE) trên nền tảng Android thường được coi là một thách thức lớn đối với các nhà phát triển. Những vấn đề như quản lý trạng thái phức tạp, hiện tượng "callback hell", xử lý đa luồng và các lỗi không xác định từ hệ thống khiến việc duy trì mã nguồn trở nên khó khăn. Thư viện RxAndroidBle ra đời để giải quyết những bài toán này bằng cách áp dụng mô hình lập trình phản ứng (Reactive Programming).
RxAndroidBle bao bọc các API BLE gốc của Android vào các Observable của RxJava, giúp chuyển đổi các thao tác Bluetooth phức tạp thành những luồng dữ liệu (streams) mạch lạc và dễ quản lý.
Ưu điểm của việc sử dụng RxAndroidBle
So với framework BLE tiêu chuẩn của Android, RxAndroidBle mang lại nhiều lợi ích vượt trội:
- API đồng nhất: Thay thế các hàm gọi ngược (callbacks) rời rạc bằng các toán tử RxJava mạnh mẽ.
- Quản lý luồng tự động: Tự động điều phối việc thực thi trên các luồng phù hợp, tránh gây treo giao diện (UI thread).
- Xử lý lỗi tập trung: Cung cấp cơ chế bắt lỗi và thử lại (retry) đồng nhất cho mọi thao tác.
- Hỗ trợ đa phiên bản: Tương thích tốt từ Android API 18 cho đến các phiên bản mới nhất.
Cài đặt và cấu hình
Để bắt đầu, bạn cần thêm thư viện vào tệp
build.gradle. Tùy thuộc vào phiên bản RxJava bạn đang sử dụng:
// Đối với RxJava 3
implementation "com.polidea.rxandroidble3:rxandroidble:1.18.1"
// Đối với RxJava 2
implementation "com.polidea.rxandroidble2:rxandroidble:1.18.1"
Tiếp theo, khai báo các quyền cần thiết trong
AndroidManifest.xml. Lưu ý từ Android 12 (API 31), các quyền Bluetooth đã có sự thay đổi lớn:
<!-- Quyền cơ bản -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Cần thiết để quét thiết bị trên Android 6.0 - 11 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Quyền mới cho Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Quét tìm thiết bị BLE
Việc tìm kiếm thiết bị trở nên đơn giản hơn với các cài đặt quét có thể tùy chỉnh:
// Khởi tạo client
RxBleClient bleManager = RxBleClient.create(context);
// Thực hiện quét
Disposable scannerDisposable = bleManager.scanBleDevices(
new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
)
.subscribe(
result -> {
String name = result.getBleDevice().getName();
String address = result.getBleDevice().getMacAddress();
// Cập nhật danh sách thiết bị tìm thấy
},
error -> {
// Xử lý lỗi khi quét
}
);
// Dừng quét khi không cần thiết
scannerDisposable.dispose();
Thiết lập kết nối
RxAndroidBle hỗ trợ cả kết nối trực tiếp và kết nối tự động (auto-connect):
RxBleDevice targetDevice = bleManager.getBleDevice("00:11:22:33:44:55");
targetDevice.establishConnection(false) // false = kết nối trực tiếp
.subscribe(
connection -> {
// Kết nối thành công, có thể thực hiện đọc/ghi GATT
},
throwable -> {
// Xử lý lỗi kết nối (ví dụ: thiết bị ngoài phạm vi)
}
);
Thao tác với dữ liệu: Đọc, Ghi và Thông báo
1. Đọc dữ liệu từ Characteristic
targetDevice.establishConnection(false)
.flatMapSingle(conn -> conn.readCharacteristic(uuidCharacteristic))
.subscribe(
bytes -> {
// Xử lý mảng byte nhận được
},
err -> { /* Xử lý lỗi */ }
);
2. Ghi dữ liệu
byte[] payload = new byte[]{0x01, 0x02};
targetDevice.establishConnection(false)
.flatMapSingle(conn -> conn.writeCharacteristic(uuidCharacteristic, payload))
.subscribe(
result -> {
// Xác nhận ghi thành công
},
err -> { /* Xử lý lỗi */ }
);
3. Đăng ký nhận thông báo (Notifications)
targetDevice.establishConnection(false)
.flatMap(conn -> conn.setupNotification(uuidCharacteristic))
.flatMap(notificationObservable -> notificationObservable) // Chuyển đổi sang luồng dữ liệu thực tế
.subscribe(
value -> {
// Nhận dữ liệu cập nhật từ thiết bị theo thời gian thực
},
err -> { /* Xử lý lỗi */ }
);
Tính năng nâng cao
Điều chỉnh MTU (Maximum Transmission Unit)
Để truyền tải các gói dữ liệu lớn hơn mặc định (23 bytes), bạn cần yêu cầu thay đổi MTU:
targetDevice.establishConnection(false)
.flatMapSingle(conn -> conn.requestMtu(256))
.subscribe(
mtu -> {
// MTU đã được nâng cấp lên 256
},
err -> { /* Xử lý lỗi */ }
);
Ghi dữ liệu khối lượng lớn (Long Write)
Khi cần gửi một tệp tin hoặc gói tin vượt quá giới hạn MTU, RxAndroidBle cung cấp giải pháp ghi theo lô (batch):
targetDevice.establishConnection(false)
.flatMap(conn -> conn.createNewLongWriteBuilder()
.setCharacteristicUuid(uuidCharacteristic)
.setBytes(largeByteArray)
.setMaxBatchSize(20)
.build()
)
.subscribe(
finalResult -> {
// Hoàn tất việc ghi dữ liệu lớn
},
err -> { /* Xử lý lỗi */ }
);
Theo dõi trạng thái hệ thống
Bạn có thể quan sát sự thay đổi trạng thái của Bluetooth (Bật/Tắt) hoặc quyền truy cập vị trí để điều chỉnh hành vi ứng dụng:
bleManager.observeStateChanges()
.subscribe(state -> {
if (state == RxBleClient.State.READY) {
// Sẵn sàng để quét và kết nối
} else if (state == RxBleClient.State.BLUETOOTH_NOT_ENABLED) {
// Yêu cầu người dùng bật Bluetooth
}
});
Kinh nghiệm triển khai thực tế
- Quản lý Instance: Luôn sử dụng
RxBleClient như một Singleton trong toàn bộ ứng dụng để tránh rò rỉ bộ nhớ và xung đột tài nguyên.
- Giải phóng tài nguyên: Đảm bảo gọi
dispose() trên các Subscription khi Activity hoặc ViewModel bị hủy để đóng kết nối và dừng quét.
- Xử lý lỗi 133: Đây là lỗi GATT phổ biến trên Android. Hãy thử triển khai toán tử
retry() với khoảng thời gian chờ (delay) để tăng tỉ lệ kết nối thành công.
- Cấu hình Log: Trong quá trình phát triển, hãy bật log mức độ DEBUG để dễ dàng theo dõi các gói tin trao đổi:
RxBleClient.setLogLevel(RxBleLog.DEBUG);