نظرة عامة على مضيف USB

عندما يكون جهاز Android في وضع مضيف USB، فإنه يعمل كمضيف USB، ويشغّل الناقل، وتعداد أجهزة USB المتصلة. يتوفر وضع مضيف USB في الإصدار 3.1 من نظام التشغيل Android والإصدارات الأحدث.

نظرة عامة على واجهة برمجة التطبيقات

قبل أن تبدأ، من المهم أن تفهم الفصول التي تحتاج إلى العمل معها. تشير رسالة الأشكال البيانية يصف الجدول التالي واجهات برمجة التطبيقات لمضيف 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 تحديد ما إذا كان تطبيقك مهتمًا بالجهاز المتصل أو لا. إذا كان الأمر كذلك، يمكنك إعداد التواصل مع الجهاز إذا رغبت في ذلك. لإجراء ذلك، يجب أن يستوفي تطبيقك المتطلبات التالية:

  1. يمكنك اكتشاف أجهزة USB المتصلة باستخدام فلتر الأهداف ليتم إرسال إشعار عندما يكون المستخدم لتوصيل جهاز USB أو عن طريق تعداد أجهزة USB المتصلة بالفعل.
  2. اطلب من المستخدم إذنًا للاتصال بجهاز USB، إذا لم يكن قد تم الحصول عليه من قبل.
  3. يمكنك التواصل مع جهاز 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), PendingIntent.FLAG_IMMUTABLE)
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), PendingIntent.FLAG_IMMUTABLE);
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
            }
        }
    }
};

يتيح إنشاء مستقبِل البث داخل التطبيق، وليس في البيان، معالجة الأحداث المنفصلة فقط أثناء تشغيله. بهذه الطريقة، يتم دمج الأحداث يتم إرساله فقط إلى التطبيق قيد التشغيل حاليًا ولا يتم بثه إلى جميع التطبيقات.