BluetoothGatt — کلاس Android که API را برای کار کلاینت GATT (Generic Attribute Profile) روی اتصال BLE فراهم میکند. BluetoothGatt اتصال به سرور GATT راهدور (دستگاه جانبی BLE) را کپسوله میکند و تمام عملیات پروفایل را مدیریت میکند: کشف سرویسها، خواندن و نوشتن مشخصهها، اشتراک در اعلانها و نشانهها. نمونه BluetoothGatt از طریق BluetoothDevice.connectGatt() با callback BluetoothGattCallback به دست میآید. به گفته Android Developers, 2026، BluetoothGatt کلاس مرکزی برای ارتباط دوطرفه BLE است که عملیات GATT را از BLE 4.0 تا BLE 5.4 پشتیبانی میکند.
نکات اصلی
BluetoothGatt — یک شیء پروکسی است که اتصال GATT بین دستگاه Android (مرکز) و جانبی BLE (سرور) را نشان میدهد. هر نمونه BluetoothGatt معادل یک اتصال فعال BLE است. از طریق آن تمام عملیات پروفایل GATT انجام میشود: کشف، خواندن، نوشتن، اعلانها. BluetoothGatt مستقیماً ایجاد نمیشود — توسط متد BluetoothDevice.connectGatt() بازگردانده میشود.
ایجاد BluetoothGatt به چهار پارامتر نیاز دارد. Context — زمینه برنامه (Activity یا Application). autoConnect — اگر false باشد، Android بلافاصله اتصال مستقیم را آغاز میکند؛ اگر true باشد، Android به طور خودکار هنگام شناسایی دستگاه متصل میشود (مفید برای اتصال پسزمینه). BluetoothGattCallback — callback اجباری برای تمام رویدادهای GATT. transport — BluetoothDevice.TRANSPORT_LE (BLE) یا TRANSPORT_BREDR (Classic). در دستگاههای BLE همیشه از TRANSPORT_LE استفاده کنید.
چرخه حیات BluetoothGatt از پنج حالت تشکیل شده است. DISCONNECTED — حالت اولیه. CONNECTING — پس از فراخوانی connectGatt، تا تأیید. CONNECTED — پس از onConnectionStateChange با STATE_CONNECTED. پس از اتصال، discoverServices() برای به دست آوردن سلسلهمراتب GATT فراخوانی میشود. پس از اتمام کار — disconnect() و close() برای آزادسازی منابع سیستمی. بدون فراخوانی close()، برنامه ممکن است محدودیت اتصالات BLE Android را تمام کند (معمولاً 4–8).
// ایجاد اتصال BluetoothGatt
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// در صورت وجود، اتصال قبلی را ببند
close()
bluetoothGatt = device.connectGatt(
context,
false, // autoConnect = false ( )
object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt, status: Int, newState: Int
) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
// ۲. اتصال برقرار شد → کشف
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// ۳. اتصال قطع شد
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// ۴. سلسلهمراتب GATT دریافت شد
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT آماده عملیات خواندن/نوشتن
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
کلاس GattConnector ایجاد صحیح BluetoothGatt را نشان میدهد. پارامتر autoConnect=false — اتصال مستقیم (برای دستگاههای اسکن شده). در onConnectionStateChange با STATE_CONNECTED بلافاصله discoverServices() فراخوانی میشود. onServicesDiscovered آمادگی GATT را نشان میدهد. close() به ترتیب disconnect() و close() را فراخوانی میکند — بدون close() منابع سیستمی آزاد نمیشوند که منجر به نشت اتصالات BLE میشود.
discoverServices() — اولین متد GATT که پس از اتصال فراخوانی میشود. جستجوی ناهمزمان تمام سرویسها را در جانبی BLE آغاز میکند. نتیجه در onServicesDiscovered() با کد وضعیت میآید: GATT_SUCCESS (0) — موفق، 133 — GATT_ERROR، 8 — GATT_CONNECTION_TIMEOUT. پس از کشف موفق، BluetoothGatt فهرست سرویسهای قابل دسترسی از طریق getServices() را پر میکند.
هر BluetoothGattService شامل فهرستی از BluetoothGattCharacteristic است. مشخصه دارای UUID، ویژگیها (PROPERTY_READ، PROPERTY_WRITE، PROPERTY_NOTIFY) و توصیفگرهای اختیاری است. ویژگیها تعیین میکنند کدام عملیات مجاز است: اگر مشخصه PROPERTY_READ نداشته باشد، فراخوانی readCharacteristic خطا برمیگرداند. برای به دست آوردن توصیفگرهای مشخصه از getDescriptors() استفاده میشود.
// کشف سرویس و جستجوی مشخصه
class GattServiceExplorer {
// پیدا کردن سرویس با UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// پیدا کردن مشخصه در سرویس
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// دریافت تمام عملیات مشخصه پشتیبانیشده
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
}
// ثبت کل سلسلهمراتب GATT
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}")
}
}
}
}
}
کلاس GattServiceExplorer ابزارهایی برای پیمایش در سلسلهمراتب GATT فراهم میکند. findService و findCharacteristic سرویسها و مشخصهها را بر اساس UUID جستجو میکنند. getCharacteristicProperties ماسکهای بیتی ویژگیها را از طریق and() بررسی میکند. dumpGattTree سلسلهمراتب کامل را در لاگ خروجی میدهد — در اشکالزدایی دستگاههای BLE مفید است. تمام عملیات روی BluetoothGatt باید پس از onServicesDiscovered موفق انجام شود، در غیر این صورت getServices() فهرست خالی برمیگرداند.
readCharacteristic() — متد ناهمزمان BluetoothGatt برای خواندن مقدار مشخصه از دستگاه BLE راهدور. نتیجه در onCharacteristicRead() BluetoothGattCallback میآید. اگر در دستگاه ۲+ مشخصه مطابق وجود داشته باشد (احتمال کم، اما ممکن)، readCharacteristic() ممکن است مشخصه نادرستی را بخواند — ایمنتر است که readCharacteristic() را روی نمونه BluetoothGattCharacteristic فراخوانی کنید، نه بر اساس UUID.
readDescriptor() — متد برای خواندن مقدار توصیفگر مشخصه. توصیفگر معمولی — CCCD (Client Characteristic Configuration Descriptor، UUID 0x2902) که تعیین میکند آیا اعلانها فعال هستند. نتیجه در onDescriptorRead(). خواندن توصیفگرها به ندرت در عمل مورد نیاز است — CCCD توسط setCharacteristicNotification() مدیریت میشود، اما برای توصیفگرهای سفارشی (User Description 0x2901، Presentation Format 0x2904) readDescriptor() تنها راه به دست آوردن فراداده است.
MTU و خواندن دادههای بزرگ — اگر مقدار مشخصه از MTU (۲۳ بایت برای BLE 4.0) بیشتر باشد، Android به طور خودکار دادهها را تکهتکه کرده و با دنبالهای از درخواستهای خواندن از طریق پشته BLE جمعآوری میکند. برای BLE 5.0+ با MTU توسعهیافته (تا ۲۵۱ بایت) تکهتکهکردن لازم نیست — یک بار خواندن داده کامل را برمیگرداند. قبل از خواندن میتوان requestMtu() را برای توافق بر سر حداکثر MTU فراخوانی کرد.
// خواندن مشخصه و توصیفگر از طریق BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// UUID سرویس Heart Rate = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// UUID اندازهگیری Heart Rate = 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) }
}
// پردازش داده در callback onCharacteristicRead:
fun parseHeartRate(value: ByteArray): Int {
// BLE Heart Rate: بایتها = flags، دوم = 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
}
}
کلاس GattReader خواندن مشخصه Heart Rate را نشان میدهد. سرویس 0x180D شامل مشخصه 0x2A37 (Heart Rate Measurement) — پروفایل استاندارد BLE Bluetooth SIG است. قبل از خواندن، ویژگی PROPERTY_READ از طریق hasProperty بررسی میشود. parseHeartRate فرمت BLE ضربان قلب را تجزیه میکند: بایت اول — flags (فرمت داده)، دوم — مقدار bpm. توصیفگر CCCD (0x2902) برای بررسی وضعیت اعلانها خوانده میشود.
writeCharacteristic() — متد BluetoothGatt برای نوشتن داده روی جانبی BLE. در Android API 33+ متد writeCharacteristic() با writeCharacteristic(request) جایگزین شده است، جایی که BluetoothGattCharacteristicWriteRequest یک شیء درخواست حاوی مشخصه، آرایه بایت و WriteType است. متد قدیمی writeCharacteristic(characteristic) با setValue() منسوخ شده است. WriteType رفتار درخواست را تعیین میکند: WRITE_TYPE_DEFAULT (بستگی به ویژگیهای مشخصه دارد)، WRITE_TYPE_NO_RESPONSE (withoutResponse) و WRITE_TYPE_SIGNED (مجوز).
انتخاب WriteType بر سرعت و قابلیت اطمینان تأثیر میگذارد. WRITE_TYPE_DEFAULT معمولاً معادل withResponse (اگر مشخصه PROPERTY_WRITE داشته باشد) یا withoutResponse (اگر PROPERTY_WRITE_NO_RESPONSE داشته باشد) است. برای دادههای جریانی (بهروزرسانیهای OTA، لاگها) از WRITE_TYPE_NO_RESPONSE استفاده کنید — حداکثر پهنای باند. برای دستورات با تضمین تحویل (فعالسازی، پیکربندی) — WRITE_TYPE_DEFAULT با تأیید از طریق onCharacteristicWrite.
// نوشتن مشخصه BLE در Android API 33+
class GattWriter {
// نوشتن با پاسخ (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)
}
}
// نوشتن بدون پاسخ (حداکثر سرعت)
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)
}
}
// callback onCharacteristicWrite (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")
}
}
}
}
کلاس GattWriter هر دو WriteType را برای سطوح مختلف API پشتیبانی میکند. writeWithResponse از WRITE_TYPE_DEFAULT استفاده میکند — دستگاه BLE نوشتن را از طریق onCharacteristicWrite تأیید میکند. writeWithoutResponse از WRITE_TYPE_NO_RESPONSE استفاده میکند — دادهها بدون تأیید ارسال میشوند، حداکثر پهنای باند. در API 33+ از writeCharacteristic(request) جدید با BluetoothGattCharacteristicWriteRequest استفاده میشود. در API < 33 — setValue() قدیمی + writeCharacteristic().
setCharacteristicNotification() — متد BluetoothGatt برای اشتراک در اعلانهای تغییر مشخصه در جانبی. پس از فعالسازی اشتراک، دستگاه BLE مقادیر جدید را از طریق onCharacteristicChanged() ارسال میکند. با این حال setCharacteristicNotification() فقط اعلان محلی Android را فعال میکند — برای فعالسازی اعلانها در خود دستگاه BLE باید مقدار 0x0100 را نیز در توصیفگر CCCD (0x2902) نوشت.
CCCD (Client Characteristic Configuration Descriptor) — توصیفگری که ارسال اعلانها را از جانبی BLE مدیریت میکند. مقدار 0x0000 — اعلانها غیرفعال، 0x0100 — اعلانها فعال (notifications)، 0x0200 — نشانهها فعال (indications). نوشتن در CCCD از طریق writeDescriptor() روی BluetoothGatt پس از فراخوانی setCharacteristicNotification() انجام میشود. Android CCCD را به طور خودکار نمینویسد — این وظیفه بر عهده توسعهدهنده است.
// اشتراک صحیح در اعلانهای BLE
class GattNotificationManager {
// ۱. فعالسازی اعلانها
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// مرحله ۱: اشتراک محلی Android
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("اشتراک ناموفق")
return
}
// مرحله ۲: نوشتن CCCD (0x2902) روی دستگاه BLE
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = اعلان، 0x0200 = نشانه
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)
}
// ۲. کشف مشخصه
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)
}
// ۳. callback اعلان
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// مقدار جدید از جانبی BLE
print("Notification: ${value.size} bytes")
}
}
}
کلاس GattNotificationManager پروتکل دو مرحلهای صحیح اشتراک در اعلانهای BLE را پیادهسازی میکند. enableNotification ابتدا setCharacteristicNotification(true) را در Android فراخوانی میکند، سپس 0x0100 را در توصیفگر CCCD از طریق writeDescriptor مینویسد. disableNotification عملیات معکوس را انجام میدهد. بدون نوشتن CCCD، دستگاه BLE اعلان ارسال نمیکند — این رایجترین اشتباه توسعهدهندگان BLE در Android است.
مثال کامل کلاینت GATT در Kotlin که ایجاد BluetoothGatt، کشف، خواندن و اشتراک در اعلانها را در یک مدیر واحد با استفاده از کوروتینها برای پردازش ناهمزمان ترکیب میکند.
// کلاینت کامل GATT با کوروتینها در Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// اتصال با کوروتین
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
)
}
// خواندن مشخصه از طریق کوروتین
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// ذخیره برچسب مشخصه برای شناسایی callback
gatt.setCharacteristic(char, null) // برای API < 33
gatt.readCharacteristic(char)
}
}
// بستن اتصال
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
کلاینت GATT GattClient از کوروتینها (suspendCoroutine) برای تبدیل API مبتنی بر callback BluetoothGatt به فراخوانیهای ترتیبی استفاده میکند. connect() منتظر onServicesDiscovered میماند، پس از آن سلسلهمراتب GATT در دسترس است. readCharacteristicValue() منتظر onCharacteristicRead میماند. این رویکرد تو در تو شدن callbackها را حذف و کد BLE را خطی میکند. release() آزادسازی منابع را تضمین میکند — فراخوانی اجباری در onDestroy Activity یا ViewModel.onCleared.
سوالات متداول
BluetoothGatt — کلاس کلاینت GATT Android که اتصال BLE را با دستگاه جانبی مدیریت میکند. از طریق BluetoothDevice.connectGatt() ایجاد میشود، متدهای discoverServices()، readCharacteristic()، writeCharacteristic()، setCharacteristicNotification() را فراهم میکند. نتایج تمام عملیات به صورت ناهمزمان از طریق BluetoothGattCallback میرسد. بدون BluetoothGatt ارتباط دوطرفه BLE در Android امکانپذیر نیست.
وضعیت 133 (GATT_ERROR) به معنای خطای داخلی پشته BLE Android است. علل: دستگاه در حین کشف قطع شده، MTU کمتر از حداقل (۲۳ بایت) است، یا پشته BLE بیش از حد بارگذاری شده است. راهحل: discoverServices() را با تأخیر ۵۰۰ میلیثانیه تکرار کنید، RSSI دستگاه را بررسی کنید و مطمئن شوید جانبی از کشف GATT در وضعیت فعلی پشتیبانی میکند.
برای نوشتن با تأیید، writeCharacteristic() را با WRITE_TYPE_DEFAULT فراخوانی کنید (API 33+: BluetoothGattCharacteristicWriteRequest). در صورت موفقیت، دستگاه BLE تأییدیه ارسال میکند و Android onCharacteristicWrite را با GATT_SUCCESS فراخوانی میکند. اگر دستگاه ظرف ۳۰ ثانیه پاسخ ندهد (تایماوت پشته)، callback وضعیت خطا برمیگرداند. برای watchdog از Handler با postDelayed استفاده کنید.
در Android 13+ (API 33) متدهای BluetoothGatt تغییر کردهاند: writeCharacteristic() اکنون BluetoothGattCharacteristicWriteRequest را میپذیرد، readCharacteristic() — BluetoothGattCharacteristicReadRequest را. setValue()/writeCharacteristic() قدیمی منسوخ شدهاند. همچنین BluetoothGattCallback تغییر کرده است: onCharacteristicRead()، onCharacteristicWrite()، onCharacteristicChanged() ByteArray value و callbackType را دریافت میکنند. برای انشعاب از Build.VERSION.SDK_INT استفاده کنید.
Android از ۴–۸ اتصال همزمان BLE-GATT پشتیبانی میکند (بستگی به سازنده و نسخه Android دارد). Pixel/Google: تا ۷، Samsung: تا ۵، Xiaomi: تا ۴. در صورت تجاوز از حد مجاز، connectGatt null برمیگرداند یا onConnectionStateChange با خطا فراخوانی میشود. برای کار با تعداد زیادی دستگاه از اتصال چرخهای یا Bluetooth Mesh استفاده کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید