BluetoothGatt — klase ng Android na nagbibigay ng API para sa paggana ng GATT-client (Generic Attribute Profile) sa BLE connection. Ang BluetoothGatt ay nag-eencapsulate ng koneksyon sa remote GATT server (peripheral BLE device) at namamahala sa lahat ng operasyon ng profile: discovery ng mga serbisyo, pagbasa at pagsulat ng mga katangian, pag-subscribe sa mga notification at indication. Ang instance ng BluetoothGatt ay nakukuha sa pamamagitan ng BluetoothDevice.connectGatt() na may callback na BluetoothGattCallback. Ayon sa Android Developers, 2026, ang BluetoothGatt ay ang sentral na klase para sa two-way BLE communication, sumusuporta sa GATT operations mula BLE 4.0 hanggang BLE 5.4.
Mga pangunahing punto
BluetoothGatt — ay isang proxy object na kumakatawan sa GATT connection sa pagitan ng Android device (central) at BLE peripheral (server). Bawat instance ng BluetoothGatt ay tumutugma sa isang aktibong BLE connection. Sa pamamagitan nito isinasagawa ang lahat ng GATT profile operations: discovery, pagbasa, pagsulat, mga notification. Ang BluetoothGatt ay hindi direktang ginagawa — ito ay ibinabalik ng pamamaraang BluetoothDevice.connectGatt().
Ang paggawa ng BluetoothGatt ay nangangailangan ng apat na parameter. Context — konteksto ng application (Activity o Application). autoConnect — kung false, agad na sinisimulan ng Android ang direktang koneksyon; kung true, awtomatikong kumokonekta ang Android kapag may nakitang device (kapaki-pakinabang para sa background connection). BluetoothGattCallback — mandatoryong callback para sa lahat ng GATT events. transport — BluetoothDevice.TRANSPORT_LE (BLE) o TRANSPORT_BREDR (Classic). Sa mga BLE device palaging gamitin ang TRANSPORT_LE.
Lifecycle ng BluetoothGatt ay binubuo ng limang estado. DISCONNECTED — paunang estado. CONNECTING — pagkatapos tawagan ang connectGatt, hanggang sa kumpirmasyon. CONNECTED — pagkatapos ng onConnectionStateChange na may STATE_CONNECTED. Pagkatapos kumonekta, tinatawag ang discoverServices() para makuha ang GATT hierarchy. Pagkatapos ng trabaho — disconnect() at close() para palayain ang system resources. Kung walang close(), maaaring maubos ng app ang limitasyon ng BLE connections ng Android (karaniwang 4–8).
// Gumagawa ng BluetoothGatt connection
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// Isara ang nakaraang connection kung mayroon
close()
bluetoothGatt = device.connectGatt(
context,
false, // autoConnect = false ( )
object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt, status: Int, newState: Int
) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
// 2. Connection naitatag → Discovery
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. Connection nawala
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. GATT hierarchy natanggap
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT handa na para sa read/write operations
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
Ang klase na GattConnector ay nagpapakita ng tamang paggawa ng BluetoothGatt. Parameter autoConnect=false — direktang koneksyon (para sa na-scan na mga device). Sa onConnectionStateChange na may STATE_CONNECTED, agad na tinatawag ang discoverServices(). Ang onServicesDiscovered ay nagpapahiwatig ng kahandaan ng GATT. Ang close() ay sunod-sunod na tumatawag ng disconnect() at close() — kung walang close(), hindi napapalaya ang system resources, na nagdudulot ng pagtagas ng BLE connections.
discoverServices() — unang GATT method na tinatawag pagkatapos kumonekta. Sinisimulan ang asynchronous na paghahanap ng lahat ng serbisyo sa BLE peripheral. Ang resulta ay dumarating sa onServicesDiscovered() na may status code: GATT_SUCCESS (0) — tagumpay, 133 — GATT_ERROR, 8 — GATT_CONNECTION_TIMEOUT. Pagkatapos ng matagumpay na discovery, pinupunan ng BluetoothGatt ang listahan ng mga serbisyo na available sa pamamagitan ng getServices().
Bawat BluetoothGattService ay naglalaman ng listahan ng BluetoothGattCharacteristic. Ang katangian ay may UUID, mga property (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) at opsyonal na mga descriptor. Tinutukoy ng mga property kung aling mga operasyon ang pinapayagan: kung ang katangian ay walang PROPERTY_READ, ang pagtawag sa readCharacteristic ay magbabalik ng error. Para makuha ang mga descriptor ng katangian, gamitin ang getDescriptors().
// Service discovery at paghahanap ng katangian
class GattServiceExplorer {
// Hanapin ang serbisyo sa pamamagitan ng UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// Hanapin ang katangian sa serbisyo
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// Kunin ang lahat ng suportadong operasyon ng katangian
fun getCharacteristicProperties(chars: BluetoothGattCharacteristic): List<String> {
val props = mutableListOf<String>()
with(chars.properties) {
if (and(BluetoothGattCharacteristic.PROPERTY_READ) != 0) props.add("READ")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) props.add("WRITE")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("WRITE_NO_RESP")
if (and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) props.add("NOTIFY")
if (and(BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) props.add("INDICATE")
}
return props
}
// I-log ang buong GATT hierarchy
fun dumpGattTree(gatt: BluetoothGatt) {
gatt.services?.forEach { service ->
print("Service: ${service.uuid}")
service.characteristics?.forEach { char ->
print(" Characteristic: ${char.uuid}, properties: ${char.properties}")
char.descriptors?.forEach { desc ->
print(" Descriptor: ${desc.uuid}")
}
}
}
}
}
Ang klase na GattServiceExplorer ay nagbibigay ng mga utility para sa pag-navigate sa GATT hierarchy. Ang findService at findCharacteristic ay naghahanap ng mga serbisyo at katangian ayon sa UUID. Ang getCharacteristicProperties ay sumusuri ng bit masks ng mga property sa pamamagitan ng and(). Ang dumpGattTree ay naglalabas ng buong hierarchy sa log — kapaki-pakinabang sa pag-debug ng mga BLE device. Lahat ng operasyon sa BluetoothGatt ay dapat gawin pagkatapos ng matagumpay na onServicesDiscovered, kung hindi ang getServices() ay magbabalik ng walang laman na listahan.
readCharacteristic() — asynchronous na paraan ng BluetoothGatt para basahin ang halaga ng katangian mula sa remote BLE device. Ang resulta ay dumarating sa onCharacteristicRead() ng BluetoothGattCallback. Kung mayroong 2+ magkatugmang katangian sa device (hindi malamang ngunit posible), ang readCharacteristic() ay maaaring magbasa ng hindi target — mas ligtas na tawagan ang readCharacteristic() sa instance ng BluetoothGattCharacteristic, hindi batay sa UUID.
readDescriptor() — paraan para basahin ang halaga ng descriptor ng katangian. Isang tipikal na descriptor — CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902) na tumutukoy kung ang mga notification ay naka-on. Resulta sa onDescriptorRead(). Ang pagbasa ng mga descriptor ay bihirang kailanganin sa praktika — ang CCCD ay pinamamahalaan ng setCharacteristicNotification(), ngunit para sa custom na mga descriptor (User Description 0x2901, Presentation Format 0x2904) ang readDescriptor() ay ang tanging paraan upang makakuha ng metadata.
MTU at pagbasa ng malaking data — kung ang halaga ng katangian ay lumampas sa MTU (23 byte para sa BLE 4.0), awtomatikong fina-fragment at ini-assemble ng Android ang data sa pamamagitan ng pagkakasunod-sunod ng read requests sa BLE stack. Para sa BLE 5.0+ na may extended MTU (hanggang 251 byte) hindi kailangan ang fragmentation — isang pagbasa ay nagbabalik ng kumpletong data. Bago magbasa, maaaring tawagan ang requestMtu() para mag-negotiate ng maximum MTU.
// Pagbasa ng katangian at descriptor sa pamamagitan ng BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// Heart Rate Service UUID = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// Heart Rate Measurement UUID = 0x2A37
val characteristic = service.getCharacteristic(
UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")
) ?: return
if (hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ)) {
gatt.readCharacteristic(characteristic)
}
}
fun readDescriptor(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// CCCD (0x2902)
val cccd = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
)
cccd?.let { gatt.readDescriptor(it) }
}
// I-process ang data sa onCharacteristicRead callback:
fun parseHeartRate(value: ByteArray): Int {
// BLE Heart Rate: bytes = flags, pangalawa = bpm
return if (value.isNotEmpty()) value[1].toInt() and 0xFF else 0
}
private fun hasProperty(char: BluetoothGattCharacteristic, prop: Int): Boolean {
return char.properties and prop != 0
}
}
Ang klase na GattReader ay nagpapakita ng pagbasa ng Heart Rate characteristic. Ang serbisyo 0x180D ay naglalaman ng katangiang 0x2A37 (Heart Rate Measurement) — karaniwang BLE profile ng Bluetooth SIG. Bago magbasa, ang property na PROPERTY_READ ay sinusuri sa pamamagitan ng hasProperty. Ang parseHeartRate ay nagpe-parse ng BLE heart rate format: unang byte — flags (format ng data), pangalawa — halaga ng bpm. Ang CCCD descriptor (0x2902) ay binabasa para suriin ang status ng notification.
writeCharacteristic() — paraan ng BluetoothGatt para magsulat ng data sa BLE peripheral. Sa Android API 33+, ang writeCharacteristic() ay pinalitan ng writeCharacteristic(request), kung saan ang BluetoothGattCharacteristicWriteRequest ay isang request object na naglalaman ng katangian, byte array, at WriteType. Ang lumang paraan na writeCharacteristic(characteristic) na may setValue() ay deprecated. Tinutukoy ng WriteType ang pag-uugali ng request: WRITE_TYPE_DEFAULT (depende sa mga property ng katangian), WRITE_TYPE_NO_RESPONSE (withoutResponse) at WRITE_TYPE_SIGNED (awtorisasyon).
Ang pagpili ng WriteType ay nakakaapekto sa bilis at pagiging maaasahan. Ang WRITE_TYPE_DEFAULT ay karaniwang tumutugma sa withResponse (kung ang katangian ay may PROPERTY_WRITE) o withoutResponse (kung may PROPERTY_WRITE_NO_RESPONSE). Para sa streaming data (OTA updates, logs) gamitin ang WRITE_TYPE_NO_RESPONSE — maximum bandwidth. Para sa mga command na may garantiya ng paghahatid (pag-activate, configuration) — WRITE_TYPE_DEFAULT na may kumpirmasyon sa pamamagitan ng onCharacteristicWrite.
// Pagsulat ng BLE katangian sa Android API 33+
class GattWriter {
// Sumulat na may tugon (withResponse)
fun writeWithResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// API 33+: writeCharacteristic
val request = BluetoothGattCharacteristicWriteRequest(
characteristic,
data,
BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_DEFAULT,
@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
)
gatt.writeCharacteristic(request)
} else {
// API < 33: (deprecated)
characteristic.setValue(data)
gatt.writeCharacteristic(characteristic)
}
}
// Sumulat nang walang tugon (max na bilis)
fun writeWithoutResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val request = BluetoothGattCharacteristicWriteRequest(
characteristic,
data,
BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_NO_RESPONSE,
@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
)
gatt.writeCharacteristic(request)
} else {
characteristic.setValue(data)
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
gatt.writeCharacteristic(characteristic)
}
}
// onCharacteristicWrite callback (API 33+)
private val writeCallback = object : BluetoothGattCallback() {
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int,
callbackType: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
print("Write success: ${value.size} bytes")
}
}
}
}
Ang klase na GattWriter ay sumusuporta sa parehong WriteType para sa iba't ibang API level. Ang writeWithResponse ay gumagamit ng WRITE_TYPE_DEFAULT — kinukumpirma ng BLE device ang pagsulat sa pamamagitan ng onCharacteristicWrite. Ang writeWithoutResponse ay gumagamit ng WRITE_TYPE_NO_RESPONSE — data ay ipinapadala nang walang kumpirmasyon, maximum bandwidth. Sa API 33+ ginagamit ang bagong writeCharacteristic(request) na may BluetoothGattCharacteristicWriteRequest. Sa API < 33 — lumang setValue() + writeCharacteristic().
setCharacteristicNotification() — paraan ng BluetoothGatt para mag-subscribe sa mga notification tungkol sa pagbabago ng katangian sa peripheral. Pagkatapos i-activate ang subscription, ang BLE device ay nagpapadala ng mga bagong halaga sa pamamagitan ng onCharacteristicChanged(). Gayunpaman, ang setCharacteristicNotification() ay nag-a-activate lamang ng lokal na notification ng Android — para i-on ang mga notification sa BLE device mismo, kailangan ding isulat ang halagang 0x0100 sa CCCD descriptor (0x2902).
CCCD (Client Characteristic Configuration Descriptor) — descriptor na namamahala sa pagpapadala ng mga notification mula sa BLE peripheral. Halaga 0x0000 — naka-off ang mga notification, 0x0100 — naka-on ang mga notification (notifications), 0x0200 — naka-on ang mga indication (indications). Ang pagsulat sa CCCD ay ginagawa sa pamamagitan ng writeDescriptor() sa BluetoothGatt pagkatapos tawagan ang setCharacteristicNotification(). Hindi awtomatikong isinusulat ng Android ang CCCD — ang responsibilidad na ito ay nasa developer.
// Tamang BLE notification subscription
class GattNotificationManager {
// 1. I-on ang mga notification
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// Hakbang 1: lokal na Android subscription
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("Hindi naka-subscribe")
return
}
// Hakbang 2: isulat ang CCCD (0x2902) sa BLE device
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = notification, 0x0200 = indication
val cccdValue = if (characteristic.properties
and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE // [0x01, 0x00]
} else {
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE // [0x02, 0x00]
}
cccdDescriptor.setValue(cccdValue)
gatt.writeDescriptor(cccdDescriptor)
}
// 2. Pag-discovery ng katangian
fun disableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
gatt.setCharacteristicNotification(characteristic, false)
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
cccdDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)
gatt.writeDescriptor(cccdDescriptor)
}
// 3. Notification callback
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// Bagong halaga mula sa BLE peripheral
print("Notification: ${value.size} bytes")
}
}
}
Ang klase na GattNotificationManager ay nagpapatupad ng tamang two-step protocol para sa pag-subscribe sa BLE notification. Ang enableNotification ay unang tumatawag ng setCharacteristicNotification(true) sa Android, pagkatapos ay nagsusulat ng 0x0100 sa CCCD descriptor sa pamamagitan ng writeDescriptor. Ang disableNotification ay nagsasagawa ng mga kabaligtarang operasyon. Kung walang pagsulat ng CCCD, ang BLE device ay hindi nagpapadala ng mga notification — ito ang pinakakaraniwang pagkakamali ng mga BLE developer sa Android.
Buong halimbawa ng GATT-client sa Kotlin, na pinagsasama ang paggawa ng BluetoothGatt, discovery, pagbasa at pag-subscribe sa mga notification sa iisang manager gamit ang coroutines para sa asynchronous processing.
// Buong GATT client na may coroutines sa Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// Kumonekta gamit ang coroutine
suspend fun connect(device: BluetoothDevice): Boolean =
suspendCoroutine { continuation ->
gatt = device.connectGatt(
context, false,
object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt, status: Int, newState: Int
) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
} else {
continuation.resume(false)
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
continuation.resume(status == BluetoothGatt.GATT_SUCCESS)
}
},
BluetoothDevice.TRANSPORT_LE
)
}
// Basahin ang katangian sa pamamagitan ng coroutine
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// I-save ang tag ng katangian para sa callback identification
gatt.setCharacteristic(char, null) // para sa API < 33
gatt.readCharacteristic(char)
}
}
// Isara ang connection
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
Ang GATT client na GattClient ay gumagamit ng coroutines (suspendCoroutine) para gawing sequential calls ang callback-based na BluetoothGatt API. Ang connect() ay naghihintay ng onServicesDiscovered, pagkatapos nito ay available na ang GATT hierarchy. Ang readCharacteristicValue() ay naghihintay ng onCharacteristicRead. Ang approach na ito ay nag-aalis ng nested callbacks at ginagawang linear ang BLE code. Ang release() ay ginagarantiyahan ang pagpapalaya ng resources — mandatoryong tawag sa onDestroy ng Activity o ViewModel.onCleared.
Mga madalas itanong
BluetoothGatt — klase para sa GATT client ng Android na namamahala ng BLE connection sa peripheral device. Ginagawa sa pamamagitan ng BluetoothDevice.connectGatt(), nagbibigay ng mga pamamaraang discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Ang mga resulta ng lahat ng operasyon ay dumarating nang asynchronous sa pamamagitan ng BluetoothGattCallback. Kung walang BluetoothGatt, ang two-way BLE communication sa Android ay hindi posible.
Ang status 133 (GATT_ERROR) ay nangangahulugang internal error ng Android BLE stack. Mga dahilan: nadiskonekta ang device habang nade-detect, ang MTU ay mas maliit sa minimum (23 byte), o ang BLE stack ay overloaded. Solusyon: ulitin ang discoverServices() na may 500 ms na pagkaantala, suriin ang RSSI ng device, at tiyakin na sinusuportahan ng peripheral ang GATT discovery sa kasalukuyang estado.
Para magsulat na may kumpirmasyon, tawagan ang writeCharacteristic() na may WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). Sa tagumpay, nagpapadala ang BLE device ng kumpirmasyon at tinatawag ng Android ang onCharacteristicWrite na may GATT_SUCCESS. Kung hindi tumugon ang device sa loob ng 30 segundo (stack timeout), ang callback ay nagbabalik ng error status. Para sa watchdog, gumamit ng Handler na may postDelayed.
Sa Android 13+ (API 33) nagbago ang mga pamamaraan ng BluetoothGatt: ang writeCharacteristic() ngayon ay tumatanggap ng BluetoothGattCharacteristicWriteRequest, ang readCharacteristic() — BluetoothGattCharacteristicReadRequest. Ang lumang setValue()/writeCharacteristic() ay deprecated. Nagbago din ang BluetoothGattCallback: ang onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() ay tumatanggap ng ByteArray value at callbackType. Gamitin ang Build.VERSION.SDK_INT para sa branching.
Ang Android ay sumusuporta ng 4–8 sabay-sabay na BLE-GATT connections (depende sa manufacturer at bersyon ng Android). Pixel/Google: hanggang 7, Samsung: hanggang 5, Xiaomi: hanggang 4. Kapag lumampas sa limitasyon, ang connectGatt ay nagbabalik ng null o onConnectionStateChange na may error. Para sa pagtatrabaho na may maraming device, gumamit ng cyclical connection o Bluetooth Mesh.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din