عندما يكون جهازك الذي يعمل بنظام التشغيل 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)
جافا
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")
جافا
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 }
جافا
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") } } } } }
جافا
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)
جافا
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)
جافا
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 } } }
جافا
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 } } } }
جافا
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 } } } };
إنّ إنشاء جهاز استقبال البث داخل التطبيق، وليس في البيان، يتيح للتطبيق معالجة الأحداث المنفصلة فقط أثناء تشغيله. وبهذه الطريقة، لا يتم إرسال الأحداث المنفصلة إلا إلى التطبيق قيد التشغيل في الوقت الحالي ولا يتم بثه إلى جميع التطبيقات.