نظرة عامة على ملحق USB

يتيح وضع ملحق USB للمستخدمين توصيل أجهزة مضيف USB المصمّمة خصيصًا للأجهزة التي تعمل بنظام التشغيل Android. ويجب أن تكون الملحقات متوافقة مع بروتوكول ملحقات Android الموضح في مستندات مجموعة أدوات تطوير ملحقات Android. يتيح هذا للأجهزة التي تعمل بنظام التشغيل Android والتي لا يمكن أن تعمل كمضيف USB الاستمرار في التفاعل مع أجهزة USB. عندما يكون الجهاز الذي يعمل بنظام التشغيل Android في وضع ملحق USB، يعمل ملحق USB المتصل بنظام التشغيل Android كمضيف، ويوفّر الطاقة إلى ناقل USB ويعدد الأجهزة المتصلة. يتوافق نظام التشغيل Android 3.1 (المستوى 12 من واجهة برمجة التطبيقات) مع وضع ملحق USB، كما يتم نقل هذه الميزة إلى الإصدار Android 2.3.4 (المستوى 10 من واجهة برمجة التطبيقات) لإتاحة التوافق مع مجموعة أكبر من الأجهزة.

اختيار واجهات برمجة التطبيقات المناسبة لملحقات USB

تم تقديم واجهات برمجة تطبيقات ملحقات USB في النظام الأساسي في الإصدار 3.1 من نظام التشغيل Android، ولكنّها متوفّرة أيضًا في الإصدار 2.3.4 من نظام التشغيل Android من خلال مكتبة إضافات Google APIs. وبما أنّه تمت إعادة نقل واجهات برمجة التطبيقات هذه باستخدام مكتبة خارجية، يمكنك استيراد حزمتَين لإتاحة وضع ملحقات USB. بناءً على الأجهزة التي تعمل بنظام التشغيل Android وتريد إتاحة استخدامها، قد تحتاج إلى استخدام أحد هذه الأجهزة على الآخر:

  • com.android.future.usb: لإتاحة وضع ملحق USB في نظام التشغيل Android 2.3.4، تتضمّن مكتبة إضافات Google APIs واجهات برمجة تطبيقات ملحقات USB ذات المنفذ الخلفي ويتم تضمينها في مساحة الاسم هذه. يتيح نظام التشغيل Android 3.1 أيضًا استيراد الصفوف الموجودة في مساحة الاسم هذه والاتصال بها لدعم التطبيقات المكتوبة في مكتبة الإضافة. مكتبة الإضافة هذه هي برنامج تضمين رفيع حول واجهات برمجة التطبيقات لملحقات android.hardware.usb ولا تتوافق مع وضع مضيف USB. إذا كنت تريد التوافق مع أوسع مجموعة من الأجهزة التي تتوافق مع وضع ملحق USB، يمكنك استخدام مكتبة الإضافات واستيراد هذه الحزمة. يُرجى العِلم أنّ ميزة ملحق USB ليست مطلوبة على بعض الأجهزة التي تعمل بالإصدار 2.3.4 من نظام التشغيل Android. وتقرّر كل شركة مصنّعة للأجهزة ما إذا كانت متوافقة مع هذه الميزة أم لا، ولهذا السبب يجب توضيحها في ملف البيان.
  • android.hardware.usb: تحتوي مساحة الاسم هذه على الفئات التي تتوافق مع وضع ملحق USB في الإصدار 3.1 من Android. يتم تضمين هذه الحزمة كجزء من واجهات برمجة التطبيقات لإطار العمل، لذا يتيح نظام التشغيل Android 3.1 وضع ملحقات USB بدون استخدام مكتبة الإضافات. يمكنك استخدام هذه الحزمة إذا كنت مهتمًا فقط بالأجهزة التي تعمل بالإصدار 3.1 من نظام التشغيل Android أو الإصدارات الأحدث والتي تتيح وضع ملحقات USB في الأجهزة، والذي يمكنك توضيحه في ملف البيان.

تثبيت مكتبة إضافات Google APIs

إذا أردت تثبيت الإضافة، يمكنك إجراء ذلك عن طريق تثبيت حزمة Google APIs لنظام التشغيل Android API 10 باستخدام "مدير SDK". يُرجى الاطّلاع على تثبيت إضافة Google APIs لمزيد من المعلومات حول تثبيت مكتبة الإضافات.

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

وبما أنّ المكتبة الإضافية هي برنامج تضمين لواجهات برمجة التطبيقات الخاصة بإطار العمل، فإنّ الفئات التي تتيح ميزة ملحقات USB مشابهة. يمكنك استخدام المستندات المرجعية الخاصة بـ "android.hardware.usb" حتى في حال استخدام المكتبة الإضافية.

ملاحظة: هناك اختلاف طفيف في الاستخدام بين مكتبة الإضافات وواجهات برمجة تطبيقات إطار العمل التي يجب أن تكون على دراية بها.

يوضِّح الجدول التالي الفئات التي تتوافق مع واجهات برمجة التطبيقات لملحقات USB:

الفئة الوصف
UsbManager تتيح لك تعداد ملحقات USB المتصلة والتواصل معها.
UsbAccessory يمثّل ملحق USB ويحتوي على طرق للوصول إلى معلومات التعريف الخاصة به.

اختلافات الاستخدام بين مكتبة الإضافات وواجهات برمجة تطبيقات النظام الأساسي

هناك اختلافان في الاستخدام بين استخدام مكتبة إضافات Google APIs وواجهات برمجة تطبيقات النظام الأساسي.

إذا كنت تستخدم المكتبة الإضافية، عليك الحصول على الكائن UsbManager على النحو التالي:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

إذا كنت لا تستخدم المكتبة الإضافية، عليك الحصول على الكائن UsbManager بالطريقة التالية:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

عند الفلترة للوصول إلى ملحق متصل يتضمّن فلتر أهداف، يتم تضمين الكائن UsbAccessory في الغرض الذي تم تمريره إلى تطبيقك. إذا كنت تستخدم المكتبة الإضافية، عليك الحصول على الكائن UsbAccessory على النحو التالي:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

إذا كنت لا تستخدم المكتبة الإضافية، عليك الحصول على الكائن UsbAccessory بالطريقة التالية:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

متطلبات بيان Android

توضح القائمة التالية ما تحتاج إلى إضافته إلى ملف بيان تطبيقك قبل التعامل مع واجهات برمجة تطبيقات ملحقات USB. توضّح أمثلة ملفات البيان وملفات الموارد كيفية الإعلان عن هذه العناصر:

  • بما أنّه لا يمكن ضمان توافق جميع الأجهزة التي تعمل بنظام التشغيل Android مع واجهات برمجة التطبيقات لملحقات USB، يمكنك تضمين عنصر <uses-feature> يشير إلى أنّ تطبيقك يستخدم ميزة android.hardware.usb.accessory.
  • إذا كنت تستخدم مكتبة الإضافات، يمكنك إضافة العنصر <uses-library> مع تحديد com.android.future.usb.accessory للمكتبة.
  • اضبط الحد الأدنى من حزمة تطوير البرامج (SDK) للتطبيق على المستوى 10 لواجهة برمجة التطبيقات إذا كنت تستخدم مكتبة الإضافات أو 12 إذا كنت تستخدم حزمة android.hardware.usb.
  • إذا أردت أن يتلقّى تطبيقك إشعارًا بملحق USB موصول، يُرجى تحديد زوج العنصر <intent-filter> و<meta-data> للغرض android.hardware.usb.action.USB_ACCESSORY_ATTACHED في نشاطك الرئيسي. يشير العنصر <meta-data> إلى ملف XML خارجي يتضمّن معلومات تعريفية حول الملحق الذي تريد رصده.

    في ملف موارد XML، حدِّد عناصر <usb-accessory> للملحقات التي تريد فلترتها. ويمكن أن يتضمّن كل <usb-accessory> السمات التالية:

    • manufacturer
    • model
    • version

    لا يُنصح بالفلترة على version. قد لا يحدّد دائمًا الملحق أو الجهاز سلسلة إصدار (عمدًا أو عن غير قصد). عندما يعلن تطبيقك عن سمة إصدار للفلترة وفقًا لها ولا يحدِّد الملحق أو الجهاز سلسلة إصدار، يؤدي ذلك إلى ظهور NullPointerException في الإصدارات السابقة من Android. تم حلّ هذه المشكلة في نظام التشغيل Android 12.

    احفظ ملف المورد في دليل res/xml/. يجب أن يكون اسم ملف المورد (بدون الامتداد .xml) مطابقًا للاسم الذي حدّدته في العنصر <meta-data>. يظهر تنسيق ملف مورد XML أيضًا في المثال أدناه.

أمثلة على ملف البيان وملفات الموارد

يعرض المثال التالي نموذج البيان وملف المورد المقابل له:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.accessory" />
    
    <uses-sdk android:minSdkVersion="<version>" />
    ...
    <application>
      <uses-library android:name="com.android.future.usb.accessory" />
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" />
        </activity>
    </application>
</manifest>

في هذه الحالة، يجب حفظ ملف المورد التالي في res/xml/accessory_filter.xml، ويحدّد أنّه يجب فلترة أي ملحق يتضمّن الطراز والشركة المصنّعة والإصدار المتوافقَين. يرسل الملحق السمات التالية إلى الجهاز الذي يعمل بنظام التشغيل Android:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

استخدام الملحقات

عندما يربط المستخدمون ملحقات USB بجهاز يعمل بنظام التشغيل Android، يمكن لنظام Android تحديد ما إذا كان تطبيقك مهتمًا بالملحق المتصل. إذا كان الأمر كذلك، يمكنك إعداد اتصال بالجهاز الملحق إذا أردت ذلك. ولإجراء ذلك، يجب أن يستوفي التطبيق الشروط التالية:

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

التعرّف على أحد الإكسسوارات

يمكن لتطبيقك اكتشاف الأجهزة الملحقة إما عن طريق استخدام فلتر أهداف ليتم إشعارها عندما يوصّل المستخدم أحد الأجهزة الملحقة أو من خلال سرد الأجهزة الملحقة التي سبق أن تم توصيلها. ويكون استخدام فلتر الأهداف مفيدًا إذا أردت أن يتمكّن تطبيقك من اكتشاف الملحق المطلوب تلقائيًا. من المفيد تعداد قيمة الأجهزة الملحقة المتصلة إذا كنت تريد الحصول على قائمة بجميع الأجهزة الملحقة المتصلة أو إذا لم يتطابق تطبيقك مع هدف محدَّد.

استخدام فلتر أهداف

للسماح لتطبيقك باكتشاف ملحق USB معيّن، يمكنك تحديد فلتر أهداف من أجل الفلترة حسب الغرض من android.hardware.usb.action.USB_ACCESSORY_ATTACHED. إلى جانب فلتر الأهداف هذا، عليك تحديد ملف مورد يحدد خصائص ملحق USB، مثل الشركة المصنّعة والطراز والإصدار.

يوضّح المثال التالي كيفية الإعلان عن فلتر الأهداف:

<activity ...>
    ...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
    </intent-filter>

    <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
</activity>

يوضّح المثال التالي كيفية الإعلان عن ملف المورد المقابل الذي يحدّد ملحقات USB التي تهمّك:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

في نشاطك، يمكنك الحصول على UsbAccessory الذي يمثّل الملحق المرفق من الغرض على النحو التالي (باستخدام مكتبة الإضافة):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

أو طريقة مماثلة (باستخدام واجهات برمجة تطبيقات النظام الأساسي):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

تعداد الملحقات

يمكنك السماح لتطبيقك بتعداد الملحقات التي تعرّفت على نفسها أثناء تشغيل التطبيق.

استخدِم الطريقة getAccessoryList() للحصول على مصفوفة جميع ملحقات USB المتصلة:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();

ملاحظة: لا يتوافق سوى ملحق واحد متصل في كل مرة.

الحصول على إذن للتواصل مع أحد الملحقات

قبل الاتصال بملحق 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 accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    accessory?.apply {
                        // call method to set up accessory communication
                    }
                } else {
                    Log.d(TAG, "permission denied for accessory $accessory")
                }
            }
        }
    }
}

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) {
                UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if(accessory != null){
                        // call method to set up accessory communication
                    }
                }
                else {
                    Log.d(TAG, "permission denied for accessory " + accessory);
                }
            }
        }
    }
};

لتسجيل جهاز استقبال البث، أدرِج ما يلي في طريقة 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 accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);

عندما يردّ المستخدمون على مربّع الحوار، يتلقّى مستقبِل البث الغرض الذي يتضمّن السمة EXTRA_PERMISSION_GRANTED الإضافية، وهي قيمة منطقية تمثّل الإجابة. يُرجى التحقّق من هذا الجزء الإضافي للحصول على قيمة true قبل الاتصال بالملحق.

التواصل باستخدام أحد الملحقات

يمكنك التواصل مع الملحق باستخدام UsbManager للحصول على أداة وصف الملفات التي يمكنك من خلالها إعداد بث الإدخال والإخراج لقراءة البيانات وكتابتها باستخدام أداة الوصف. تمثل مجموعات البث نقاط النهاية المجمّعة للإدخال والمخرجات في الملحق. يجب إعداد الاتصال بين الجهاز والملحق في سلسلة محادثات أخرى، حتى لا يتم قفل سلسلة التعليمات الرئيسية في واجهة المستخدم. يوضح المثال التالي كيفية فتح ملحق للاتصال به:

Kotlin

private lateinit var accessory: UsbAccessory
private var fileDescriptor: ParcelFileDescriptor? = null
private var inputStream: FileInputStream? = null
private var outputStream: FileOutputStream? = null
...

private fun openAccessory() {
    Log.d(TAG, "openAccessory: $mAccessory")
    fileDescriptor = usbManager.openAccessory(accessory)
    fileDescriptor?.fileDescriptor?.also { fd ->
        inputStream = FileInputStream(fd)
        outputStream = FileOutputStream(fd)
        val thread = Thread(null, this, "AccessoryThread")
        thread.start()
    }
}

Java

UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
...

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    fileDescriptor = usbManager.openAccessory(accessory);
    if (fileDescriptor != null) {
        FileDescriptor fd = fileDescriptor.getFileDescriptor();
        inputStream = new FileInputStream(fd);
        outputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

في طريقة run() الخاصة بسلسلة المحادثات، يمكنك القراءة والكتابة في الملحق باستخدام العنصرَين FileInputStream أو FileOutputStream. عند قراءة البيانات من ملحق يتضمّن عنصر FileInputStream، يجب التأكّد من أنّ المخزن المؤقت الذي تستخدمه كبير بما يكفي لتخزين بيانات حزمة USB. ويتوافق بروتوكول ملحق Android مع حِزم البيانات المخزنة مؤقتًا يصل حجمها إلى 16384 بايت، لذا يمكنك أن تختار الإعلان دائمًا عن أنّ المخزن المؤقت بهذا الحجم لتسهيل العملية.

ملاحظة: عند المستوى الأدنى، تكون حِزم البيانات 64 بايت لملحقات USB كاملة السرعة و512 بايت لملحقات USB عالية السرعة. ويجمع بروتوكول ملحق Android الحِزم معًا للسرعتَين في حزمة منطقية واحدة لتسهيل الأمر.

لمزيد من المعلومات حول استخدام سلاسل المحادثات في Android، يُرجى الاطّلاع على العمليات وسلاسل المحادثات.

إنهاء الاتصال باستخدام أحد الأجهزة الملحقة

عند الانتهاء من الاتصال بأحد الأجهزة الملحقة أو إذا تم فصله، أغلِق أداة وصف الملف التي فتحتها من خلال طلب الرقم close(). للاستماع إلى الأحداث المنفصلة، يمكنك إنشاء جهاز استقبال بث، كما هو موضّح أدناه:

Kotlin

var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED == intent.action) {
            val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
            accessory?.apply {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
}

Java

BroadcastReceiver usbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
            UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
            if (accessory != null) {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
};

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