عندما يكون الجهاز الذي يعمل بنظام التشغيل Android في وضع مضيف USB، فإنّه يعمل كمضيف USB ويشغّل الناقل ويعدد أجهزة USB المتصلة. يتوافق وضع مضيف USB في الإصدار Android 3.1 والإصدارات الأحدث.
نظرة عامة على واجهة برمجة التطبيقات
قبل أن تبدأ، من المهم فهم الفصول التي تحتاج إلى العمل معها. يصف الجدول التالي واجهات برمجة تطبيقات مضيف USB في حزمة android.hardware.usb
.
الجدول 1. واجهات برمجة تطبيقات مضيف USB
الفئة | الوصف |
---|---|
UsbManager |
تتيح لك تعداد أجهزة USB المتصلة والاتصال بها. |
UsbDevice |
يمثل جهاز USB متصلاً ويحتوي على طرق للوصول إلى معلومات التعريف والواجهات ونقاط النهاية. |
UsbInterface |
تمثل هذه السمة واجهة لجهاز USB، وتحدّد مجموعة من الوظائف للجهاز. يمكن أن يحتوي الجهاز على واجهة واحدة أو أكثر للاتصال من خلالها. |
UsbEndpoint |
يمثل نقطة نهاية للواجهة، وهي قناة اتصال لهذه الواجهة. ويمكن أن تحتوي الواجهة على نقطة نهاية واحدة أو أكثر، وعادةً ما تحتوي على نقاط نهاية للإدخال والإخراج للاتصال الثنائي بالجهاز. |
UsbDeviceConnection |
يمثل الاتصال بالجهاز الذي ينقل البيانات على نقاط النهاية. تتيح لك هذه الفئة إرسال البيانات بشكل متزامن أو غير متزامن. |
UsbRequest |
ويمثِّل طلبًا غير متزامن للاتصال بجهاز من خلال UsbDeviceConnection . |
UsbConstants |
تحدّد هذه السياسة ثوابت USB التي تتوافق مع التعريفات في linux/usb/ch9.h الخاصة بنواة Linux. |
في معظم الحالات، تحتاج إلى استخدام جميع هذه الفئات (تكون UsbRequest
مطلوبة فقط في حال إجراء اتصال غير متزامن)
عند الاتصال بجهاز USB. وبشكل عام، تحصل على UsbManager
لاسترداد UsbDevice
المطلوب.
عندما يكون الجهاز بحوزتك، عليك العثور على UsbInterface
وUsbEndpoint
المناسبتَين لتلك الواجهة للتواصل من خلالهما. بعد الحصول على نقطة النهاية الصحيحة، افتح UsbDeviceConnection
للاتصال بجهاز USB.
متطلبات بيان Android
توضح القائمة التالية ما تحتاج إلى إضافته إلى ملف بيان تطبيقك قبل التعامل مع واجهات برمجة تطبيقات مضيف USB:
- بما أنّه لا يمكن ضمان توافق جميع الأجهزة التي تعمل بنظام التشغيل Android مع واجهات برمجة تطبيقات مضيف USB، يمكنك تضمين عنصر
<uses-feature>
يشير إلى أنّ تطبيقك يستخدم ميزةandroid.hardware.usb.host
. - اضبط الحد الأدنى لحزمة تطوير البرامج (SDK) للتطبيق على مستوى واجهة برمجة التطبيقات 12 أو مستوى أعلى. ولا تتوفر واجهات برمجة التطبيقات لمضيف USB في مستويات واجهة برمجة التطبيقات السابقة.
- إذا أردت أن يتلقّى تطبيقك جهاز USB موصولًا، حدِّد زوجًا من
<intent-filter>
و<meta-data>
لعنصرandroid.hardware.usb.action.USB_DEVICE_ATTACHED
في نشاطك الرئيسي. يشير العنصر<meta-data>
إلى ملف XML خارجي يتضمّن معلومات تعريفية حول الجهاز الذي تريد رصده.في ملف موارد XML، حدِّد عناصر
<usb-device>
لأجهزة USB التي تريد فلترتها. تصف القائمة التالية سمات<usb-device>
. وبشكل عام، استخدِم معرّف المورّد والمنتج إذا كنت تريد فلترة جهاز معيّن واستخدِم الفئة والفئة الفرعية والبروتوكول إذا أردت الفلترة للوصول إلى مجموعة من أجهزة USB، مثل أجهزة التخزين الكبيرة أو الكاميرات الرقمية. لا يمكنك تحديد أي من هذه السمات أو تحديد كل السمات. لا يتطابق تحديد أي سمات مع كل جهاز USB، لذا نفِّذ هذا الإجراء فقط إذا كان التطبيق يتطلب ذلك:vendor-id
product-id
class
subclass
protocol
(الجهاز أو الواجهة)
احفظ ملف المورد في دليل
res/xml/
. يجب أن يكون اسم ملف المورد (بدون الامتداد .xml) مطابقًا للاسم الذي حدّدته في العنصر<meta-data>
. يمكنك الاطّلاع على تنسيق ملف XML المتوفّر في المثال أدناه.
أمثلة على ملف البيان وملفات الموارد
يعرض المثال التالي نموذج البيان وملف المورد المقابل له:
<manifest ...> <uses-feature android:name="android.hardware.usb.host" /> <uses-sdk android:minSdkVersion="12" /> ... <application> <activity ...> ... <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity> </application> </manifest>
في هذه الحالة، يجب حفظ ملف المورد التالي في
res/xml/device_filter.xml
وتحديد أنّه يجب فلترة أي جهاز USB يتضمّن
السمات المحدّدة:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> </resources>
العمل مع الأجهزة
عندما يصل المستخدمون أجهزة USB بجهاز يعمل بنظام التشغيل Android، يمكن لنظام Android تحديد ما إذا كان تطبيقك مهتمًا بالجهاز المتصل. وإذا كان الأمر كذلك، يمكنك إعداد الاتصال بالجهاز عند الحاجة. ولإجراء ذلك، يجب أن يستوفي التطبيق الشروط التالية:
- يمكنك استكشاف أجهزة USB المتصلة من خلال استخدام فلتر أهداف ليتم رصدها عندما يوصِّل المستخدم جهاز USB أو من خلال إحصاء أجهزة USB التي سبق أن تم توصيلها.
- اطلب من المستخدم الإذن للاتصال بجهاز USB، إذا لم يسبق لك الحصول عليه.
- يمكنك التواصل مع جهاز USB عن طريق قراءة البيانات وكتابتها على نقاط النهاية المناسبة للواجهة.
اكتشاف جهاز
يمكن لتطبيقك اكتشاف أجهزة USB إما عن طريق استخدام فلتر أهداف ليتم إعلامه عندما يوصِّل المستخدم جهازًا أو عن طريق تعداد أجهزة USB المتصلة مسبقًا. ويكون استخدام فلتر الأهداف مفيدًا إذا كنت تريد أن تتيح لتطبيقك اكتشاف الجهاز المطلوب تلقائيًا. يُعدّ تعداد أجهزة USB المتصلة مفيدًا إذا كنت تريد الحصول على قائمة بجميع الأجهزة المتصلة أو في حال عدم فلترة التطبيق حسب الغرض.
استخدام فلتر أهداف
للسماح لتطبيقك باكتشاف جهاز USB معيّن، يمكنك تحديد فلتر أهداف من أجل
الفلترة للوصول إلى الغرض android.hardware.usb.action.USB_DEVICE_ATTACHED
. بالإضافة إلى
فلتر الأهداف هذا، عليك تحديد ملف موارد يحدِّد سمات جهاز USB،
مثل معرّف المنتج ومعرّف المورّد. عندما يوصل المستخدمون جهازًا يتطابق مع فلتر الجهاز،
يعرض النظام لهم مربّع حوار يسألهم ما إذا كانوا يريدون بدء تشغيل التطبيق.
في حال قبول المستخدمين، سيحصل تطبيقك تلقائيًا على إذن بالوصول إلى الجهاز إلى أن
يتم فصل الجهاز.
يوضّح المثال التالي كيفية الإعلان عن فلتر الأهداف:
<activity ...> ... <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity>
يوضّح المثال التالي كيفية الإعلان عن ملف المورد المقابل الذي يحدّد أجهزة USB التي تهمّك:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" /> </resources>
في نشاطك، يمكنك الحصول على UsbDevice
الذي يمثّل الجهاز المتصل من الغرض على النحو التالي:
Kotlin
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
Java
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
تعداد الأجهزة
إذا كان تطبيقك مهتمًا بفحص جميع أجهزة USB المتصلة حاليًا
أثناء تشغيل التطبيق، يمكنه تعداد الأجهزة على الناقل. استخدِم الطريقة getDeviceList()
للحصول على خريطة تجزئة لجميع أجهزة USB المتصلة. يتم تحديد خريطة التجزئة حسب اسم جهاز USB إذا كنت تريد الحصول على جهاز من الخريطة.
Kotlin
val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... val deviceList = manager.getDeviceList() val device = deviceList.get("deviceName")
Java
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
وإذا أردت، يمكنك أيضًا الحصول على مُكرّر من خريطة التجزئة ومعالجة كل جهاز واحدًا تلو الآخر:
Kotlin
val manager = getSystemService(Context.USB_SERVICE) as UsbManager .. val deviceList: HashMap<String, UsbDevice> = manager.deviceList deviceList.values.forEach { device -> //your code }
Java
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while(deviceIterator.hasNext()){ UsbDevice device = deviceIterator.next(); //your code }
الحصول على إذن للاتصال بأحد الأجهزة
قبل الاتصال بجهاز USB، يجب أن يحصل التطبيق على إذن من المستخدمين.
ملاحظة: إذا كان تطبيقك يستخدم فلتر الأهداف لاكتشاف أجهزة USB أثناء اتصالها، سيحصل التطبيق تلقائيًا على الإذن إذا سمح المستخدم للتطبيق بمعالجة الغرض. وإذا لم يكن الأمر كذلك، يجب طلب الإذن صراحةً في تطبيقك قبل الاتصال بالجهاز.
قد يكون طلب الإذن صراحةً ضروريًا في بعض الحالات، مثلاً عندما يسرد تطبيقك أجهزة USB المتصلة من قبل ثم يريد الاتصال بأحدها. يجب التحقُّق من إذن الوصول إلى الجهاز قبل محاولة الاتصال به. وفي حال عدم تشغيله، ستتلقى رسالة خطأ في وقت التشغيل إذا رفض المستخدم الإذن بالوصول إلى الجهاز.
للحصول على إذن صريح، يجب أولاً إنشاء جهاز استقبال بث. يرصد جهاز الاستقبال هذا
الغرض الذي يتم بثه عند الاتصال برقم requestPermission()
. تعرض المكالمة إلى requestPermission()
مربّع حوار للمستخدم الذي يطلب الإذن بالاتصال بالجهاز. يعرض الرمز النموذجي التالي طريقة إنشاء جهاز استقبال البث:
Kotlin
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" private val usbReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (ACTION_USB_PERMISSION == intent.action) { synchronized(this) { val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { device?.apply { //call method to set up device communication } } else { Log.d(TAG, "permission denied for device $device") } } } } }
Java
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if(device != null){ //call method to set up device communication } } else { Log.d(TAG, "permission denied for device " + device); } } } } };
لتسجيل جهاز استقبال البث، أضِف ما يلي في طريقة onCreate()
في نشاطك:
Kotlin
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" ... val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0) val filter = IntentFilter(ACTION_USB_PERMISSION) registerReceiver(usbReceiver, filter)
Java
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; ... permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbReceiver, filter);
لعرض مربّع الحوار الذي يطلب من المستخدمين الإذن بالاتصال بالجهاز، عليك استدعاء طريقة requestPermission()
:
Kotlin
lateinit var device: UsbDevice ... usbManager.requestPermission(device, permissionIntent)
Java
UsbDevice device; ... usbManager.requestPermission(device, permissionIntent);
عندما يردّ المستخدمون على مربّع الحوار، يتلقّى مستقبِل البث الغرض الذي يتضمّن
السمة EXTRA_PERMISSION_GRANTED
الإضافية، وهي قيمة
منطقية تمثّل الإجابة. يُرجى التحقّق من هذه السمة الإضافية لمعرفة القيمة true قبل الاتصال بالجهاز.
التواصل مع جهاز
يمكن أن يكون الاتصال بجهاز USB إما متزامنًا أو غير متزامن. وفي كلتا الحالتين، عليك إنشاء سلسلة محادثات جديدة لإجراء جميع عمليات نقل البيانات عليها، لكي لا تحظر سلسلة محادثات واجهة المستخدم. لإعداد الاتصال بجهاز بشكل صحيح، يجب الحصول على UsbInterface
وUsbEndpoint
المناسبتَين للجهاز المطلوب الاتصال به وإرسال الطلبات على نقطة النهاية هذه باستخدام UsbDeviceConnection
. بشكل عام، يجب أن تتضمن التعليمة البرمجية:
- راجِع سمات العنصر
UsbDevice
، مثل معرّف المنتج أو رقم تعريف المورّد أو فئة الجهاز لمعرفة ما إذا كنت تريد الاتصال بالجهاز أم لا. - عندما تكون متأكدًا من أنّك تريد الاتصال بالجهاز، ابحث عن رمز
UsbInterface
المناسب الذي تريد استخدامه للتواصل معUsbEndpoint
بالشكل المناسب لتلك الواجهة. يمكن أن تحتوي الواجهات على نقطة نهاية واحدة أو أكثر، وعادةً ما يكون لها نقطة نهاية للإدخال والإخراج للاتصال الثنائي. - عند العثور على نقطة النهاية الصحيحة، افتح
UsbDeviceConnection
على نقطة النهاية هذه. - يمكنك توفير البيانات التي تريد نقلها على نقطة النهاية باستخدام طريقة
bulkTransfer()
أوcontrolTransfer()
. يجب تنفيذ هذه الخطوة في سلسلة محادثات أخرى لمنع حظر سلسلة التعليمات الرئيسية في واجهة المستخدم. لمزيد من المعلومات حول استخدام سلاسل المحادثات في Android، يُرجى الاطّلاع على العمليات وسلاسل المحادثات.
يعتبر مقتطف الرمز التالي طريقة بسيطة لإجراء عملية نقل متزامن للبيانات. يجب أن يتضمّن الرمز الخاص بك منطقًا أكثر للعثور على الواجهة الصحيحة ونقاط النهاية الصحيحة للتواصل عليها، ويجب أيضًا نقل البيانات في سلسلة محادثات مختلفة عن سلسلة محادثات واجهة المستخدم الرئيسية:
Kotlin
private lateinit var bytes: ByteArray private val TIMEOUT = 0 private val forceClaim = true ... device?.getInterface(0)?.also { intf -> intf.getEndpoint(0)?.also { endpoint -> usbManager.openDevice(device)?.apply { claimInterface(intf, forceClaim) bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT) //do in another thread } } }
Java
private Byte[] bytes; private static int TIMEOUT = 0; private boolean forceClaim = true; ... UsbInterface intf = device.getInterface(0); UsbEndpoint endpoint = intf.getEndpoint(0); UsbDeviceConnection connection = usbManager.openDevice(device); connection.claimInterface(intf, forceClaim); connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
لإرسال البيانات بشكل غير متزامن، يمكنك استخدام الصف UsbRequest
من أجل initialize
وqueue
لطلب غير متزامن، ثم انتظار النتيجة
باستخدام requestWait()
.
إنهاء الاتصال بأحد الأجهزة
عند الانتهاء من الاتصال بجهاز أو إذا كان الجهاز منفصلاً، أغلِق UsbInterface
وUsbDeviceConnection
من خلال الاتصال بـ releaseInterface()
وclose()
. للاستماع إلى الأحداث المنفصلة،
أنشِئ جهاز استقبال بث على النحو التالي:
Kotlin
var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) { val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) device?.apply { // call your method that cleans up and closes communication with the device } } } }
Java
BroadcastReceiver usbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { // call your method that cleans up and closes communication with the device } } } };
إنّ إنشاء جهاز استقبال البث داخل التطبيق، وليس في البيان، يسمح للتطبيق بمعالجة الأحداث المنفصلة أثناء تشغيلها فقط. وبهذه الطريقة، لا يتم إرسال الأحداث المنفصلة إلا إلى التطبيق قيد التشغيل في الوقت الحالي، ولا يتم بثها إلى جميع التطبيقات.