ב-Android יש מסגרת חזקה שמבוססת על לוח העתקה להעתקה ולהדבקה. הוא תומך בסוגי נתונים פשוטים ומורכבים, כולל מחרוזות טקסט, מבני נתונים מורכבים, נתוני טקסט ונתונים בינאריים של זרם ונכסי אפליקציה. נתוני טקסט פשוטים מאוחסנים ישירות בלוח, ואילו נתונים מורכבים מאוחסנים כהפניה שהאפליקציה המדביקה פותרת באמצעות ספק תוכן. העתקה והדבקה פועלות גם בתוך אפליקציה וגם בין אפליקציות שמטמיעות את המסגרת.
חלק מהמסגרת משתמש בספקי תוכן, ולכן במאמר הזה מניחים שיש לכם היכרות עם Android Content Provider API, שמתואר במאמר ספקי תוכן.
המשתמשים מצפים לקבל משוב כשהם מעתיקים תוכן ללוח, ולכן בנוסף למסגרת שמאפשרת העתקה והדבקה, מערכת Android מציגה למשתמשים ממשק משתמש כברירת מחדל כשהם מעתיקים ב-Android 13 (API ברמה 33) ומעלה. בגלל התכונה הזו, יש סיכון לכפילות בהתראות. מידע נוסף על המקרה הזה זמין בקטע הימנעות משכפול של התראות.

צריך לספק משוב למשתמשים באופן ידני כשמעתיקים ב-Android 12L (רמת API 32) ובגרסאות קודמות. אפשר לראות המלצות לגבי זה במסמך הזה.
המסגרת של הלוח
כשמשתמשים במסגרת של לוח העתקה, מכניסים נתונים לאובייקט של קליפ, ואז מכניסים את אובייקט הקליפ ללוח ההעתקה של המערכת. אובייקט הקליפ יכול להופיע באחת משלוש צורות:
- טקסט
- מחרוזת טקסט. מכניסים את המחרוזת ישירות לאובייקט הקליפ, ואז מכניסים אותו ללוח הגזירה. כדי להדביק את המחרוזת, מאחזרים את אובייקט הקליפ מלוח העריכה ומעתיקים את המחרוזת לאחסון של האפליקציה.
- URI
-
אובייקט
Uri
שמייצג כל סוג של URI. האפשרות הזו מיועדת בעיקר להעתקת נתונים מורכבים מספק תוכן. כדי להעתיק נתונים, מכניסים אובייקטUri
לאובייקט של לוח ומכניסים את האובייקט של לוח ללוח. כדי להדביק את הנתונים, צריך לקבל את אובייקט הגזירה, לקבל את אובייקטUri
, לפתור אותו למקור נתונים, כמו ספק תוכן, ולהעתיק את הנתונים מהמקור לאחסון של האפליקציה. - כוונה
-
An
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 או נתוני הכוונה:
- טקסט
-
A
CharSequence
. - URI
-
A
Uri
. הוא בדרך כלל מכיל URI של ספק תוכן, אבל מותר להשתמש בכל URI. האפליקציה שמספקת את הנתונים מעתיקה את ה-URI ללוח. אפליקציות שרוצות להדביק את הנתונים מקבלות את ה-URI מלוח הבקרה ומשתמשות בו כדי לגשת לספק התוכן או למקור נתונים אחר ולאחזר את הנתונים. - כוונה
-
An
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)
- Returns a
ClipData
object whose singleClipData.Item
object contains a 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)
- Returns a
ClipData
object whose singleClipData.Item
object contains anIntent
. התווית של האובייקט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()
לא ריק –coerceToText()
מנסה להשתמש בו כ-URI של תוכן.- אם כתובת ה-URI היא כתובת URI של תוכן והספק יכול להחזיר זרם טקסט,
coerceToText()
מחזירה זרם טקסט. - אם כתובת ה-URI היא כתובת URI של תוכן, אבל הספק לא מציע זרם טקסט,
הפונקציה
coerceToText()
מחזירה ייצוג של כתובת ה-URI. הייצוג זהה לזה שמוחזר על ידיUri.toString()
. - אם ה-URI הוא לא URI של תוכן, הפונקציה
coerceToText()
מחזירה ייצוג של ה-URI. הייצוג זהה לזה שמוחזר על ידיUri.toString()
.
- אם כתובת ה-URI היא כתובת URI של תוכן והספק יכול להחזיר זרם טקסט,
- כוונה
- If
ClipData.Item
is anIntent
—that is, ifgetIntent()
isn't null—coerceToText()
converts it to an Intent URI and returns it. הייצוג זהה לזה שמוחזר על ידיIntent.toUri(URI_INTENT_SCHEME)
.
סיכום של מסגרת לוח העריכה מוצג באיור 2. כדי להעתיק נתונים, אפליקציה מציבה אובייקט ClipData
בלוח העריכה הגלובלי ClipboardManager
. האובייקט ClipData
מכיל אובייקט ClipData.Item
אחד או יותר ואובייקט ClipDescription
אחד. כדי להדביק נתונים, האפליקציה מקבלת את ClipData
, מקבלת את סוג ה-MIME שלו מ-ClipDescription
ומקבלת את הנתונים מ-ClipData.Item
או מספק התוכן שאליו מתייחס ClipData.Item
.

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


כדי לסמן תוכן רגיש, מוסיפים ערך בוליאני ל-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)
. השיטה הזו מחזירה ערך null אםUri
לא מצביע על ספק תוכן תקין.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()
כדי לטפל בכתובות URI שמגיעות מאפליקציות שרוצות להדביק נתונים. כדאי לשנות את השיטה כדי לטפל בתבנית URI של 'העתקה'. - אם האפליקציה שלכם מתחזקת מסד נתונים פנימי, יכול להיות שתרצו להעביר את מסד הנתונים הזה לספק תוכן כדי להקל על ההעתקה ממנו.
- אם אתם לא משתמשים במסד נתונים, אתם יכולים להטמיע ספק תוכן פשוט שהמטרה היחידה שלו היא להציע נתונים לאפליקציות שמדביקות מהלוח.
בספק התוכן, מחליפים לפחות את השיטות הבאות:
-
query()
- אפליקציות להדבקה מניחות שהן יכולות לקבל את הנתונים שלכם באמצעות השיטה הזו עם ה-URI שהצבתם בלוח. כדי לתמוך בהעתקה, השיטה הזו צריכה לזהות כתובות URI שמכילות נתיב מיוחד של 'העתקה'. לאחר מכן האפליקציה יכולה ליצור 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);
-
בטווח הגלובלי של ספק התוכן, יוצרים התאמה של 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 של התוכן, אתם יכולים להשתמש בשיטה הזו כדי לקבוע איזה סטרימינג לפתוח. - כדי להעתיק את מקור הנתונים ללוח, יוצרים את ה-URI של התוכן ומציבים אותו בלוח.
כדי להדביק זרם נתונים, אפליקציה מקבלת את הקליפ מלוח העריכה, מקבלת את ה-URI ומשתמשת בו בקריאה לשיטה של מתאר קובץ שפותחת את הזרם.ContentResolver
השיטה ContentResolver
קוראת לשיטה ContentProvider
המתאימה ומעבירה לה את ה-URI של התוכן. הספק מחזיר את מתאר הקובץ לשיטה ContentResolver
. אחרי כן, האפליקציה שאליה מדביקים את הנתונים אחראית לקרוא את הנתונים מהזרם.
הרשימה הבאה מציגה את השיטות החשובות ביותר לתיאור קובץ עבור ספק תוכן. לכל אחד מהם יש שיטת ContentResolver
תואמת עם המחרוזת Descriptor שנוספת לשם השיטה. לדוגמה, האנלוגיה של ContentResolver
ל-openAssetFile()
היא openAssetFileDescriptor()
.
-
openTypedAssetFile()
-
השיטה הזו מחזירה מתאר של קובץ נכס, אבל רק אם ספק התוכן תומך בסוג ה-MIME שצוין. האפליקציה שמבצעת את ההדבקה מספקת תבנית של סוג MIME. ספק התוכן של האפליקציה שמבצעת העתקה של URI ללוח, מחזיר ידית קובץ
AssetFileDescriptor
אם הוא יכול לספק את סוג ה-MIME הזה, וזורק חריגה אם הוא לא יכול.השיטה הזו מטפלת בחלקים של קבצים. אפשר להשתמש בו כדי לקרוא נכסים שספק התוכן העתיק ללוח.
-
openAssetFile()
-
השיטה הזו היא צורה כללית יותר של
openTypedAssetFile()
. הוא לא מסנן לפי סוגי MIME מותרים, אבל הוא יכול לקרוא חלקים של קבצים. -
openFile()
-
זוהי צורה כללית יותר של
openAssetFile()
. הוא לא יכול לקרוא קטעי משנה של קבצים.
אפשר להשתמש בשיטה openPipeHelper()
עם שיטת מתאר הקובץ. כך אפליקציית ההדבקה יכולה לקרוא את נתוני הזרם בשרשור ברקע באמצעות צינור. כדי להשתמש בשיטה הזו, מטמיעים את הממשק ContentProvider.PipeDataWriter
.
עיצוב פונקציונליות יעילה של העתקה והדבקה
כדי לעצב פונקציונליות יעילה של העתקה והדבקה באפליקציה, חשוב לזכור את הנקודות הבאות:
- בכל רגע נתון, יש רק קטע אחד בלוח. פעולת העתקה חדשה על ידי כל אפליקציה במערכת מחליפה את הקטע הקודם. יכול להיות שהמשתמש יעבור מהאפליקציה שלכם ויעתיק משהו אחר לפני שיחזור אליה, ולכן אי אפשר להניח שהלוח מכיל את הקטע שהמשתמש העתיק קודם באפליקציה שלכם.
-
המטרה של שימוש בכמה אובייקטים מסוג
ClipData.Item
לכל קטע היא לתמוך בהעתקה ובהדבקה של כמה בחירות, ולא של הפניות שונות לבחירה אחת. בדרך כלל, רוצים שכל האובייקטיםClipData.Item
בקליפ יהיו באותו פורמט. כלומר, כולם חייבים להיות טקסט פשוט, URI של תוכן אוIntent
, ולא שילוב של כמה סוגים. -
כשמספקים נתונים, אפשר להציע ייצוגים שונים של MIME. מוסיפים את סוגי ה-MIME שאתם תומכים בהם אל
ClipDescription
, ואז מטמיעים את סוגי ה-MIME בספק התוכן. -
כשמקבלים נתונים מלוח העריכה, האפליקציה אחראית לבדוק את סוגי ה-MIME הזמינים ואז להחליט באיזה מהם להשתמש, אם בכלל. גם אם יש קטע בלוח העריכה והמשתמש מבקש להדביק אותו, האפליקציה לא חייבת לבצע את ההדבקה. אם סוג ה-MIME תואם, מדביקים את התוכן. אפשר להמיר את הנתונים בלוח לטקסט באמצעות
coerceToText()
. אם האפליקציה תומכת ביותר מסוג MIME אחד מתוך הסוגים הזמינים, אפשר לאפשר למשתמש לבחור באיזה סוג להשתמש.