BluetoothGatt — فئة Android توفر API لعمل عميل GATT (Generic Attribute Profile) عبر اتصال BLE. يغلف BluetoothGatt الاتصال بخادم GATT بعيد (جهاز BLE طرفي) ويدير جميع عمليات الملف الشخصي: اكتشاف الخدمات، قراءة وكتابة الخصائص، الاشتراك في الإشعارات والتأشيرات. يتم الحصول على مثيل BluetoothGatt عبر BluetoothDevice.connectGatt() مع 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 — إذا كان خطأ، يبدأ Android فورًا اتصالًا مباشرًا؛ إذا كان صحيحًا، يتصل Android تلقائيًا عند اكتشاف الجهاز (مفيد للاتصال في الخلفية). BluetoothGattCallback — رد اتصال إلزامي لجميع أحداث 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 -> {
// 2. تم إنشاء الاتصال ← اكتشاف
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. فقدان الاتصال
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. تم استلام التسلسل الهرمي 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("كتابة")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("كتابة_بدون_رد")
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. إذا كان الجهاز يحتوي على 2+ خصائص متطابقة (غير محتمل لكن ممكن)، قد تقرأ readCharacteristic() خاصية غير مستهدفة — من الأكثر أمانًا استدعاء readCharacteristic() على مثيل BluetoothGattCharacteristic بدلاً من UUID.
readDescriptor() — طريقة لقراءة قيمة واصف الخاصية. الواصف النموذجي هو CCCD (Client Characteristic Configuration Descriptor، UUID 0x2902)، الذي يحدد ما إذا كانت الإشعارات مفعلة. النتيجة في onDescriptorRead(). نادرًا ما تكون قراءة الواصفات ضرورية عمليًا — تتم إدارة CCCD بواسطة setCharacteristicNotification()، لكن للواصفات المخصصة (User Description 0x2901، Presentation Format 0x2904) readDescriptor() هي الطريقة الوحيدة للحصول على البيانات الوصفية.
MTU وقراءة البيانات الكبيرة — إذا تجاوزت قيمة الخاصية MTU (23 بايت لـ BLE 4.0)، يقوم Android تلقائيًا بتجزئة وإعادة تجميع البيانات عبر سلسلة من طلبات القراءة عبر مكدس BLE. بالنسبة لـ BLE 5.0+ مع MTU موسع (حتى 251 بايت)، لا يلزم التجزئة — قراءة واحدة تعيد البيانات كاملة. قبل القراءة، يمكنك استدعاء requestMtu() للتفاوض على أقصى MTU.
// قراءة الخاصية والواصف عبر BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// UUID لخدمة معدل ضربات القلب = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// 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) }
}
// معالجة البيانات في callback onCharacteristicRead:
fun parseHeartRate(value: ByteArray): Int {
// معدل ضربات القلب BLE: bytes = flags, second = 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 قراءة خاصية معدل ضربات القلب. الخدمة 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 (بدون استجابة) و 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 كلا WriteTypes لمستويات 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 — الإشعارات مفعلة، 0x0200 — التأشيرات مفعلة. تتم الكتابة في CCCD عبر writeDescriptor() على BluetoothGatt بعد استدعاء setCharacteristicNotification(). لا يكتب Android CCCD تلقائيًا — هذه المسؤولية تقع على المطور.
// الاشتراك الصحيح في إشعارات BLE
class GattNotificationManager {
// 1. تفعيل الإشعارات
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// الخطوة 1: الاشتراك المحلي في Android
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("فشل الاشتراك")
return
}
// الخطوة 2: كتابة 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)
}
// 2. اكتشاف الخصائص
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. 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 والاكتشاف والقراءة والاشتراك في الإشعارات في مدير واحد باستخدام coroutines للمعالجة غير المتزامنة.
// عميل GATT كامل مع coroutines في Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// الاتصال مع 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
)
}
// قراءة الخاصية عبر coroutine
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 coroutines (suspendCoroutine) لتحويل API BluetoothGatt القائمة على callback إلى استدعاءات تسلسلية. ينتظر connect() onServicesDiscovered، وبعد ذلك يصبح التسلسل الهرمي GATT متاحًا. ينتظر readCharacteristicValue() onCharacteristicRead. هذا النهج يلغي تداخل callbacks ويجعل كود BLE خطيًا. يضمن release() تحرير الموارد — استدعاء إلزامي في onDestroy لـ Activity أو ViewModel.onCleared.
الأسئلة الشائعة
BluetoothGatt — فئة Android لعميل GATT تدير اتصال BLE مع جهاز طرفي. يتم إنشاؤها عبر BluetoothDevice.connectGatt()، وتوفر طرق discoverServices()، readCharacteristic()، writeCharacteristic()، setCharacteristicNotification(). نتائج جميع العمليات تصل بشكل غير متزامن عبر BluetoothGattCallback. بدون BluetoothGatt، الاتصال الثنائي BLE على Android مستحيل.
الحالة 133 (GATT_ERROR) تعني خطأ داخلي في مكدس BLE في Android. الأسباب: انفصال الجهاز أثناء الاكتشاف، MTU أقل من الحد الأدنى (23 بايت)، أو مكدس BLE محمل زائدًا. الحل: أعد discoverServices() بتأخير 500 مللي ثانية، تحقق من RSSI للجهاز وتأكد من أن الجهاز الطرفي يدعم اكتشاف GATT في حالته الحالية.
للكتابة مع تأكيد، استدع writeCharacteristic() مع WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). عند النجاح، يرسل جهاز BLE تأكيدًا ويستدعي Android onCharacteristicWrite مع GATT_SUCCESS. إذا لم يستجب الجهاز خلال 30 ثانية (مهلة المكدس)، يعيد 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 4–8 اتصالات BLE GATT متزامنة (يختلف حسب الشركة المصنعة وإصدار Android). Pixel/Google: حتى 7، Samsung: حتى 5، Xiaomi: حتى 4. عند تجاوز الحد، يعيد connectGatt قيمة null أو onConnectionStateChange مع خطأ. للعمل مع عدد كبير من الأجهزة، استخدم الاتصال الدوري أو Bluetooth Mesh.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.