כשהמכשיר מבוסס-Android נמצא במצב מארח USB, הוא משמש כמארח ה-USB, מפעיל את האוטובוס, וסופר את התקני ה-USB המחוברים. מצב מארח USB נתמך ב-Android מגרסה 3.1 ואילך.
סקירה כללית על API
לפני שמתחילים, חשוב להבין עם אילו כיתות צריך לעבוד.
בטבלה הבאה מתוארים ממשקי ה-API של מארח ה-USB בחבילה android.hardware.usb
.
דרגה | תיאור |
---|---|
UsbManager |
הרשאה לספירת התקני USB מחוברים ולתקשר איתם. |
UsbDevice |
מייצג מכשיר USB מחובר ומכיל שיטות לגישה מידע, ממשקים ונקודות קצה. |
UsbInterface |
מייצג ממשק של התקן USB, שמגדיר קבוצה של פונקציונליות עבור במכשיר. מכשיר יכול לכלול ממשק אחד או יותר שבהם אפשר לתקשר. |
UsbEndpoint |
מייצג נקודת קצה (endpoint) של ממשק, שהיא ערוץ תקשורת עבור הממשק הזה. יכולה להיות נקודת קצה (endpoint) אחת או יותר, ובדרך כלל יש בה נקודות קצה לקלט ולפלט תקשורת דו-כיוונית עם המכשיר. |
UsbDeviceConnection |
מייצג חיבור למכשיר, שמעביר נתונים על נקודות קצה. הכיתה הזו מאפשרת לשלוח נתונים הלוך ושוב באופן סינכרוני או אסינכרוני. |
UsbRequest |
מייצג בקשה אסינכרונית לתקשורת עם מכשיר דרך UsbDeviceConnection . |
UsbConstants |
מגדירה קבועי USB שתואמים להגדרות ב-linux/usb/ch9.h של Linux. של הליבה. |
ברוב המקרים, צריך להשתמש בכל הכיתות האלה (UsbRequest
נדרש רק אם מבצעים תקשורת אסינכרונית).
בזמן תקשורת עם התקן USB. באופן כללי, צריך לקבל UsbManager
כדי לאחזר את ה-UsbDevice
הרצוי.
כשהמכשיר נמצא אצלך, עליך לאתר את ה-UsbInterface
וה-UsbEndpoint
המתאימים
הממשק לתקשורת. לאחר קבלת נקודת הקצה הנכונה, פותחים UsbDeviceConnection
כדי לתקשר עם התקן ה-USB.
דרישות לגבי מניפסטים ב-Android
ברשימה הבאה מפורטים מה צריך להוסיף לקובץ המניפסט של האפליקציה לפני עבודה עם ממשקי ה-API של מארח ה-USB:
- מכיוון שלא כל המכשירים מבוססי Android יתמכו בממשקי API של מארח ה-USB,
כוללים רכיב
<uses-feature>
שמצהיר שהאפליקציה משתמשת את התכונהandroid.hardware.usb.host
. - צריך להגדיר את ערכת ה-SDK המינימלית של האפליקציה ל-API ברמה 12 ואילך. ממשקי ה-API של מארח ה-USB לא שקיימים ברמות ה-API המוקדמות יותר.
- אם ברצונך שהאפליקציה שלך תקבל התראה על התקן 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 מחוברים באמצעות מסנן Intent לקבלת התראה כשהמשתמש שמחבר התקן USB או על ידי ספירת התקני USB שכבר מחוברים.
- צריך לבקש מהמשתמש הרשאה להתחבר להתקן ה-USB, אם הוא עדיין לא התקבל.
- תקשורת עם התקן ה-USB על ידי קריאה וכתיבה של נתונים בממשק המתאים נקודות קצה (endpoints).
היכרות עם מכשיר
האפליקציה שלך יכולה לגלות התקני USB באמצעות מסנן Intent לקבלת התראה כאשר המשתמש מחבר מכשיר או על ידי ספירת התקני USB שכבר מחוברים. באמצעות מסנן Intent הוא שימושי אם אתם רוצים לאפשר לאפליקציה לזהות באופן אוטומטי של המכשיר הרצוי. ספירת התקני USB מחוברים היא שימושית אם רוצים לקבל רשימה של כל המכשירים או אם האפליקציה לא סוננה לפי Intent.
שימוש במסנן Intent
כדי שהאפליקציה שלך תאתר התקן USB ספציפי, אפשר לציין מסנן Intent כדי
לסנן לפי ה-Intent android.hardware.usb.action.USB_DEVICE_ATTACHED
. יחד עם
את מסנן ה-Intent הזה, עליכם לציין קובץ משאבים שמציין את המאפיינים של ה-USB
המכשיר, כגון מזהה המוצר ומזהה הספק. כשמשתמשים מחברים מכשיר שתואם למכשיר שלכם
מסנן, המערכת מציגה להם תיבת דו-שיח ששואלת אם הם רוצים להפעיל את האפליקציה.
אם המשתמשים יאשרו את ההזמנה, לאפליקציה יש הרשאה אוטומטית לגשת למכשיר עד
שהמכשיר מנותק.
הדוגמה הבאה מראה איך להצהיר על מסנן Intent:
<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
שמייצגת
את המכשיר המחובר מתוך Intent, למשל:
Kotlin
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
Java
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
ספירת מכשירים
אם האפליקציה שלך מעוניינת לבדוק את כל התקני ה-USB שמחוברים כרגע
בזמן שהיישום פועל, הוא יכול לספור מכשירים באוטובוס. אפשר להשתמש בשיטה getDeviceList()
כדי לקבל מפת גיבוב (hash) של כל הפריטים
התקני ה-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");
אם רוצים, אפשר גם לקבל איטרטור ממפת הגיבוב (hash) ולעבד כל מכשיר בנפרד אחד:
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, האפליקציה צריכה לקבל הרשאה משתמשים.
הערה: אם האפליקציה משתמשת מסנן Intent כדי לגלות התקני 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
המתאים בממשק הזה. לממשקים אפשר להגדיר אחד או יותר, בדרך כלל תהיה נקודת קצה (endpoint) דו-כיוונית של קלט ופלט תקשורת בלתי מכוונת. - כשמוצאים את נקודת הקצה הנכונה, פותחים
UsbDeviceConnection
בנקודת הקצה הזאת. - מספקים את הנתונים שרוצים לשדר בנקודת הקצה באמצעות השיטה
bulkTransfer()
אוcontrolTransfer()
. אתם צריכים לבצע את השלב הזה בשרשור אחר כדי למנוע חסימה של ה-thread הראשי של ממשק המשתמש. לקבלת מידע נוסף למידע על השימוש בשרשורים ב-Android, ראו תהליכים שרשורים.
קטע הקוד הבא הוא דרך טריוויאלית לבצע העברת נתונים סינכרונית. הקוד שלך צריכה להיות יותר לוגיקה כדי למצוא בצורה נכונה את הממשק ונקודות הקצה הנכונים לתקשורת וגם לבצע העברת נתונים בשרשור שונה מה-thread הראשי של ממשק המשתמש:
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 } } } };
יצירת מקלט השידור בתוך האפליקציה, ולא במניפסט, מאפשרת אפליקציה שמטפלת באירועים שמנותקים רק בזמן שהיא פועלת. כך, אירועים מנותקים נשלחת רק לאפליקציה שפועלת כרגע ולא משודרת לכל האפליקציות.