34 -اندرويد : Connectivity -بلوتوث Bluetooth
ملاحظة : الموضوع عبارة عن حلقة من حلقات سلسلة برمجة وتطوير اندرويد Android Development
اندرويد : بلوتوث Bluetooth
السلام عليكم ورحمة الله وبركاته
لمحة سريعة :
تمكن واجهات التطبيقات البرمجية APIs الخاصة ببلوتوث ضمن اندرويد تطبيقك من انجاز عمليات نقل المعطيات اللاسلكي مع بقية التجهيزات.
ضمن هذه المقالة سوف نتحدث عن :
- الاساسيات The Basics
- سماحيات بلوتوث Bluetooth Permissions
- اعداد بلوتوث Setting Up Bluetooth
- ايجاد الاجهزة Finding Devices
- الاستعلام عن الاجهزة المقترنة Querying paired devices
- استكشاف الاجهزة Discovering devices
- توصيل الاجهزة Connecting Devices
- الاتصال كخادم Connecting as a server
- الاتصال كزبون Connecting as a client
- الادارة والتحكم بالاتصال Managing a Connection
- العمل مع الملفات Working with Profiles
- Vendor-specific AT commands
- Health Device Profile
الصفوف الاساسية والمهمة
بعض الامثلة ذات العلاقة بالموضوع
تتضمن منصة عمل اندرويد دعم لمكدس شكبة بلوتوث Bluetooth network stack, والذي يمكن بدوره الجهاز من تبادل المعطيات بشكل لاسلكي مع بقية اجهزة بلوتوث.
يزود اطار عمل التطبيق امكانية النفاذ إلى خدمات بلوتوث عبر واجهات التطبيقات البرمجية الخاصة ببلوتوث ضمن اندرويد Android Bluetooth APIs. تمكن هذه الواجهات APIs التطبيقات من التواصل بشكل لاسلكي مع بقية الجهزة الي تدعم بلوتوث, لتصبح بذلك ميزات الاتصال اللاسلكي بين نقطة – نقطة أو (بين عدة نقاط) متاحة.
بامكان تطبيقك انجاز المهام التالية عبر استخدام Bluetooth APIs
- البحث عن اجهزة بلوتوث اخرىScan for other Bluetooth devices
- الاستعلام من محول بلوتوث الداخلي عن اجهزة بلوتوث المتقرنة Query the local Bluetooth adapter for paired Bluetooth devices
- انشاء قنوات RFCOMM.Establish RFCOMM channels
- الاتصال مع بقية الاجهزة عبر خدمة الاستكشاف Connect to other devices through service discovery
- نقل المعطيات من وإلى بقية الاجهزة Transfer data to and from other devices
- التحكم وادارة الاتصالات المتعددة Manage multiple connections
الاساسيات The Basics
تتحدث هذه المقالة عن آلية استخدام واجهات التطبيقات البرمجية الخاصة ببلوتوث ضمن اندرويد Android Bluetooth APIs وذلك بهدف انجاز المهام الاساسية الاربعة الضرورية للتواصل عبر استخدام بلوتوث:
- إعداد بلوتوث setting up Bluetooth
- ايجاد الاجهزة التي – بكل الاحوال – باحد حالتين : اما مقترنة paired او متاحة ضمن المنطقة الحالية.
- الاتصال بين الاجهزة connecting devices
- نقل المعطيات بين الاجهزة transferring data between devices
تتواجد كل واجهات بلوتوث Bluetooth APIs ضمن الحزمة android.bluetooth.
فيما يلي ملخص للصفوف classes والواجهات interfaces التي تحتاجها لإنشاء اتصال بلوتوث:
BluetoothAdapter
تمثل محول بلوتوث محلي local Bluetooth adapter(Bluetooth radio). يعتبر BluetoothAdapter بمثابة المدخل الاساسي لكل اساليب التواصل والتواصل عبر البلوتوث Bluetooth interactions.
باستخدام BluetoothAdapter يصبح بإمكانك
- اكتشاف اجهزة البلوتوث الاخرى
- والاستعلام عن قائمة بالاجهزة المتقرنة paired devices
- استنساخ BluetoothDevice عبر استخدام عنوان MAC معروف.
- إنشاء BluetoothServerSocket للتصنت والاستماع على الاتصالات بين بقية الاجهزة
BluetoothDevice
ويمثل جهاز بلوتوث عن بعد remote Bluetooth device.
استخدم BluetoothDevice لطلب الاتصال مع جهاز عن بعد وذلك عبر BluetoothSocket أوعبر الاستعلام عن معلومات الجهاز مثل الاسم name او العنوان address او الصف class او حالة الاقتران bonding state.
BluetoothSocket
وتمثل واجهة ل Bluetooth socket (بشكل مشابه ل TCP Socket).
عبارة عن نقطة الاتصال التي تمكن تطبيقك من تبادل المعطيات مع بقية اجهزة بلوتوث عبر InputStream أو OutputStream.
BluetoothServerSocket
وتمثل server socket مفتوحة تتنصت للطلبات القادمة( بشكل مشابه ل ServerSocketTCP ).
لكي تقوم باجراء اتصال بين جهازي اندرويد, يجب على احد الجهازين ان يفتح server socket عبر هذا الصف BluetoothServerSocket class. عندما يجري جهاز بلوتوث عن بعد طلب اتصال لهذا الجهاز (أي الجهاز الذي يحوي على BluetoothServerSocket) فإن BluetoothServerSocket سوف يعيد BluetoothSocket متصلة , وذلك عندما يتم قبول الاتصال.
BluetoothClass
يوصف الصفات الامكانيات العامة لجهاز بلوتوث.عبارة عن مجموعة من الخصائص( التي يمكن فقط قرائتها) والتي تعرف الصفوف الاساسية والثانوية للجهاز مع خدماتهاmajor and minor device classes and its services .
على كل الاحوال فإن هذا الصف لا يوصف عليه بروفايل بلوتوث والخدمات المدعومة من قبل الجهاز بشكل يمكن الاعتماد, ولكنها مفيدة في الاشارة إلى نوع الجهاز.
BluetoothProfile
عبارة عن واجهة تمثل بروفايل بلوتوث Bluetooth profile.
Bluetooth profile (بروفايل بلوتوث) : عبارة عن خصائص ومواصفات لواجهة لا سكلية تخص الاتصالات بين الاجهزة التي تعتمد على بلوتوث.
مثال عليها : Hands-Free profile.
لمزيد من المعلومات حول البروفايلات بالامكان الاطلاع على الرابط التالي : Working with Profiles.
BluetoothHeadset
توفر دعم لسماعات بلوتوث Bluetooth headsets التي تستخدم مع اجهزة الموبايل. تضمن كل من بروفايلات Bluetooth headsets و Head-Free(v1.5)
BluetoothA2dp
تحدد مدى جودة الصوت الذي يمكن ارساله (عبر stream) من جهاز إلى اخر من خلال اتصال بلوتوث.
إن “A2DP”عبارة عن اختصار ل Advanced Audio Distribution Profile.
BluetoothHealth
Represents a Health Device Profile proxy that controls the Bluetooth service
BluetoothHealthCallback
صف مجرد abstract class تستخدمه لتحقيق (تنجيز implement) استدعاءات BluetoothHealth.
يجب القيام ب extend لهذا الصف وتنجيز implement توابع الاستدعاء بهدف تلقي التحديثات حول التغيرات في حالة تسجيل التطبيق application’s registration state وحالة قناة بلوتوث Bluetooth channel state.
BluetoothHealthAppConfiguration
يمثل اعدادات التطبيق التي يسجلها تطبيق Bluetooth Health third-party بهدف التواصل مع جهاز بلوتوث عن بعد remote Bluetooth health device .
BluetoothProfile.ServiceListener
واجهة تقوم بتنبيه واخطار BluetoothProfile IPC clients عندما يتم اتصالهم او انفصالهم عن الخدمة(that is, the internal service that runs a particular profile).
سماحيات وصلاحيات بلوتوث Bluetooth Permissions
لكي تستطيع استخدام ميزات بلوتوث ضمن تطبيقك , يتوجب عليك ان تصرح على الأقل عن واحد من الصلاحيات التالية :
يتوجب عليك ان تطلب صلاحية BLUETOOTH لكي تستطيع ان تنجز أي نوع من انواع اتصالات بلوتوث, مثل عملية طلب اتصال requesting a connection , قبول طلب اتصال accepting a connection , ونقل المعطيات transferring data.
يتوجب عليك ان تطلب صلاحية BLUETOOTH_ADMIN لكي تهيئ مستكشف الاجهزة device discovery أو للتعامل مع اعدادات بلوتوث.
اغلب التطبيقات تحتاج لهذه الصلاحية فقط بهدف اكتشاف اجهزة البلوتوث المحلية. اما بالنسبة لبقية الامكانيات التي تتيحها هذه الصلاحية فلا يجب استخدامها, إلا في حال كان التطبيق عبارةعن “power manager” الذي يقوم بتعديل اعدادات بلوتوث بناء على طلب المستخدم.
ملاحظة : في حال استخدمت صلاحية BLUETOOTH_ADMIN, يتوجب عليك ايضا تملك صلاحية BLUETOOTH.
يتم التصريح عن صلاحيات بلوتوث ضمن ملف manifest ضمن تطبيقك. على سبيل المثال:
<manifest ... > <uses-permission android:name="android.permission.BLUETOOTH" /> ... </manifest>
لمزيد من المعلومات حول التصريح عن صلاحيات التطبيق بالامكان مراجعة الدليل التالي : <uses-permission>
اعداد بلوتوث Setting Up Bluetooth

قبل ان يستطيع تطبيقك التواصل عبر بلوتوث , انت بحاجة للتحقق من ان بلوتوث مدعوم على الجهاز, وفي حال كان مدعوما, يجب التحقق من انه مفعل.
في حال لم يكن البلوتوث مدعوما على الجهاز, عندها يتوجب عليك ازالة تفعيل ميزة البلوتوث من تطبيقك.
اما في حال كان البلوتوث مدعوما على الجهاز, ولكنه غير مفعل, عندها بإمكانك ان تطلب من المستخدم ان يفعل البلوتوث بدون مغادرة تطبيقك.
يتم الاعداد عبر الخطوتين التاليتين وذلك باستخدام BluetoothAdapter:
الحصول على BluetoothAdapter:
تعتبر ال BluetoothAdapter مطلوبة واساسية في كل فعاليات بلوتوث Bluetooth activity.ويعيد هذا الصف BluetoothAdapter الذي يمثل محول البلوتوث الخاص بالجهاز ذاته device’s own Bluetooth adapter(the Bluetooth radio).
يوجد فقط محول بلوتوث وحيد Bluetooth adapter للنظام باكمله, وباستطاعة تطبيقك التفاعل معه باستخدام this object. اذا كانت القيمة المعادة من قبل التابع getDefaultAdapter() مساوية ل Null, فهذا يعني بأن الجهاز لا يدعم بلوتوث وتنتهي القصة عند هذه النقطة. على سبيل المثال:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Device does not support Bluetooth }
تفعيل البلوتوث Enable Bluetooth
في المرحلة التالية : انت بحاجة للتحقق من ان البلوتوث مفعل على الجهاز.
نقوم باستدعاء التابع isEnabled() للتحقق فيما اذا كان البلوتوث حاليا مفعل. اذا كانت القيمة المعادة من هذا التابع مساوية ل false , عندها يكون البلوتوث غير مفعل. لكي تطلب ان يكون البلوتوث مفعلا عليك استدعاء التابع startActivityForResult()مع حدث ال intent التالي : ACTION_REQUEST_ENABLE. سوف يقوم هذا بارسال طلب لتفعيل البلوتوث من خلال اعدادات النظام (بدون ايقاف تطبيقك). على سبيل المثال:
if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
عندها سوف يظهر صندوق حوار يطلب من المستخدم السماح بتفعيل البلوتوث, كما هو مبين في الشكل رقم 1. في حال وافق المستخدم وضغط على “yes”عندها سوف يبدأ النظام بتفعيل البلوتوث وسيعود التركيز إلى تطبيقك ما إن تنتهي الاجرائية (أو تفشل).
في المثال السابق – تم تميرير الثابت REQUEST_ENABLE_BT إلى التابع startActivityForResult()وهو عبارة عن عدد صحيح معرف محليا (ويجب ان تكون قيمته اكبر من 0), ومن ثم يعيد النظام تمرير هذا الثابت مرة اخرى إليك وذلك ضمن تحقيق التابع onActivityResult()وهو بمثابة معامل “ترميز الطلب requestCode”.
في حال نجحت عملية تفعيل البلوتوث, عندها سوف تتلقى فعاليتك ترميز النتيجة التالي RESULT_OK وذلك ضمن استدعاء التابع onActivityResult(). في حال لم يتم تفعيل البلوتوث على الجهاز بسبب خطأ ما ( او في حال ضغط المستخدم على خيار “No”) عندها فإن ترميز النتيجة هو التالي : RESULT_CANCELED.
بشكل خياري , باستطاع تطبيقك ايضا ان يتصنت على ACTION_STATE_CHANGED (الخاصة ب Intent البث broadcast Intent), والتي سوف يقوم النظام ببثها ما إن تتغير حالة البلوتوث. يحوي هذا البث على حقول اضافية : EXTRA_STATE و EXTRA_PREVIOUS_STATE تحوي على حالة البلوتوث القديمة والحديثة بالترتيب. القيم المحتملة لهذه الحقول الاضافية هي التالية :
يعتبر الانصات لهذا البث مفيد جدا لاكتشاف التغيرات التي تطرأ على حالة البلوتوث اثناء عمل تطبيقك.
ملاحظة : إن تفعيل الاستكشاف discoverability سوف يفعل البلوتوث بشكل تلقائي. في حال كنت تخطط بشكل دائم لتفعيل مستكشف الاجهزة قبل تنفيذ فعالية البلوتوث , عندها بإمكانك تجاوز الخطوة 2 اعلاه. بالامكان قراءة المزيد حول enabling discoverability ادناه.
ايجاد الاجهزة Finding Devices
بإمكانك عبر استخدام BluetoothAdapterايجاد اجهزة بلوتوث اما عبر مستكشف الاجهزة device discovery او عبر الاستعلام عن قائمة الاجهزة المقترنة paired (bonded)devices.
استكشاف الاجهزة Device discovery : عبارة عن اجرائية مسح تقوم بالبحث ضمن المنطقة المحلية عن اجهزة البلوتوث المفعلة ومن ثم تطلب بعض المعلومات عن كل واحد منها (يشار في بعض الاحيان إلى هذه العملية ب ” discovering” , “inquiring” , “scanning” ).
على كل الاحوال, سوف تستجيب اجهزة البلوتوث ضمن المنطقة المحلية إلى طلب الاكتشاف فقط في حال كانت مفعلة لأن يتم اكتشافها. في حال كان الجهاز قابل للاستكشاف, عندها فإنه سوف يستجيب لطلب الاستكشاف discovery request عبر مشاركة بعض المعلومات , مثل اسم الجهاز device name, الصف class , بالاضافة إلى عنوان MAC الفريد. باستخدام هذه المعلومات, يصبح بإمكان الجهاز الذي يقوم بالاستكشاف ان يختار ان يبدأ بعملية اتصال مع الاجهزة التي تم ايجادها.
ما إن يتم لأول مرة انشاء الاتصال مع جهاز عن بعد, حتى يظهر طلب اقتران pairing request بشكل اوتوماتيكي للمستخدم. عندما يقترن الجهاز, فإن المعلومات الاساسية حول الجهاز (مثل اسم الجهاز, الصف , عنوان ال MAC) سيتم حفظها ويمكن قرائتها بواسطة واجهات بلوتوث Bluetooth APIs.
باستخدام عنوان ال MAC المعروف لجهاز عن بعد , يصبح بالامكان بدء اتصال مع ذاك الجهاز في أي وقت بدون القيام بعملية البحث (حيث يعتبر الجهاز ضمن المجال).
يجب ان تتذكر بأن هنالك اختلاف بين كون الجهاز مقترن , وبين كونه متصل.
ان يكون الجهاز مقترن paired يعني بأن كلا الجهازين يعلمان بوجود بعضهما البعض, ويوجد بينهما shared link-key يمكن استخدمها بهدف التوثق authentication, وقادران على انشاء اتصال مشفر بين بعضهما البعض.
اما ان يكون الجهاز متصل connected فهذا يعني بأن الاجهزة تتشارك حاليا قناة RFCOMM وهي قادرة على ارسال المعطيات لبعضها البعض.
إن واجهات بلوتوث ضمن اندرويد الحالية Android Bluetooth API’s تتطلب ان يكون الجهازان مقترنان paired قبل انشاء اتصال من نوع RFCOMM.(يتم انجاز الاقتران بشكل اوتوماتيكي عندما تبدأ باتصال مشفر عبر Bluetooth APIs).
توصف المقاطع التالية كيفية ايجاد الاجهزة التي تم الاقتران بها, او اكتشاف الاجهزة الجديدة عبر استخدام “مكتشف الاجهزة” device discovery.
ملاحظة :لا تعتبر الاجهزة التي تحوي نظام تشغيل اندرويد قابلة للاكشاف بشكل افتراضي . يستطيع المستخدم ان يجعل الجهاز قابل للاستكشاف لوقت محدود عبر اعدادات النظام. سيتم فيما بعض مناقشة “تفعيل امكانية الاستكشاف enable discoverabilityفيما بعد”.
الاستعلام عن الاجهزة المتقرنة querying paired devices
قبل القيام باجرائية استكشاف الاجهزة, يجدر الاستعلام عن الاجهزة المتقرنة لمعرفة فيما اذا كان الجهاز المطلوب ضمنها للتو. للقيام بذلك , يتم استدعاء التابع getBondedDevices() . سوف يعيد هذا التابع مجموعة من BluetoothDevices تمثل الاجهزة المقترنة.
على سبيل المثال, بالإمكان الاستعلام عن كل الاجهزة المقترنة ومن ثم يتم اظهار اسم كل جهاز للمستخدم , باستخدام ArrayAdapter:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); // If there are paired devices if (pairedDevices.size() > 0) { // Loop through paired devices for (BluetoothDevice device : pairedDevices) { // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } }
كل ما يحتاجه غرض BluetoothDevice لكي يبدأ اتصال هو عنوان ال MAC.
في هذا المثال, يتم حفظه على انه جزء من ArrayAdapter التي يتم اظهارها للمستخدم.
يمكن فيما بعد استخلاص عنوان ال MAC لكي نبدأ الاتصال. بالامكان تعلم المزيد حول انشاء الاتصال ضمن قسم Connecting Devices.
استكشاف الاجهزة Discovering devices
لكي تبدأ باستكشاف الاجهزة, بإمكانك ببساطة استدعاء التابع startDiscovery().تعتبر هذه الاجرائية غير متزامنة asynchronous , وسوف يعيد هذا التابع فورا قيمة منطقية تشير فيما اذا كان الاستكشاف قد بدأ بشكل ناحج ام لا. عادة ما تتضمن اجرائية الاستكشاف استعلام ومسح يستمر حوالي 12 ثانية , ويتبعه مسح لكل جهاز يتم العثور عليه بهدف استعادة اسم البلوتوث.
يتوجب على تطبيقك ان يسجل BroadcastReceiver من اجل intent ACTION_FOUNDلكي يتلقى المعلومات حول الاجهزة المستكشفة.
Your application must register a BroadcastReceiver for the ACTION_FOUND Intent in order to receive information about each device discovered
بالنسبة لكل جهاز, سوف يقوم النظام ببث intent من نوع ACTION_FOUND. تحمل هذه ال intent الحقول الاضافية EXTRA_DEVICEو EXTRA_CLASS , وتحوي BluetoothDevice و BluetoothClass, بالترتيب. على سبيل المثال, فيما يلي مثال يوضح كيف بإمكانك التسجيل بهدف التعامل مع البث عندما يتم استكشاف الاجهزة:
// Create a BroadcastReceiver for ACTION_FOUND private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // When discovery finds a device if (BluetoothDevice.ACTION_FOUND.equals(action)) { // Get the BluetoothDevice object from the Intent BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } }; // Register the BroadcastReceiver IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
كل ما هو مطلوب من غرض BluetoothDevice لكي يستطيع ان يبدأ الاتصال هو عنوان ال MAC.ضمن المثال السابق, يتم حفظ MAC address كجزء من ArrayAdapter الذي يتم اظهاره للمستخدم.
يمكن فيما بعد استخلاص عنوان ال MAC لكي نبدأ الاتصال.بالامكان تعلم المزيد حول انشاء الاتصال في القسم الخاص ب “الاتصال بين الاجهزة Connecting Devices“.
تنبيه : تعتبر عملية استكشاف الاجهزة اجرائية ثقيلة بالنسبة لمحول البلوتوث Bluetooth adapter وسوف تستهلك الكثير من الموارد. لذلك ما ان تجد جهاز لتتصل معه , تاكد من ايقاف عملية البحث عبر استدعاء التابع cancelDiscovery()قبل محاولة القيام بالاتصال. ايضا في حال كنت للتو تملك اتصال مع جهاز ما , عندها فإن انجاز عملية الاستكشاف سوف يقوم بتقليل عرض الحزمة bandwidth المتاح بشكل كبير وخصوصا بالنسبة للاتصال, لذلك لا يتوجب عليك القيام بالبحث واستكشاف الاجهزة مادمت متصلا مع احد الاجهزة.
تفعيل الاستكشاف Enabling discoverability
في حال كنت ترغب بان تجعل جهاز محلي قابل للاستكشاف من قبل بقية الاجهزة , قم باستدعاء التابع startActivityForResult(Intent, int) مع حدث ال Intent التالي ACTION_REQUEST_DISCOVERABLE. سوف يقوم هذا بارسال اشعار بطلب تفعيل نمط الاستكشاف عبر اعدادات النظام ( بدون ايقاف تطبيقك).
بشكل افتراضي , سوف يصبح الجهاز قابل للاستكشاف لمدة 120 ثانية.بإمكانك تعريف وتحديد فترة زمنية اخرى عبر اضافة EXTRA_DISCOVERABLE_DURATIONضمن المعاملات الاضافة extra الخاصة بتلك ال intent. اقصى قيمة للفترة الزمنية الخاصة التي يكون فيها الاستكشاف مفعلا تبلغ 3600 ثانية, اما القيمة 0 فهي تعني بأن الجهاز دوما قابل للاستكشاف. واي قيمة ضمن المجال 0 للقيمة 3600 فإنها سوف تسند بشكل اوتوماتيكي إلى القيمة 120 ثانية. على سبيل المثال, يقوم الكود ادناه بتحديد قيمة الفترة ب 300 :
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent);

سوف يتم عرض صندوق حوار, يطلب من المستخدم الاذن لجعل الجهاز قابل للاستكشاف, كما هو موضح ضمن الشكل 2. في حال ضغط المستخدم على كلمة “Yes” عندها سوف يصبح الجهاز قابل للاستكشاف وذلك ضمن الفترة المحددة من الزمن.
بعدها سوف تتلقى فعاليتك الاستدعاء onActivityResult()) مع ترميز نتيجة result code مساوي للتفرة التي كان فيها الجهاز قابل للاستكشاف.
اما في حال ضغط المستخدم على “No” اوحدث خلل ما , عندها سوف يكون ترميز النتيجة مساوي ل RESULT_CANCELED.
ملاحظة : في حال لم يكن البلوتوث مفعل على الجهاز, عندها فإن تفعيل استكشاف الجهاز سوف يفعل بشكل اوتوماتيكي بلوتوث.
سوف يبقى الجهاز بشكل صامت ضمن نمط الاستكشاف خلال الفترة الزمنية المحددة. في حال كنت ترغب بأن يتم اعلامك عند تغير نمط الاستكشاف , عليك بتسجيل BroadcastReceiver لل Intent : ACTION_SCAN_MODE_CHANGED
سوف يحوي هذا ايضا على حقول اضافية EXTRA_SCAN_MODEو EXTRA_PREVIOUS_SCAN_MODE, والتي تخبرك بقيم نمط المسح الجديدة والقديمة بالترتيب.
القيم المحتملة لكل منها هي التالية :
SCAN_MODE_CONNECTABLE_DISCOVERABLE
والتي تشير بأن الجهاز إما ضمن نمط الاستكشاف, او ليس ضمن نمط الاستكشاف ولكن لا يزال قادر على استقبال الاتصالات, او ليس ضمن نمط الاستكشاف وغير قادر على استقبال الاتصالات بالترتيب.
لا تحتاج إلى تفعيل استكشاف الجهاز في حال كنت ترغب ببدء الاتصال مع جهاز بعيد.
إن تفعيل الاستكشاف ضروري فقط عندما ترغب بأن يستضيف تطبيقك server socket التي سوف تقوم بدورها بتلقي الاتصالات connections, لأن الاجهزة عن بعد يجب ان تكون قادرة على اكتشاف الاجهزة التي ترغب الاتصال معها قبل ان تبدأ الاتصال معها.
الاتصال بالاجهزة Connecting Devices
لكي تكون قادرا على انشاء اتصال بين تطبيقك الخاص بك على جهازين, يتجب عليك ان تقوم بتنجيز implement كل من آليتي server-side و client-side, لأن احد الجهازين يجب ان يفتح server socket , ويتوجب على الجهاز الآخر ان يبدأ الاتصال initiate the connection (وذلك باستخدام عنوان ال MAC الخاص بالجهاز الذي يعمل كمخدم ليبدأ الاتصال).
يعتبر كل من جهاز المخدم server وجهاز الزبون client متصلان ببعضهما البعض عندما يملك كل منها BluetoothSocket متصلة (connected)على نفس قناة RFCOMM.
عند هذه النقطة, يستطيع كل من الجهازين الحصول على مجاري دخل وخرج input/output streams ويمكن عند هذه النقطة بدء نقل المعطيات data transfer, والذي ستتم مناقشته ضمن القسم الذي يتحدث عن “ادارة الاتصالManaging a Connection“.
يوصف هذا الفصل كيفية بدء اتصال بين جهازين.
يحصل كل من جهاز المخدم server device وجهاز العميل client device على BluetoothSocket بطرق مختلفة.
يتلقى المخدم BluetoothSocket عندما يتم قبول طلب اتصال قادم إليه.
يتلقى العميل BluetoothSocket عندما يقوم بفتح قناة RFCOMM مع المخدم server.

أحد التقنيات المستخدمة لتنجيز ذلك هي بأن نجهز كل جهاز من الاجهزة على انه المخدم server, وبذلك فإن كل جهاز من الاجهزة يكون لديه امكانية بدء اتصال مع الجهاز الآخر الذي بدوره سوف يصبح العميل.
الطريقة البديلة هي التالية :بإمكان احد الاجهزة ان يستضيف host الاتصال (connection) بشكل صريح ويقوم بفتح server socket بحسب الطلب , وبإمكان الجهاز الاخر ان يبدأ الاتصال ببساطة.
ملاحظة : في حال لم يكن كلا الجهازين قد اقرنا من قبل, عندها فإن اطار عمل اندرويد سوف يقوم بشكل اوتوماتيكي بعرض تنبيه لطلب اقتران أو صندوق حوار للمستخدم خلال اجرائية الاتصال, كما هو مبين ضمن الشكل 3. لذلك عند محاولة الاتصال بين الاجهزة , لا يحتاج تطبيقك لأن يهتم فيما اذا كان الجهازين مقترنين ام لا. وسيتم حجب محاولات الاتصال عبر RFCOMM حتى يتم الاقتران بنجاح, او انه سيفشل في حال رفض المستخدم طلب الاقتران , او في حال فشل الاقتران , او في حال نفذ الوقت.
الاتصال كمخدم Connecting as a server
عندما ترغب بوصل جهازين, فإنه يتوجب على احد الجهازين ان يتصرف كمخدم وذلك عبر عقد BluetoothServerSocketمفتوحة.
الهدف من server socket هي الانصات إلى طلبات الاتصال القادمة , وعندما يتم قبول احد هذه الطلبات, يقوم المخدم بتزويد connected BluetoothSocket. عندما يتم الحصول على BluetoothSocket من BluetoothServerSocket, فإنه يتم تجاهل ال BluetoothServerSocket, إلا في حال كنت ترغب بالحصول على المزيد من الاتصالات(connections).
حول UUID
UUID : المعرف الفريد عالميا A universally Unique Identifier,
يستخدم كمعرف فريد للمعطيات.
الميزة المهمة ل UUID هي انه كبير بما فيه الكفاية ليصبح بإمكانك ان تختار أي قيمة عشوائية ضمن مجاله بدون ان تخاف من تضارب القيم.
في حالتنا, يستخدم كمعرف identifier فريد لخدمة بلوتوث الخاصة بتطبيقاتنا.
لكي تحصل على UUID لكي تستخدمه ضمن تطبيقك, بإمكانك استخدام واحد من العديد من مولدات UUID العشوائية الموجودة على الانترنت, ومن ثم تقوم بتهيئة UUID ب fromString(String).
فيما يلي الاجرائية الاساسية لاعداد server socket ولكي تقبل الاتصال accept a connection:
- الحصول على BluetoothServerSocket عبر استدعاء listenUsingRfcommWithServiceRecord(String, UUID).
السلسلة المحرفية عبارة عن اسم قابل للتمييز لخدمتك identifiable name , و سوف يقوم النظام بشكل اوتوماتيكي بالكتابة ضمن مدخل جديد ضمن قاعدة لمعطيات (SDP:Service Discovery Protocol) على الجهاز(الاسم عشوائي وببساطة يمكن ان يكون اسم تطبيقك).
يتم تضمين ال UUID ضمن مدخل SDP وسيكون بمثابة العنصر الاساسي فيما يخص معاملات (المحاورات التي تجري بين الجهازين ) الاتصال مع الجهاز الزبون.
عندما يحاول الزبون الاتصال مع الجهاز, فإنه سوف يحمل ال UUID الذي يميز بشكل فريد الخدمة التي ترغب بالاتصال بها.
يجب ان تكون هذه ال UUIDs متطابقة لكي يتم قبول الاتصال(في الخطوة التالية). - البدء بالتصنت لطلبات الاتصال عبر استدعاء accept().
هذا الاستدعاء يمثل استدعاء حجب blocking call (أي انه يقوم بمنع أي نوع اخر من التفاعلات ضمن التطبيق).يعود هذا الاستدعاء في احد حالتين , اما عندما يتم قبول الاتصال , او عند حدوث استثناء ما. يتم قبول الاتصال فقط عندما يرسل جهاز بعيد طلب اتصال ب UUID مطابقة لتلك المسجلة مع server socket الذي يقوم بالتنصت. عند نجاح العملية سوف يعيد التابعaccept() connected BluetoothSocket. - في حال لم ترغب بقبول المزيد من الاتصالات , عندها يتوجب عليك استدعاء التابعclose().
يقوم هذا التابع بتحرير server socket وكل الموارد, ولكنه لا يقوم باغلاق connected BluetoothSocket التي تمت اعادتها من قبل التابع accept().على نقيض TCP/IP فإن RFCOMM يسمح فقط لزبون متصل واحد على القناة في نفس الوقت, لذلك فإنه في اغلب الحالات من المنطقي استدعاء close()على BluetoothServerSocketفورا بعد قبول a connected socket.
لا يتوجب ان يتم استدعاء التابع accept()في نفس thread الخاص بواحهة المستخدم الخاصة بالفعالية الاساسية وذلك لانه عبارة عن استدعاء حجب blocking call الذي سيقوم بدوره بمنع أي نوع اخر من التفاعلات ضمن التطبيق. عادة من المنطقي ان نقوم بكل العمل المتضمن ل BluetoothServerSocket أو BluetoothSocket ضمن مجرى جديد New thread تتم ادارته والتحكم به من قبل تطبيقك.
لكي نجهض وننهي استدعاء حضر ما blocked call , ,يتم استدعاء التابع accept()على غرض BluetoothServerSocket (أو BluetoothSocket ) من مسرى آخرanother thread و بذلك سوف يتم انهاء استدعاء الحضر blocked call بشكل فوري (وسوف يعود return).
لاحظ بأن كل التوابع الخاصة ب BluetoothServerSocket أو BluetoothSocket عبارة عن توابع thread-safe.
مثال:
فيما يلي مثال على مسرى بسيط لمكون مخدم server component مسؤول عن تلقي طلبات الاتصال القادمة
privateclassAcceptThreadextendsThread{ privatefinalBluetoothServerSocket mmServerSocket; publicAcceptThread(){ // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final BluetoothServerSocket tmp =null; try{ // MY_UUID is the app's UUID string, also used by the client code tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); }catch(IOException e){} mmServerSocket = tmp; } publicvoid run(){ BluetoothSocket socket =null; // Keep listening until exception occurs or a socket is returned while(true){ try{ socket = mmServerSocket.accept(); }catch(IOException e){ break; } // If a connection was accepted if(socket !=null){ // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); mmServerSocket.close(); break; } } } /** Will cancel the listening socket, and cause the thread to finish */ publicvoid cancel(){ try{ mmServerSocket.close(); }catch(IOException e){} }}
في المثال السابق, المطلوب قبول طلب اتصال وحيد connection, ما إن يتم قبول طلب اتصال ما ويتم الحصول على BluetoothSocket, حتى يقوم التطبيق بارسال BluetoothSocketالمطلوبة إلى مسرى منفصل , ويغلق BluetoothServerSocket ويكسر الحلقة.
لاحظ بأنه عندما يعيد التابع accept(), ال BluetoothSocket, فإن ال socket تكون متصلة للتو , لذلك لست بحاجة لاستدعاء التابع connect() (كما تفعل عندما تكون من طرف الزبون client –side).
التابع manageConnectedSocket() عبارة عن تابع متخيل ضمن التطبيق, مهمته القيام بتهيئة المسرى initiate the thread بهدف نقل المعطيات , والتي سيتم مناقشة موضعها ضمن القسم الذي يتحدث عن “ادارة الاتصال about Managing a Connection“.
يتوجب اغلاق BluetoothServerSocket ما إن تنتهي من التنصت على الاتصالات القادمة incoming connections. على سبيل المثال, يتم استدعاء التابع close()ما إن يتم الحصول على BluetoothSocket.
ربما قد ترغب ايضا ان تضع تابع عام ضمن المسرى thread يستطيع ان يغلق private BluetoothSocket عند الحدث التي ترغب عنده بايقاف التنصت على server socket.
الاتصال كعميل Connecting as a client
لكي تبدأ الاتصال initiate a connection مع جهاز عن بعد ( مع جهاز يستحوز على open server socket), يتوجب عليك في البداية ان تحصل على غرض BluetoothDevice الذي يمثل جهاز عن بعد.
(تمت تغطية موضوع الحصول على BluetoothDevice ضمن المقطع “ايجاد الاجهزة Finding Devices. اعلاه).
ومن ثم يجب استخدام BluetoothDevice بهدف الحصول على BluetoothSocket وبدء الاتصال.
فيما يلي الاجرائية الاساسية :
- باستخدام BluetoothDevice, يتم الحصول على BluetoothSocket عبر استدعاء التابع createRfcommSocketToServiceRecord(UUID).
يقوم هذا بتهيئة BluetoothSocket التي سوف تقوم بالاتصال ب BluetoothDevice.
إن UUID الممرر هنا يجب ان يطابق ال UUID المستخدم من قبل الجهاز المخدم server device عندما يفتح BluetoothServerSocket الخاصة به(عبر listenUsingRfcommWithServiceRecord(String, UUID)).
إن عملية استخدام نفس ال UUID هي ببساطة عملية تثبيت قيمة السلسلة المحرفية ل UUID ضمن تطبيقك ومن ثم الاشارة إليها من ترميزي (كودي) المخدم server والعميل client.
- بدء الاتصال عبر استدعاء التابع connect().
عند هذا الاستدعاء, سيقوم النظام بتنفيذ عملية بحث SDP lookup على الجهاز الاخر بهدف المطابقة بين UUID. في حال كانت نتيجة البحث ناحجة , وقبل الجهاز الاخر عن بعد طلب الاتصال , عندها فإنه سوف يقوم بمشاركة قناة RFCOMM مع الجهاز المرسل لطلب الاتصال (الجهاز العميل) بهدف الاستخدام اثناء الاتصال , وسوف يتم اعادة connect().
يعتبر هذا الاستدعاء بمثابة استدعاء حظر blocking call, في حال- لاي سبب من الاسباب- فشل الاتصال , او انتهى وقت التابع connect() (بعد 12 ثانية), عندها فإنه سوف يلقي استثناء exception.
وبما ان التابع عبارة عن استدعاء حظر blocking call, لذلك فإن اجراء الاتصال هذا يجب ان يتم دوما ضمن مسرى منفصل separate thread بعيدا عن مسرى الفعالية الاساسية.
ملاحظة : يجب دوما التاكد من ان الجهاز لا ينفذ عملية استكشاف للاجهزة اثناء استدعاء التابع connect() .
في حال كانت عملية البحث جارية , عندها فإن محاولات الاتصال سوف تتباطئ بشكل ملحوظ ونسبة احتمال فشلها كبيرة.
مثال :
فيما يلي مثال على مسرى يقوم بتهيئة اتصال بلوتوث:
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null; mmDevice = device; // Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mBluetoothAdapter.cancelDiscovery(); try { // Connect the device through the socket. This will block // until it succeeds or throws an exception mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and get out try { mmSocket.close(); } catch (IOException closeException) { } return; } // Do work to manage the connection (in a separate thread) manageConnectedSocket(mmSocket); } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }
لاحظ بأن استدعاء التابع cancelDiscovery()قد تم قبل انشاء الاتصال. يجب عليك دوما القيام بذلك قبل الاتصال , ومن الآمن استدعاءه حتى قبل التحقق الفعلي فيما اذا كان البحث عن الاجهزة جاريا ام لا( ولكن اذا كنت ترغب بالتحقق بالامكان استدعاء التابع isDiscovering().
التابع manageConnectedSocket() عبارة عن تابع تخيلي ضمن تطبيقك الهدف منه هو تهيئة المسرى بهدف نقل المعطيات, وهذا ما ستتم مناقشته ضمن مقطع “ادارة الاتصال Managing a Connection“.
عندما تنتهي من BluetoothSocket, دوما قم باستدعاء التابع close()بهدف التنظيف. ان ذلك يفعّل القيام بشكل اوتوماتيكي باغلاق كل ال connected socket وينظف كل الوارد الداخلية.
ادارة الاتصال Managing a Connection
عندما تحقق بنجاح الاتصال بين جهازين (او اكثر), عندها سيكون لدى كل جهاز من الاجهزة connected BluetoothSocket. هنا تبدأ الحكاية , حيث يصبح بالامكان مشاركة المعطيات بين الاجهزة.
إن الاجرائية العامة لنقل معطيات عشوائية بين الاجهزة بسيطة , وتتم عبر الاستفادة من BluetoothSocket.
- الحصول على InputStream و OutputStream بهدف التعامل مع عمليات النقل عبر ال socket, عبر getInputStream(), وgetOutputStream() بالترتيب.
- قراءة وكتابة المعطيات للمجاري streams عبر استخدام كل من التابعين read(byte[]) وwrite(byte[]) .
هذا هو كل شيء !!
طبعا هنالك تفاصيل لها علاقة بتنجيز الموضوع يجب ان تاخذ بعين الاعتبار.
اولا وقبل كل شيء, يجب ان تستخدم مسرى مخصص dedicated thread لكل مجاري القراءة والكتابه هذه .هذا مهم جدا لأن كل من تابعي read(byte[])write(byte[])يعتبران استدعاءات حظر.
read(byte[])سوف يقوم بالحظر حتى يجد شيء يقوم بقرائته من المجرى stream.
write(byte[])لا يقوم بالحظر عادة , ولكن يمكن ان يقوم بحظر متحكمات التدفق في حال لم يكن الجهاز الاخر يستدعي التابع read(byte[])بالسرعة الكافية , وكانت ال buffers الوسيطة ممتلأة.
اذن الحلقة الاساسية ضمن المسرى thread يجب ان تكون مخصصة للقراءة من InputStream.
ويمكن استخدام تابع عام منفصل بهدف بدء الكتابة من OutputStream.
مثال :
فيما يلي مثال يوضح الفكرة :
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; // Get the input and output streams, using temp objects because // member streams are final try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; // buffer store for the stream int bytes; // bytes returned from read() // Keep listening to the InputStream until an exception occurs while (true) { try { // Read from the InputStream bytes = mmInStream.read(buffer); // Send the obtained bytes to the UI activity mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (IOException e) { break; } } } /* Call this from the main activity to send data to the remote device */ public void write(byte[] bytes) { try { mmOutStream.write(bytes); } catch (IOException e) { } } /* Call this from the main activity to shutdown the connection */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }
يستحصل الباني على المجاري streams الضرورية , وما ان يتنفذ, حتى يبدأ المجرى thread بانتظار المعطيات القادمة عبر InuptStream.
عندما يعود التابع read(byte[])مع البايتات القادمة من المجرى stream , عندها يتم ارسال المعطيات إلى الفعالية الاساسية باستخدام مرجع عضو member Handler ضمن الصف الاب. ومن ثم يعود وينتظر المزيد من البايتات القادمة من المسرىstream.
ان عملية ارسال المعطيات الصادرة عملية سهلة جدا , تتم ببساطة عبر استدعاء التابع write() ضمن المسرى من الفعالية الاساسية ونمرر عبره البايتات التي نرغب بارسالها.
ومن ثم يستدعي التابع write(byte[]) لارسال المعطيات إلى الجهاز عن بعد.
يعتبر التابع cancel() الخاص بالمسرى مهم جدا لانهاء الاتصال في أي وقت عبر اغلاق BluetoothSocket. يجب دوما استدعاء هذا التابع عندما تنتهي من استخدام اتصال البلوتوث.
للحصول على ديمو حول استخدام واجهات بلوتوث APIs , بالامكان مراجعة الرابط التالي: Bluetooth Chat sample app.
التعامل مع البروفايل Working with Profile
انطلاقا من نسخة اندرويد 3.0 , تضمنت واجهات بلوتوث دعم للتعامل مع بروفايلات بلوتوث.
بروفايل بلوتوث Bluetooth profile: عبارة عن مواصفات وخصائص لواجهة لاسلكية wireless interface specification خاصة بالاتصالات بين الاجهزة التي تعتمد على بلوتوث .
عند هذه النقطة وبهذا القدر ننهي حلقة اليوم , للمزيد من التفاصيل بالامكان مراجعة المراجع التي تم ذكرها , واولها من الدروس الوارة في الموقع الرسمي لاندرويد
وإلى لقاء قريب في الحلقة القادمة , وإلى ذلك الحين استودعكم الله والسلام عليكم ورحمة الله وبركاته
الترجمة |
المصطلح |
مخدم |
Server |
زبون |
Client |
اتصال |
Connection |
مجاري(تدفقات) دخل أو خرج |
input/output streams |
مسرى |
thread |
استثناء |
exception |
استدعاء حظر |
blocking call |
بروفايل |
Profile |
طرف المخدم |
Server-side |
طرف العميل |
Client-side |
Tag:Android, Android development, android Intent, Android linux, Android Market, android operation system, Android Programming, Android system, AndroidManifest file, bluetooth, Building a Flexible UI using fragments, external storage anroid, Getting a Result from an Activity, Google, Google play, internal storage, saving data android, shared preferences fiile, sqlit database android, التفاعل مع بقية التطبيقات, الحصول على نتيجة من فعالية, برمجة اندرويد, بلوتوث, تطوير وبرمجة اندرويد, حفظ المعطيات ضمن مجموعات (مفتاح - قيمة ) Saving Key-Value Sets, حفظ الملفات اندرويد, نظام اندرويد