סקירה כללית של אביזרי USB

מצב אביזר USB מאפשר למשתמשים להתחבר חומרת מארח USB שתוכננה במיוחד למכשירים מבוססי Android. האביזרים צריכים להיות תואמים לפרוטוקול האביזר של Android שמתואר בתיעוד של Android Accessory Development Kit. הפעולה הזו מאפשרת למכשירים מבוססי Android, שלא יכולים לפעול כמארח USB, להמשיך לקיים אינטראקציה עם USB חומרה. כשמכשיר מבוסס Android נמצא במצב אביזר USB, מחובר Android USB האביזר משמש כמארח, מספק חשמל לאפיק ה-USB וסופר את המכשירים המחוברים. מערכת Android 3.1 (רמת API 12) תומכת במצב אביזר USB וגם התכונה מועברת לאחור אל Android 2.3.4 (רמת API 10) כדי לאפשר תמיכה במגוון רחב יותר של מכשירים.

בחירת ממשקי ה-API המתאימים של אביזרי USB

למרות שממשקי ה-API של אביזרי USB נוספו לפלטפורמה ב-Android 3.1, הם גם זמינות ב-Android 2.3.4 באמצעות ספריית התוספים של Google APIs. כי ממשקי ה-API האלה לאחר העברה לאחור באמצעות ספרייה חיצונית, יש שתי חבילות שניתן לייבא כדי לתמוך ב-USB מצב 'אביזר'. יכול להיות שתצטרכו לתמוך במכשירים מבוססי-Android שבהם רוצים לתמוך משתמשים באחד מהשניים:

  • com.android.future.usb: כדי לתמוך במצב אביזר USB ב-Android 2.3.4, תוסף Google APIs הספרייה כוללת את ממשקי ה-API של אביזרי ה-USB עם העברה לאחור והם כלולים מרחב שמות. Android 3.1 תומך גם בייבוא של כיתות במרחב השמות הזה והפעלה שלהן כדי לתמוך באפליקציות שנכתבו באמצעות ספריית התוספים. ספריית התוספים הזו היא wrapper צר סביב ממשקי ה-API של האביזר android.hardware.usb ולא תומך במצב אירוח USB. אם המיקום אם אתם רוצים לתמוך במגוון הרחב ביותר של מכשירים שתומכים במצב אביזר USB, אתם יכולים להשתמש בתוסף ולייבא את החבילה הזו. חשוב לציין שלא כל מכשירי Android 2.3.4 נתמכים שנדרש לתמיכה בתכונה 'אביזר USB'. כל יצרן מכשיר מחליט האם לתמוך ביכולת הזו, ולכן עליך להצהיר על כך במניפסט חדש.
  • android.hardware.usb: מרחב השמות הזה מכיל את המחלקות שתומכות ב-USB מצב 'אביזר' ב-Android 3.1. החבילה הזו כלולה כחלק מממשקי ה-API של framework, כך Android 3.1 תומך במצב אביזר USB ללא שימוש בספריית תוספים. שימוש בחבילה הזו אם חשובים לך רק מכשירי Android 3.1 ואילך עם תמיכה בחומרה ב-USB למצב אביזר, שניתן להצהיר עליו בקובץ המניפסט.

התקנה של ספריית התוספים של Google APIs

כדי להתקין את התוסף, צריך להתקין את Google APIs Android API 10 חבילה עם מנהל ה-SDK. למידע נוסף, ניתן לעיין בקטע התקנת ממשקי ה-API של Google. למידע נוסף על התקנת ספריית התוספים,

סקירה כללית על ממשקי API

מכיוון שספריית התוספים היא wrapper של ממשקי ה-API של framework, המחלקות שתומכות ב-framework מאפייני אביזר ה-USB דומים. אפשר להיעזר במסמכי העזר של android.hardware.usb גם אם משתמשים בספריית התוספים.

הערה: עם זאת, יש שימוש קטן ההבדל בין ממשקי ה-API של ספריית התוספים ושל framework, שחשוב להיות מודעים להם.

הטבלה הבאה מתארת את המחלקות שתומכות בממשקי API של אביזרי USB:

דרגה תיאור
UsbManager מאפשר לבצע ספירה ולתקשר עם אביזרי USB מחוברים.
UsbAccessory מייצג אביזר USB ומכיל שיטות לגישה מידע.

הבדלים בשימוש בין ממשקי ה-API של ספריית התוספים ושל הפלטפורמה

יש שני הבדלים בשימוש בין השימוש בספריית התוספים של Google APIs לבין השימוש בפלטפורמה ממשקי API.

אם משתמשים בספריית התוספים, צריך לקבל את האובייקט 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);

כשמסננים לפי אביזר מחובר עם מסנן Intent, האובייקט UsbAccessory נמצא בתוך ה-Intent שמועבר אל תרגום מכונה. אם משתמשים בספריית התוספים, צריך לקבל את האובייקט 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

ברשימה הבאה מפורטים מה צריך להוסיף לקובץ המניפסט של האפליקציה לפני עבודה עם ממשקי API של אביזרי USB. המניפסט וקובץ המשאבים דוגמאות: איך להצהיר (declare) על הפריטים האלה:

  • מכיוון שלא כל המכשירים מבוססי Android יתמכו בממשקי API של אביזרים ל-USB, כוללים רכיב <uses-feature> שמצהיר שהאפליקציה משתמשת את התכונה android.hardware.usb.accessory.
  • אם משתמשים בספריית התוספים, מוסיפים את הרכיב <uses-library> שמציין com.android.future.usb.accessory לספרייה.
  • צריך להגדיר את ה-SDK המינימלי של האפליקציה לרמת API 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. מגלים אביזרים מחוברים באמצעות מסנן Intent שמסננים לפי אביזר אירועים מצורפים או על ידי ספירת אביזרים מחוברים ומציאת האביזרים המתאימים.
  2. צריך לבקש מהמשתמש הרשאה לתקשר עם האביזר, אם עדיין לא שקיבלתם.
  3. כדי לתקשר עם האביזר באמצעות קריאה וכתיבה של נתונים בממשק המתאים נקודות קצה (endpoints).

חיפוש אביזר

האפליקציה שלך יכולה לגלות אביזרים באמצעות מסנן Intent כדי לקבל התראה כאשר שהמשתמש מחבר אביזר או על ידי ספירת האביזרים שכבר מחוברים. באמצעות מסנן Intent הוא שימושי אם אתם רוצים לאפשר לאפליקציה לזהות באופן אוטומטי האביזר הרצוי. ספירת האביזרים המחוברים היא שימושית אם רוצים לקבל רשימה של כל האביזרים אביזרים מחוברים או אם האפליקציה לא סוננה לפי Intent.

שימוש במסנן Intent

כדי שהאפליקציה תמצא אביזר USB ספציפי, אפשר לציין מסנן Intent כדי לסנן לפי ה-Intent 'android.hardware.usb.action.USB_ACCESSORY_ATTACHED'. יחד באמצעות מסנן ה-Intent הזה, עליכם לציין קובץ משאבים שמציין את המאפיינים של ה-USB אביזרים, כמו יצרן, דגם וגרסה.

הדוגמה הבאה מראה איך להצהיר על מסנן Intent:

<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 שמייצגת האביזר המצורף מתוך Intent כמו בדוגמה הבאה (עם ספריית התוסף):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

או כך (באמצעות ממשקי ה-API של הפלטפורמה):

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, האפליקציה צריכה לקבל הרשאה משתמשים.

הערה: אם האפליקציה משתמשת מסנן Intent כדי לגלות אביזרים בזמן שהם מחוברים, הוא מקבל באופן אוטומטי הרשאה אם המשתמש מאפשר לאפליקציה לטפל בכוונה. אם לא, צריך לשלוח בקשה הרשאה מפורשת באפליקציה לפני החיבור לאביזר.

ייתכן שיהיה צורך לבקש הרשאה מפורשת במצבים מסוימים, למשל האפליקציה סופרת אביזרים שכבר מחוברים ואחר כך רוצים לתקשר איתם אחת. חובה לבדוק אם יש הרשאת גישה לאביזר לפני שמנסים לתקשר איתו. אם לא, תתקבל שגיאת זמן ריצה אם המשתמש דחה את ההרשאה לגשת אחר.

כדי לקבל הרשאה באופן מפורש, קודם צריך ליצור מקלט שידורים. המקלט הזה מאזין של הכוונה שמשודרת כשמתקשרים אל 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);
                }
            }
        }
    }
};

כדי לרשום את מקלט השידורים, יש להוסיף את הקוד הזה ל-method 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 למטרות הבאות: לקבל מתאר קובץ שאפשר להגדיר בו שידורי קלט ופלט, כדי לקרוא ולכתוב נתונים. עם תיאור. השידורים מייצגים את נקודות הקצה בכמות גדולה לקלט ולפלט של האביזר. עליך להגדיר את התקשורת בין המכשיר והאביזר בשרשור אחר, כך שלא תנעלו את ה-thread הראשי של ממשק המשתמש. הדוגמה הבאה מראה איך לפתוח אביזר כדי לתקשר איתו:

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 תומך מאגר נתונים זמני של עד 16,384 בייטים, כך שתוכלו לבחור תמיד להצהיר שהמאגר הזמני הוא כזה כדי לשמור על פשטות.

הערה: ברמה נמוכה יותר, המנות הן 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
            }
        }
    }
};

יצירת מקלט השידור בתוך האפליקציה, ולא במניפסט, מאפשרת אפליקציה שמטפלת באירועים שמנותקים רק בזמן שהיא פועלת. כך, אירועים מנותקים נשלחת רק לאפליקציה שפועלת כרגע ולא משודרת לכל האפליקציות.