Android מספק מסגרת חזקה מבוססת-לוח להעתקה ולהדבקה. הוא תומך בסוגי נתונים פשוטים ומורכבים, כולל מחרוזות טקסט, מבני נתונים מורכבים, נתוני טקסט ונתוני זרם בינאריים ונכסי אפליקציה. נתוני טקסט פשוטים מאוחסנים ישירות בלוח, ואילו נתונים מורכבים מאוחסנים כהפניה שהאפליקציה להדבקה פותרת עם ספק התוכן. אפשר להעתיק ולהדביק גם בתוך אפליקציה וגם בין אפליקציות שמטמיעות את המסגרת.
מאחר שחלק מהמסגרת משתמש בספקי תוכן, במסמך הזה אנו מניחים שאתם מכירים את Android Content Provider API, שמתואר בקטע ספקי תוכן.
משתמשים מצפים למשוב כשהם מעתיקים תוכן ללוח העריכה, לכן בנוסף למסגרת שמפעילה את ההעתקה וההדבקה, מערכת Android מציגה למשתמשים ממשק משתמש שמוגדר כברירת מחדל כשהם מעתיקים ב-Android 13 (API ברמה 33) ואילך. בגלל התכונה הזו, יש סיכון לקבלת התראה כפולה. מידע נוסף על מקרה קיצון כזה זמין בקטע הימנעות מהצגת התראות כפולות.
לספק משוב למשתמשים באופן ידני בזמן העתקה ב-Android 12L (רמת API 32) ומטה. המלצות לכך מפורטות במסמך הזה.
מסגרת הלוח
כשמשתמשים במסגרת של הלוח, צריך להעביר נתונים לאובייקט של קליפ, ואז להעביר את אובייקט הקליפ ללוח ברמת המערכת. אובייקט הקליפ יכול להיות באחת משלוש צורות:
- טקסט
- מחרוזת טקסט. מוסיפים את המחרוזת ישירות לאובייקט הקליפ, ואז מעבירים אותו ללוח העריכה. כדי להדביק את המחרוזת, מקבלים את אובייקט הקליפ מלוח העריכה ומעתיקים את המחרוזת לאחסון של האפליקציה.
- URI
-
אובייקט
Uri
שמייצג כל צורה של URI. האפשרות הזו מיועדת בעיקר להעתקת נתונים מורכבים מספק תוכן. כדי להעתיק נתונים, מעבירים אובייקטUri
לאובייקט קליפ ומעבירים את אובייקט הקליפ ללוח העריכה. כדי להדביק את הנתונים, מקבלים את אובייקט הקליפ, מקבלים את האובייקטUri
, מקשרים אותו למקור נתונים, כמו ספק תוכן, ומעתיקים את הנתונים מהמקור לאחסון של האפליקציה. - כוונה
-
Intent
. אפשר להשתמש בהם כדי להעתיק קיצורי דרך של אפליקציות. כדי להעתיק נתונים, יוצריםIntent
, מכניסים אותו לאובייקט קליפ ומעבירים את אובייקט הקליפ ללוח. כדי להדביק את הנתונים, מקבלים את אובייקט הקליפ ומעתיקים את האובייקטIntent
לאזור הזיכרון של האפליקציה.
הלוח יכול להכיל רק אובייקט אחד בכל פעם. כשאפליקציה מעבירה אובייקט של קליפס ללוח העריכה, אובייקט הקליפס הקודם נעלם.
אם אתם רוצים לאפשר למשתמשים להדביק נתונים באפליקציה, אתם לא צריכים לטפל בכל סוגי הנתונים. אתם יכולים לבדוק את הנתונים שבמקלדת לפני שאתם נותנים למשתמשים אפשרות להדביק אותם. בנוסף לפורמט נתונים מסוים, אובייקט הקליפ מכיל גם מטא-נתונים שמציינים אילו סוגי MIME זמינים. המטא-נתונים האלה עוזרים לכם להחליט אם האפליקציה שלכם יכולה לעשות משהו שימושי עם נתוני הלוח. לדוגמה, אם יש לכם אפליקציה שמטפלת בעיקר בטקסט, כדאי לכם להתעלם מאובייקטים של קליפים שמכילים URI או כוונה.
כדאי גם לאפשר למשתמשים להדביק טקסט ללא קשר לפורמט הנתונים שבמקלדת. כדי לעשות זאת, צריך לאלץ את נתוני הלוח לייצוג טקסט, ואז להדביק את הטקסט הזה. הנושא הזה מתואר בקטע אילוץ הלוח לטקסט.
כיתות של לוח
בקטע הזה מתוארות הכיתות שבהן משתמשת מסגרת הלוח.
ClipboardManager
הלוח של מערכת Android מיוצג על ידי הכיתה הגלובלית ClipboardManager
.
אין ליצור מופע ישירות של הכיתה הזו. במקום זאת, צריך לקבל הפניה אליו באמצעות קריאה ל-getSystemService(CLIPBOARD_SERVICE)
.
ClipData, ClipData.Item ו-ClipDescription
כדי להוסיף נתונים ללוח, יוצרים אובייקט ClipData
שמכיל תיאור של הנתונים ואת הנתונים עצמם. הלוח יכול להכיל רק ClipData
אחד בכל פעם. אובייקט ClipData
מכיל אובייקט ClipDescription
ואובייקט ClipData.Item
אחד או יותר.
אובייקט ClipDescription
מכיל מטא-נתונים על הקליפ. באופן ספציפי, הוא מכיל מערך של סוגי MIME זמינים לנתוני הקליפ. בנוסף, ב-Android 12 ואילך (רמת API 31 ואילך), המטא-נתונים כוללים מידע על כך שהאובייקט מכיל טקסט בסגנון מיוחד ועל סוג הטקסט באובייקט.
כשאתם מעבירים קליפ ללוח העריכה, המידע הזה זמין לאפליקציות להדבקה, שיכולות לבדוק אם הן יכולות לטפל בנתוני הקליפ.
אובייקט ClipData.Item
מכיל את הטקסט, ה-URI או נתוני הכוונה:
- טקסט
-
CharSequence
. - URI
-
Uri
. בדרך כלל הוא מכיל URI של ספק תוכן, אבל כל URI מותר. האפליקציה שמספקת את הנתונים מעבירה את ה-URI ללוח. אפליקציות שרוצות להדביק את הנתונים מקבלות את ה-URI מהלוח ומשתמשות בו כדי לגשת לספק התוכן או למקור נתונים אחר ולשלוף את הנתונים. - כוונה
-
Intent
. סוג הנתונים הזה מאפשר להעתיק קיצור דרך של אפליקציה ללוח העריכה. לאחר מכן, המשתמשים יכולים להדביק את קיצור הדרך באפליקציות שלהם לשימוש עתידי.
אפשר להוסיף יותר מאובייקט ClipData.Item
אחד לקליפ. כך המשתמשים יכולים להעתיק ולהדביק כמה קטעים כקליפ אחד. לדוגמה, אם יש לכם ווידג'ט של רשימה שמאפשר למשתמש לבחור יותר מפריט אחד בכל פעם, תוכלו להעתיק את כל הפריטים ללוח העריכה בבת אחת. לשם כך, יוצרים ClipData.Item
נפרד לכל פריט ברשימה, ואז מוסיפים את אובייקטי ה-ClipData.Item
לאובייקט ClipData
.
שיטות נוחות של ClipData
בכיתה ClipData
יש שיטות נוחות סטטיות ליצירת אובייקט ClipData
עם אובייקט ClipData.Item
יחיד ואובייקט ClipDescription
פשוט:
-
newPlainText(label, text)
- מחזירה אובייקט
ClipData
שהאובייקט היחיד שלו מסוגClipData.Item
מכיל מחרוזת טקסט. התווית של האובייקטClipDescription
מוגדרת כ-label
. סוג ה-MIME היחיד ב-ClipDescription
הואMIMETYPE_TEXT_PLAIN
.משתמשים ב-
newPlainText()
כדי ליצור קליפ ממחרוזת טקסט. -
newUri(resolver, label, URI)
- מחזירה אובייקט
ClipData
שהאובייקט היחיד שלו מסוגClipData.Item
מכיל URI. התווית של האובייקטClipDescription
מוגדרת כ-label
. אם ה-URI הוא URI של תוכן – כלומר, אם הפונקציהUri.getScheme()
מחזירה את הערךcontent:
– השיטה משתמשת באובייקטContentResolver
שסופק ב-resolver
כדי לאחזר את סוגי ה-MIME הזמינים מספק התוכן. לאחר מכן, המערכת שומרת אותם ב-ClipDescription
. לכתובת URI שאינה כתובת URI מסוגcontent:
, ה-method מגדיר את סוג ה-MIME בתורMIMETYPE_TEXT_URILIST
.משתמשים ב-
newUri()
כדי ליצור קליפ מ-URI, במיוחד מ-URI מסוגcontent:
. -
newIntent(label, intent)
- מחזירה אובייקט
ClipData
שהאובייקט היחיד שלו מסוגClipData.Item
מכילIntent
. התווית של האובייקטClipDescription
מוגדרת כ-label
. סוג ה-MIME מוגדר כ-MIMETYPE_TEXT_INTENT
.משתמשים ב-
newIntent()
כדי ליצור קליפ מאובייקטIntent
.
איך מאלצים את נתוני הלוח להפוך לטקסט
גם אם האפליקציה שלכם מטפלת רק בטקסט, תוכלו להעתיק נתונים שאינם טקסט מהלוח על ידי המרתם באמצעות השיטה ClipData.Item.coerceToText()
.
השיטה הזו ממירה את הנתונים ב-ClipData.Item
לטקסט ומחזירה CharSequence
. הערך שמוחזר על ידי ClipData.Item.coerceToText()
מבוסס על צורת הנתונים ב-ClipData.Item
:
- טקסט
-
אם
ClipData.Item
הוא טקסט – כלומר, אםgetText()
לא null – הפונקציה coerceToText() מחזירה את הטקסט. - URI
-
אם
ClipData.Item
הוא URI – כלומר, אםgetUri()
לא null –coerceToText()
מנסה להשתמש בו כ-URI של תוכן.- אם כתובת ה-URI היא כתובת URI של תוכן והספק יכול להחזיר פיד טקסט, הפונקציה
coerceToText()
מחזירה פיד טקסט. - אם כתובת ה-URI היא URI של תוכן אבל הספק לא מציע שידור טקסט, הפונקציה
coerceToText()
מחזירה ייצוג של כתובת ה-URI. הייצוג זהה לזה שמוחזר על ידיUri.toString()
. - אם ה-URI הוא לא URI של תוכן, הפונקציה
coerceToText()
מחזירה ייצוג של ה-URI. הייצוג זהה לזה שמוחזר על ידיUri.toString()
.
- אם כתובת ה-URI היא כתובת URI של תוכן והספק יכול להחזיר פיד טקסט, הפונקציה
- כוונה
- אם
ClipData.Item
הואIntent
– כלומר, אםgetIntent()
לא null –coerceToText()
ממירה אותו ל-URI של כוונה ומחזירה אותו. הייצוג זהה לזה שמוחזר על ידיIntent.toUri(URI_INTENT_SCHEME)
.
תרשים 2 מסכם את המסגרת של הלוח. כדי להעתיק נתונים, האפליקציה מעבירה אובייקט ClipData
ללוח העריכה הגלובלי ClipboardManager
. השדה ClipData
מכיל אובייקט ClipData.Item
אחד או יותר ואובייקט ClipDescription
אחד. כדי להדביק נתונים, האפליקציה מקבלת את ClipData
, את סוג ה-MIME שלו מה-ClipDescription
ואת הנתונים מה-ClipData.Item
או מספק התוכן שמצוין ב-ClipData.Item
.
העתקה ללוח
כדי להעתיק נתונים ללוח העריכה, מקבלים אחיזה (handle) לאובייקט הגלובלי ClipboardManager
, יוצרים אובייקט ClipData
ומוסיפים לו אובייקט ClipDescription
ואובייקט ClipData.Item
אחד או יותר. לאחר מכן מוסיפים את אובייקט ה-ClipData
המוגמר לאובייקט ClipboardManager
. הנושא הזה מוסבר בהרחבה בתהליך הבא:
- אם מעתיקים נתונים באמצעות URI של תוכן, צריך להגדיר ספק תוכן.
- אחזור של לוח העריכה של המערכת:
Kotlin
when(menuItem.itemId) { ... R.id.menu_copy -> { // if the user selects copy // Gets a handle to the clipboard service. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } }
Java
... // If the user selects copy. case R.id.menu_copy: // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
-
מעתיקים את הנתונים לאובייקט
ClipData
חדש:-
לטקסט
Kotlin
// Creates a new text clip to put on the clipboard. val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
Java
// Creates a new text clip to put on the clipboard. ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
-
עבור URI
קטע הקוד הזה יוצר URI על ידי קידוד מזהה רשומה ב-URI של התוכן של הספק. הטכניקה הזו מוסברת בהרחבה בקטע קידוד מזהה ב-URI.
Kotlin
// Creates a Uri using a base Uri and a record ID based on the contact's last // name. Declares the base URI string. const val CONTACTS = "content://com.example.contacts" // Declares a path string for URIs, used to copy data. const val COPY_PATH = "/copy" // Declares the Uri to paste to the clipboard. val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName") ... // Creates a new URI clip object. The system uses the anonymous // getContentResolver() object to get MIME types from provider. The clip object's // label is "URI", and its data is the Uri previously created. val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
Java
// Creates a Uri using a base Uri and a record ID based on the contact's last // name. Declares the base URI string. private static final String CONTACTS = "content://com.example.contacts"; // Declares a path string for URIs, used to copy data. private static final String COPY_PATH = "/copy"; // Declares the Uri to paste to the clipboard. Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName); ... // Creates a new URI clip object. The system uses the anonymous // getContentResolver() object to get MIME types from provider. The clip object's // label is "URI", and its data is the Uri previously created. ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
-
לכוונה
קטע הקוד הזה יוצר
Intent
לאפליקציה ומכניס אותו לאובייקט הקליפ:Kotlin
// Creates the Intent. val appIntent = Intent(this, com.example.demo.myapplication::class.java) ... // Creates a clip object with the Intent in it. Its label is "Intent" // and its data is the Intent object created previously. val clip: ClipData = ClipData.newIntent("Intent", appIntent)
Java
// Creates the Intent. Intent appIntent = new Intent(this, com.example.demo.myapplication.class); ... // Creates a clip object with the Intent in it. Its label is "Intent" // and its data is the Intent object created previously. ClipData clip = ClipData.newIntent("Intent", appIntent);
-
לטקסט
-
מעבירים את אובייקט הקליפ החדש ללוח העריכה:
Kotlin
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Java
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
שליחת משוב כשמעתיקים ללוח
משתמשים מצפים למשוב חזותי כשאפליקציה מעתיקה תוכן ללוח העריכה. הפעולה הזו מתבצעת באופן אוטומטי למשתמשים ב-Android מגרסה 13 ואילך, אבל צריך להטמיע אותה באופן ידני בגרסאות קודמות.
החל מ-Android 13, המערכת מציגה אישור חזותי רגיל כשתוכן מתווסף ללוח. האישור החדש מבצע את הפעולות הבאות:
- אישור שהתוכן הועתק בהצלחה.
- תצוגה מקדימה של התוכן שהועתק.
בגרסאות Android 12L (רמת API 32) וגרסאות ישנות יותר, יכול להיות שהמשתמשים לא יהיו בטוחים אם הם העתיקו תוכן או מה הם העתיקו. התכונה הזו מאפשרת לאפליקציות להציג התראות שונות לאחר ההעתקה, ומעניקה למשתמשים יותר שליטה על הלוח.
הימנעות מהצגת התראות כפולות
ב-Android 12L (רמת API 32) ובגרסאות ישנות יותר, מומלץ להציג למשתמשים הודעה חזותית באפליקציה לאחר ההעתקה, באמצעות ווידג'ט כמו Toast
או Snackbar
, כדי להודיע להם שההעתקה בוצעה בהצלחה.
כדי למנוע הצגה כפולה של מידע, מומלץ מאוד להסיר הודעות טקסט או סרגל סטטוסים שמופיעים אחרי עותק באפליקציה ב-Android 13 ואילך.
דוגמה להטמעה:
fun textCopyThenPost(textCopied:String) { val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager // When setting the clipboard text. clipboardManager.setPrimaryClip(ClipData.newPlainText ("", textCopied)) // Only show a toast for Android 12 and lower. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast.makeText(context, “Copied”, Toast.LENGTH_SHORT).show() }
הוספת תוכן רגיש ללוח
אם האפליקציה מאפשרת למשתמשים להעתיק תוכן רגיש ללוח העריכה, כמו סיסמאות או פרטי כרטיס אשראי, צריך להוסיף דגל ל-ClipDescription
ב-ClipData
לפני שמפעילים את ClipboardManager.setPrimaryClip()
. הוספת הדגל הזה מונעת את הצגת התוכן הרגיש באישור החזותי של תוכן שהועתק בגרסה 13 ואילך של Android.
כדי לסמן תוכן רגיש, מוסיפים מאפיין בווליאני ל-ClipDescription
. כל האפליקציות צריכות לעשות זאת, ללא קשר לרמת ה-API לטירגוט.
// If your app is compiled with the API level 33 SDK or higher. clipData.apply { description.extras = PersistableBundle().apply { putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true) } } // If your app is compiled with a lower SDK. clipData.apply { description.extras = PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) } }
הדבקה מהלוח
כפי שמתואר למעלה, כדי להדביק נתונים מהלוח, צריך לקבל את האובייקט הגלובלי של הלוח, לקבל את אובייקט הקליפ, לבדוק את הנתונים שלו, ואם אפשר, להעתיק את הנתונים מאובייקט הקליפ לאחסון שלכם. בקטע הזה נסביר בפירוט איך להדביק את שלוש הצורות של נתוני הלוח.
הדבקת טקסט פשוט
כדי להדביק טקסט פשוט, מקבלים את הלוח הגלובלי ומוודאים שהוא יכול להחזיר טקסט פשוט. לאחר מכן מקבלים את אובייקט הקליפ ומעתיקים את הטקסט שלו לאחסון שלכם באמצעות getText()
, כפי שמתואר בתהליך הבא:
- אפשר לקבל את האובייקט הגלובלי
ClipboardManager
באמצעותgetSystemService(CLIPBOARD_SERVICE)
. בנוסף, מגדירים משתנה גלובלי שיכיל את הטקסט המודבק:Kotlin
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Java
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
- קובעים אם צריך להפעיל או להשבית את האפשרות 'הדבקה' בפעילות הנוכחית. מוודאים שהבורד מכיל קליפ ושאתם יכולים לטפל בסוג הנתונים שמיוצגים בקליפ:
Kotlin
// Gets the ID of the "paste" menu item. val pasteItem: MenuItem = menu.findItem(R.id.menu_paste) // If the clipboard doesn't contain data, disable the paste menu item. // If it does contain data, decide whether you can handle the data. pasteItem.isEnabled = when { !clipboard.hasPrimaryClip() -> { false } !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> { // Disables the paste menu item, since the clipboard has data but it // isn't plain text. false } else -> { // Enables the paste menu item, since the clipboard contains plain text. true } }
Java
// Gets the ID of the "paste" menu item. MenuItem pasteItem = menu.findItem(R.id.menu_paste); // If the clipboard doesn't contain data, disable the paste menu item. // If it does contain data, decide whether you can handle the data. if (!(clipboard.hasPrimaryClip())) { pasteItem.setEnabled(false); } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) { // Disables the paste menu item, since the clipboard has data but // it isn't plain text. pasteItem.setEnabled(false); } else { // Enables the paste menu item, since the clipboard contains plain text. pasteItem.setEnabled(true); }
- מעתיקים את הנתונים מהלוח. אפשר להגיע לנקודה הזו בקוד רק אם פריט התפריט 'הדבקה' מופעל, כך שאפשר להניח שהלוח מכיל טקסט פשוט. עדיין לא ידוע אם הוא מכיל מחרוזת טקסט או URI שמפנה לטקסט פשוט.
קטע הקוד הבא בודק את זה, אבל הוא מציג רק את הקוד לטיפול בטקסט פשוט:
Kotlin
when (menuItem.itemId) { ... R.id.menu_paste -> { // Responds to the user selecting "paste". // Examines the item on the clipboard. If getText() doesn't return null, // the clip item contains the text. Assumes that this application can only // handle one item at a time. val item = clipboard.primaryClip.getItemAt(0) // Gets the clipboard as text. pasteData = item.text return if (pasteData != null) { // If the string contains data, then the paste operation is done. true } else { // The clipboard doesn't contain text. If it contains a URI, // attempts to get data from it. val pasteUri: Uri? = item.uri if (pasteUri != null) { // If the URI contains something, try to get text from it. // Calls a routine to resolve the URI and get data from it. // This routine isn't presented here. pasteData = resolveUri(pasteUri) true } else { // Something is wrong. The MIME type was plain text, but the // clipboard doesn't contain text or a Uri. Report an error. Log.e(TAG,"Clipboard contains an invalid data type") false } } } }
Java
// Responds to the user selecting "paste". case R.id.menu_paste: // Examines the item on the clipboard. If getText() does not return null, // the clip item contains the text. Assumes that this application can only // handle one item at a time. ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); // Gets the clipboard as text. pasteData = item.getText(); // If the string contains data, then the paste operation is done. if (pasteData != null) { return true; // The clipboard doesn't contain text. If it contains a URI, attempts to get // data from it. } else { Uri pasteUri = item.getUri(); // If the URI contains something, try to get text from it. if (pasteUri != null) { // Calls a routine to resolve the URI and get data from it. // This routine isn't presented here. pasteData = resolveUri(Uri); return true; } else { // Something is wrong. The MIME type is plain text, but the // clipboard doesn't contain text or a Uri. Report an error. Log.e(TAG, "Clipboard contains an invalid data type"); return false; } }
הדבקת נתונים מ-URI של תוכן
אם אובייקט ClipData.Item
מכיל URI של תוכן ואתם קובעים שאתם יכולים לטפל באחד מסוגי ה-MIME שלו, צריך ליצור ContentResolver
ולקרוא לשיטה המתאימה של ספק התוכן כדי לאחזר את הנתונים.
בתהליך הבא מוסבר איך לקבל נתונים מספק תוכן על סמך URI של תוכן שנמצא בלוח. הבדיקה נועדה לבדוק אם סוג MIME שבו האפליקציה יכולה להשתמש זמין מהספק.
-
מגדירים משתנה גלובלי שיכיל את סוג ה-MIME:
Kotlin
// Declares a MIME type constant to match against the MIME types offered // by the provider. const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
Java
// Declares a MIME type constant to match against the MIME types offered by // the provider. public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
- אחזור הלוח הגלובלי. צריך גם לקבל פתרון לבעיות שקשורות לתוכן כדי שתוכלו לגשת לספק התוכן:
Kotlin
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Gets a content resolver instance. val cr = contentResolver
Java
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Gets a content resolver instance. ContentResolver cr = getContentResolver();
- מקבלים את הקליפ הראשי מהלוח ומקבלים את התוכן שלו כ-URI:
Kotlin
// Gets the clipboard data from the clipboard. val clip: ClipData? = clipboard.primaryClip clip?.run { // Gets the first item from the clipboard data. val item: ClipData.Item = getItemAt(0) // Tries to get the item's contents as a URI. val pasteUri: Uri? = item.uri
Java
// Gets the clipboard data from the clipboard. ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { // Gets the first item from the clipboard data. ClipData.Item item = clip.getItemAt(0); // Tries to get the item's contents as a URI. Uri pasteUri = item.getUri();
- כדי לבדוק אם מזהה ה-URI הוא מזהה URI של תוכן, צריך להפעיל את הפונקציה
getType(Uri)
. אם הערך שלUri
לא מפנה לספק תוכן תקין, ה-method מחזיר null.Kotlin
// If the clipboard contains a URI reference... pasteUri?.let { // ...is this a content URI? val uriMimeType: String? = cr.getType(it)
Java
// If the clipboard contains a URI reference... if (pasteUri != null) { // ...is this a content URI? String uriMimeType = cr.getType(pasteUri);
- בודקים אם ספק התוכן תומך בסוג MIME שהאפליקציה מבינה. אם כן, צריך להפעיל את הפונקציה
ContentResolver.query()
כדי לקבל את הנתונים. הערך המוחזר הואCursor
.Kotlin
// If the return value isn't null, the Uri is a content Uri. uriMimeType?.takeIf { // Does the content provider offer a MIME type that the current // application can use? it == MIME_TYPE_CONTACT }?.apply { // Get the data from the content provider. cr.query(pasteUri, null, null, null, null)?.use { pasteCursor -> // If the Cursor contains data, move to the first record. if (pasteCursor.moveToFirst()) { // Get the data from the Cursor here. // The code varies according to the format of the data model. } // Kotlin `use` automatically closes the Cursor. } } } }
Java
// If the return value isn't null, the Uri is a content Uri. if (uriMimeType != null) { // Does the content provider offer a MIME type that the current // application can use? if (uriMimeType.equals(MIME_TYPE_CONTACT)) { // Get the data from the content provider. Cursor pasteCursor = cr.query(uri, null, null, null, null); // If the Cursor contains data, move to the first record. if (pasteCursor != null) { if (pasteCursor.moveToFirst()) { // Get the data from the Cursor here. // The code varies according to the format of the data model. } } // Close the Cursor. pasteCursor.close(); } } } }
הדבקת כוונה
כדי להדביק כוונה, קודם צריך לקבל את הלוח הגלובלי. בודקים את האובייקט ClipData.Item
כדי לראות אם הוא מכיל Intent
. לאחר מכן, צריך להפעיל את getIntent()
כדי להעתיק את הכוונה לאחסון שלכם. קטע הקוד הבא מדגים זאת:
Kotlin
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Checks whether the clip item contains an Intent by testing whether // getIntent() returns null. val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent if (pasteIntent != null) { // Handle the Intent. } else { // Ignore the clipboard, or issue an error if // you expect an Intent to be on the clipboard. }
Java
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Checks whether the clip item contains an Intent, by testing whether // getIntent() returns null. Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent(); if (pasteIntent != null) { // Handle the Intent. } else { // Ignore the clipboard, or issue an error if // you expect an Intent to be on the clipboard. }
התראה מערכתית שמופיעה כשהאפליקציה שלכם ניגשת לנתוני הלוח
ב-Android 12 (רמת API 31) ואילך, המערכת בדרך כלל מציגה הודעת טוסטים כשהאפליקציה שלכם מבצעת קריאה ל-getPrimaryClip()
.
הטקסט שבהודעה מכיל את הפורמט הבא:
APP pasted from your clipboard
המערכת לא מציגה הודעת טוסט כשהאפליקציה מבצעת אחת מהפעולות הבאות:
- גישה אל
ClipData
מהאפליקציה שלכם. - האפליקציה ניגשת שוב ושוב ל-
ClipData
מאפליקציה ספציפית. ההודעה מופיעה רק בפעם הראשונה שהאפליקציה ניגשת לנתונים מהאפליקציה הזו. - אחזור המטא-נתונים של אובייקט הקליפ, למשל באמצעות קריאה ל-
getPrimaryClipDescription()
במקום ל-getPrimaryClip()
.
שימוש בספקי תוכן כדי להעתיק נתונים מורכבים
ספקי תוכן תומכים בהעתקת נתונים מורכבים כמו רשומות במסדי נתונים או מקורות נתונים של קבצים. כדי להעתיק את הנתונים, מעתיקים ללוח URI של תוכן. אפליקציות הדבקה מקבלות את ה-URI הזה מהלוח ומשתמשות בו כדי לאחזר נתוני מסדי נתונים או מתארי תהליכי העברת נתונים של קבצים.
מכיוון שלאפליקציה להדבקה יש רק את ה-URI של התוכן של הנתונים, היא צריכה לדעת איזה קטע נתונים לאחזר. אפשר לספק את המידע הזה על ידי קידוד מזהה לנתונים ב-URI עצמו, או על ידי מתן URI ייחודי שמחזיר את הנתונים שרוצים להעתיק. השיטה שבוחרים תלויה בארגון הנתונים.
בקטעים הבאים מוסבר איך להגדיר מזהי URI, לספק נתונים מורכבים ולספק מקורות של נתוני קבצים. בתיאור האפשרויות האלה, אנחנו מניחים שאתם מכירים את העקרונות הכלליים של עיצוב של ספקי תוכן.
קידוד מזהה ב-URI
שיטה שימושית להעתקת נתונים ללוח באמצעות URI היא לקודד מזהה לנתונים ב-URI עצמו. לאחר מכן, ספק התוכן יוכל לקבל את המזהה מה-URI ולהשתמש בו כדי לאחזר את הנתונים. אפליקציית ההדבקה לא צריכה לדעת שהמזהה קיים. הוא רק צריך לקבל את 'ההפניה' – ה-URI והמזהה – מהלוח, להעביר אותה לספק התוכן ולקבל בחזרה את הנתונים.
בדרך כלל, כדי לקודד מזהה ב-URI של תוכן, צריך לצרף אותו לסוף ה-URI. לדוגמה, נניח שהגדרתם את ה-URI של הספק כמחרוזת הבאה:
"content://com.example.contacts"
כדי לקודד שם בכתובת ה-URI הזו, משתמשים בקטע הקוד הבא:
Kotlin
val uriString = "content://com.example.contacts/Smith" // uriString now contains content://com.example.contacts/Smith. // Generates a uri object from the string representation. val copyUri = Uri.parse(uriString)
Java
String uriString = "content://com.example.contacts" + "/" + "Smith"; // uriString now contains content://com.example.contacts/Smith. // Generates a uri object from the string representation. Uri copyUri = Uri.parse(uriString);
אם אתם כבר משתמשים בספק תוכן, כדאי להוסיף נתיב URI חדש שמציין שה-URI מיועד להעתקה. לדוגמה, נניח שכבר יש לכם את נתיבי ה-URI הבאים:
"content://com.example.contacts/people" "content://com.example.contacts/people/detail" "content://com.example.contacts/people/images"
אפשר להוסיף נתיב נוסף להעתקת מזהי URI:
"content://com.example.contacts/copying"
לאחר מכן תוכלו לזהות URI של 'העתקה' באמצעות התאמת דפוסים ולטפל בו באמצעות קוד ספציפי להעתקה ולהדבקה.
בדרך כלל משתמשים בשיטת הקידוד אם כבר משתמשים בספק תוכן, במסד נתונים פנימי או בטבלה פנימית כדי לארגן את הנתונים. במקרים כאלה, יש לכם כמה קטעי נתונים שאתם רוצים להעתיק, וכנראה מזהה ייחודי לכל קטע. בתגובה לשאילתה מהאפליקציה להדבקה, אפשר לחפש את הנתונים לפי המזהה שלהם ולהחזיר אותם.
אם אין לכם כמה פריטים של נתונים, סביר להניח שאין צורך לקודד מזהה. אפשר להשתמש ב-URI ייחודי לספק. בתגובה לשאילתה, הספק מחזיר את הנתונים שהוא מכיל כרגע.
העתקת מבני נתונים
מגדירים ספק תוכן להעתקה ולהדבקה של נתונים מורכבים כסוג משנה של הרכיב ContentProvider
. מקודדים את ה-URI שמוסיפים ללוח העריכה כך שיצביע על הרשומה המדויקת שרוצים לספק. בנוסף, כדאי לבדוק את המצב הנוכחי של הבקשה:
- אם כבר יש לכם ספק תוכן, אתם יכולים להוסיף לפונקציונליות שלו. יכול להיות שתצטרכו לשנות רק את השיטה
query()
כדי לטפל ב-URIs שמגיעים מאפליקציות שרוצות להדביק נתונים. סביר להניח שתרצו לשנות את השיטה כדי לטפל בתבנית URI של 'copy'. - אם באפליקציה שלכם יש מסד נתונים פנימי, כדאי להעביר את מסד הנתונים הזה לספק תוכן כדי להקל על ההעתקה ממנו.
- אם אתם לא משתמשים במסד נתונים, תוכלו להטמיע ספק תוכן פשוט שמטרתו היחידה היא להציע נתונים לאפליקציות שמדביקות מהלוח.
בספק התוכן, משנים את השיטה לפחות לפי הדרכים הבאות:
-
query()
- אפליקציות להדבקה מניחות שהן יכולות לקבל את הנתונים שלכם באמצעות השיטה הזו עם ה-URI שהעברתם ללוח. כדי לתמוך בהעתקה, צריך לגרום לשיטה הזו לזהות מזהי URI שמכילים נתיב 'copy' מיוחד. לאחר מכן, האפליקציה יכולה ליצור URI של 'העתקה' כדי להעביר ללוח, שכולל את נתיב ההעתקה ואת הפניה לרשומה המדויקת שרוצים להעתיק.
-
getType()
- השיטה הזו חייבת להחזיר את סוגי ה-MIME של הנתונים שאתם מתכוונים להעתיק. השיטה
newUri()
מפעילה אתgetType()
כדי להוסיף את סוגי ה-MIME לאובייקט החדשClipData
.סוגי ה-MIME לנתונים מורכבים מתוארים בקטע ספקי תוכן.
אין צורך בשיטות אחרות של ספקי תוכן, כמו insert()
או update()
.
אפליקציית הדבקה צריכה רק לקבל את סוגי ה-MIME הנתמכים ולהעתיק נתונים מהספק.
אם כבר יש לכם את השיטות האלה, הן לא יפריעו לפעולות ההעתקה.
בקטעי הקוד הבאים מוסבר איך להגדיר את האפליקציה להעתקת נתונים מורכבים:
-
בקבצים הקבועים הגלובליים של האפליקציה, מגדירים מחרוזת URI בסיסית ונתיב שמזהה את מחרוזות ה-URI שבהן אתם משתמשים כדי להעתיק נתונים. צריך גם להצהיר על סוג MIME של הנתונים המועתקים.
Kotlin
// Declares the base URI string. private const val CONTACTS = "content://com.example.contacts" // Declares a path string for URIs that you use to copy data. private const val COPY_PATH = "/copy" // Declares a MIME type for the copied data. const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
Java
// Declares the base URI string. private static final String CONTACTS = "content://com.example.contacts"; // Declares a path string for URIs that you use to copy data. private static final String COPY_PATH = "/copy"; // Declares a MIME type for the copied data. public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
- בפעילות שממנה המשתמשים מעתיקים נתונים, מגדירים את הקוד להעתקת הנתונים ללוח.
בתגובה לבקשת העתקה, מעתיקים את ה-URI ללוח.
Kotlin
class MyCopyActivity : Activity() { ... when(item.itemId) { R.id.menu_copy -> { // The user has selected a name and is requesting a copy. // Appends the last name to the base URI. // The name is stored in "lastName". uriString = "$CONTACTS$COPY_PATH/$lastName" // Parses the string into a URI. val copyUri: Uri? = Uri.parse(uriString) // Gets a handle to the clipboard service. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri) // Sets the clipboard's primary clip. clipboard.setPrimaryClip(clip) } }
Java
public class MyCopyActivity extends Activity { ... // The user has selected a name and is requesting a copy. case R.id.menu_copy: // Appends the last name to the base URI. // The name is stored in "lastName". uriString = CONTACTS + COPY_PATH + "/" + lastName; // Parses the string into a URI. Uri copyUri = Uri.parse(uriString); // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri); // Sets the clipboard's primary clip. clipboard.setPrimaryClip(clip);
-
ברמת ה-global של ספק התוכן, יוצרים מתאמת URI ומוסיפים דפוס URI שתואם למזהי ה-URI שמוסיפים ללוח.
Kotlin
// A Uri Match object that simplifies matching content URIs to patterns. private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { // Adds a matcher for the content URI. It matches. // "content://com.example.contacts/copy/*" addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT) } // An integer to use in switching based on the incoming URI pattern. private const val GET_SINGLE_CONTACT = 0 ... class MyCopyProvider : ContentProvider() { ... }
Java
public class MyCopyProvider extends ContentProvider { ... // A Uri Match object that simplifies matching content URIs to patterns. private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); // An integer to use in switching based on the incoming URI pattern. private static final int GET_SINGLE_CONTACT = 0; ... // Adds a matcher for the content URI. It matches // "content://com.example.contacts/copy/*" sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
-
מגדירים את השיטה
query()
. השיטה הזו יכולה לטפל בדפוסים שונים של URI, בהתאם לאופן שבו כותבים את הקוד, אבל רק הדפוס של פעולת ההעתקה ללוח יוצג.Kotlin
// Sets up your provider's query() method. override fun query( uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { ... // When based on the incoming content URI: when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> { // Queries and returns the contact for the requested name. Decodes // the incoming URI, queries the data model based on the last name, // and returns the result as a Cursor. } } ... }
Java
// Sets up your provider's query() method. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... // Switch based on the incoming content URI. switch (sUriMatcher.match(uri)) { case GET_SINGLE_CONTACT: // Queries and returns the contact for the requested name. Decodes the // incoming URI, queries the data model based on the last name, and // returns the result as a Cursor. ... }
-
מגדירים את השיטה
getType()
כך שתחזיר סוג MIME מתאים לנתונים המועתקים:Kotlin
// Sets up your provider's getType() method. override fun getType(uri: Uri): String? { ... return when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT ... } }
Java
// Sets up your provider's getType() method. public String getType(Uri uri) { ... switch (sUriMatcher.match(uri)) { case GET_SINGLE_CONTACT: return (MIME_TYPE_CONTACT); ... } }
בקטע הדבקת נתונים מ-URI של תוכן מוסבר איך לקבל URI של תוכן מהלוח ולהשתמש בו כדי לקבל נתונים ולהדביק אותם.
העתקת מקורות נתונים
אפשר להעתיק ולהדביק כמויות גדולות של טקסט ונתונים בינאריים כזרמים. הנתונים יכולים להיות בפורמטים הבאים:
- קבצים שמאוחסנים במכשיר עצמו
- מקורות נתונים משקעים
- כמויות גדולות של נתונים שמאוחסנים במערכת מסד הנתונים הבסיסית של הספק
ספק תוכן של מקורות נתונים מספק גישה לנתונים שלו באמצעות אובייקט של מתאר קובץ, כמו AssetFileDescriptor
, במקום אובייקט Cursor
. אפליקציית ההדבקה קוראת את מקור הנתונים באמצעות מתאר הקובץ הזה.
כדי להגדיר את האפליקציה להעתקת מקור נתונים עם ספק, פועלים לפי השלבים הבאים:
-
מגדירים URI של תוכן לזרם הנתונים שמעבירים ללוח. האפשרויות לביצוע הפעולה הזו כוללות:
- מקודדים מזהה של מקור הנתונים ב-URI, כפי שמתואר בקטע קידוד מזהה ב-URI, ולאחר מכן שומרים בטבלה אצל הספק מזהי מקור נתונים ושם מקור הנתונים התואם.
- מקודדים את שם הסטרימינג ישירות ב-URI.
- משתמשים ב-URI ייחודי שתמיד מחזיר את הסטרימינג הנוכחי מהספק. אם משתמשים באפשרות הזו, חשוב לזכור לעדכן את הספק כך שיצביע על מקור נתונים אחר בכל פעם שמעתיקים את מקור הנתונים ללוח באמצעות ה-URI.
- יש לציין סוג MIME לכל סוג של מקור נתונים שאתם מתכננים להציע. אפליקציות להדבקה זקוקות למידע הזה כדי לקבוע אם הן יכולות להדביק את הנתונים בלוח.
- מטמיעים אחת מהשיטות
ContentProvider
שמחזירה מתאר קובץ של מקור נתונים. אם אתם מקודדים מזהים ב-URI של התוכן, תוכלו להשתמש בשיטה הזו כדי לקבוע איזה מקור נתונים (stream) לפתוח. - כדי להעתיק את מקור הנתונים ללוח העריכה, יוצרים את ה-URI של התוכן ומעבירים אותו ללוח העריכה.
כדי להדביק מקור נתונים, האפליקציה מקבלת את הקליפ מהלוח, מקבלת את ה-URI ומשתמשת בו בקריאה לשיטת מתאר הקובץ ContentResolver
שפותחת את המקור. השיטה ContentResolver
קוראת לשיטה ContentProvider
המתאימה ומעבירה לה את מזהה ה-URI של התוכן. הספק מחזיר את מתאר הקובץ לשיטה ContentResolver
. לאחר מכן, אפליקציית ההדבקה אחראית לקרוא את הנתונים מהמקור.
ברשימה הבאה מפורטות השיטות החשובות ביותר של מתארי קבצים עבור ספק תוכן. לכל אחד מהם יש שיטה תואמת של ContentResolver
עם המחרוזת 'Descriptor' שמצורפת לשם השיטה. לדוגמה, הערך המקביל של ContentResolver
ל-openAssetFile()
הוא openAssetFileDescriptor()
.
-
openTypedAssetFile()
-
השיטה הזו מחזירה מתאר קובץ של נכס, אבל רק אם סוג ה-MIME שצוין נתמך על ידי הספק. מבצע הקריאה החוזרת – האפליקציה שמבצעת את ההדבקה – מספק דפוס של סוג MIME. ספק התוכן של האפליקציה שמעתיק את ה-URI ללוח העריכה מחזיר את ה-handle של הקובץ
AssetFileDescriptor
אם הוא יכול לספק את סוג ה-MIME הזה, ומעביר חריגה אם הוא לא יכול.השיטה הזו מטפלת בקטעים משנה של קבצים. אפשר להשתמש בו כדי לקרוא נכסים שספק התוכן העתיק ללוח.
-
openAssetFile()
-
השיטה הזו היא צורה כללית יותר של
openTypedAssetFile()
. הוא לא מסנן לפי סוגי MIME מותרים, אבל הוא יכול לקרוא קטעים של קבצים. -
openFile()
-
זוהי צורה כללית יותר של
openAssetFile()
. לא ניתן לקרוא קטעים משנה של קבצים.
אפשר להשתמש ב-method openPipeHelper()
בשיטת מתאר הקובץ. כך אפליקציית ההדבקה יכולה לקרוא את נתוני הסטרימינג בשרשור ברקע באמצעות צינור. כדי להשתמש בשיטה הזו, צריך להטמיע את הממשק ContentProvider.PipeDataWriter
.
עיצוב פונקציונליות יעילה של העתקה והדבקה
כדי לתכנן פונקציונליות יעילה של העתקה והדבקה באפליקציה, חשוב לזכור את הנקודות הבאות:
- בכל רגע נתון, יש רק קליפס אחד בלוח העריכה. פעולת העתקה חדשה של כל אפליקציה במערכת מחליפה את הקליפ הקודם. מכיוון שהמשתמש עשוי לנווט מחוץ לאפליקציה שלכם ולבצע העתקה לפני שהוא חוזר, אי אפשר להניח שהלוח מכיל את הקליפ שהמשתמש העתיק בעבר באפליקציה שלכם.
-
המטרה של מספר אובייקטים מסוג
ClipData.Item
בכל קליפ היא לתמוך בהעתקה ובהדבקה של מספר בחירות, ולא בדרכים שונות של הפניה לבחירה אחת. בדרך כלל, כדאי שכל העצמים מסוגClipData.Item
בקליפ יהיו באותה צורה. כלומר, כולם חייבים להיות טקסט פשוט, URI של תוכן אוIntent
, ולא שילוב שלהם. -
כשאתם מספקים נתונים, אתם יכולים להציע ייצוגים שונים של MIME. מוסיפים את סוגי ה-MIME שאתם תומכים בהם ל-
ClipDescription
, ולאחר מכן מטמיעים את סוגי ה-MIME אצל ספק התוכן. -
כשמקבלים נתונים מהלוח, האפליקציה אחראית לבדוק את סוגי ה-MIME הזמינים ולאחר מכן להחליט באיזה מהם להשתמש, אם בכלל. גם אם יש קטע מודבק בלוח העריכה והמשתמש מבקש להדביק אותו, האפליקציה לא חייבת לבצע את ההדבקה. מדביקים את הקובץ אם סוג ה-MIME תואם. אפשר להמיר את הנתונים בלוח לטקסט באמצעות
coerceToText()
. אם האפליקציה תומכת ביותר מסוג MIME אחד, תוכלו לאפשר למשתמש לבחור את הסוג שבו הוא רוצה להשתמש.