ב-Android יש מסגרת יעילה להעתקה והדבקה שמבוססת על הלוח. הוא תומך בסוגי נתונים פשוטים ומורכבים, כולל מחרוזות טקסט, מבני נתונים מורכבים, נתוני טקסט ונתונים בינאריים של סטרימינג ונכסי אפליקציות. נתוני טקסט פשוטים מאוחסנים ישירות בלוח, ונתונים מורכבים מאוחסנים כהפניה שהאפליקציה שבה מדביקים פותרת באמצעות ספק לניהול גישה. אפשר להעתיק ולהדביק בתוך אפליקציה ובין אפליקציות שמטמיעות את המסגרת.
חלק מהמסגרת משתמש בספקים לניהול גישה, לכן המסמך הזה מניח שיש לכם היכרות מסוימת עם Android Content Provider API, שמתואר במאמר ספקים לניהול גישה.
המשתמשים מצפים לקבל משוב כשהם מעתיקים תוכן ללוח, אז בנוסף למסגרת שמאפשרת העתקה והדבקה, ב-Android מוצג ממשק משתמש כברירת מחדל כשמעתיקים ב-Android בגרסה 13 (רמת API 33) ואילך. בגלל התכונה הזו, יש סיכון שתקבלו התראות כפולות. מידע נוסף על מקרה הקצה הזה זמין בקטע מניעת התראות כפולות.
לספק משוב למשתמשים באופן ידני כשמעתיקים ב-Android 12L (רמת API 32) ובגרסאות קודמות. ההמלצות בנושא מפורטות במאמר הזה.
מסגרת לוח העריכה
כשמשתמשים ב-framework של לוח העריכה, מכניסים נתונים לאובייקט קליפ ואז מכניסים את אובייקט הקליפ ללוח העריכה ברמת המערכת. אובייקט הקליפ יכול להופיע באחת משלוש צורות:
- טקסט
- מחרוזת טקסט. מכניסים את המחרוזת ישירות לאובייקט הקליפ, ואז מכניסים אותו ללוח. כדי להדביק את המחרוזת, צריך לקבל את אובייקט הקליפ מלוח העריכה ולהעתיק את המחרוזת לאחסון של האפליקציה.
- URI
-
אובייקט
Uriשמייצג כל סוג של URI. האפשרות הזו מיועדת בעיקר להעתקת נתונים מורכבים מספק לניהול גישה. כדי להעתיק נתונים, צריך להכניס אובייקטUriלאובייקט קליפ ולהכניס את אובייקט הקליפ ללוח. כדי להדביק את הנתונים, צריך לקבל את אובייקט ה-clip, לקבל את אובייקט ה-Uri, לפתור אותו למקור נתונים, כמו ספק לניהול גישה, ולהעתיק את הנתונים מהמקור לאחסון של האפליקציה. - כוונת רכישה
-
Intent. האפשרות הזו תומכת בהעתקת קיצורי דרך לאפליקציות. כדי להעתיק נתונים, יוצריםIntent, מכניסים אותו לאובייקט קליפ ומכניסים את אובייקט הקליפ ללוח. כדי להדביק את הנתונים, צריך לקבל את האובייקט של הלוח ואז להעתיק את האובייקטIntentלאזור הזיכרון של האפליקציה.
הלוח יכול להכיל רק אובייקט אחד בכל פעם. כשאפליקציה שמה אובייקט בלוח, האובייקט הקודם נעלם.
אם אתם רוצים לאפשר למשתמשים להדביק נתונים באפליקציה, אתם לא צריכים לטפל בכל סוגי הנתונים. אתם יכולים לבדוק את הנתונים בלוח לפני שאתם נותנים למשתמשים אפשרות להדביק אותם. בנוסף לפורמט נתונים מסוים, אובייקט ה-clip מכיל גם מטא-נתונים שמציינים אילו סוגי MIME זמינים. המטא-נתונים האלה עוזרים לכם להחליט אם האפליקציה יכולה לעשות משהו שימושי עם הנתונים בלוח. לדוגמה, אם יש לכם אפליקציה שמטפלת בעיקר בטקסט, יכול להיות שתרצו להתעלם מאובייקטים של קליפים שמכילים URI או Intent.
יכול להיות שתרצו לאפשר למשתמשים להדביק טקסט בלי קשר לסוג הנתונים בלוח. כדי לעשות את זה, צריך לאלץ את הנתונים בלוח להפוך לייצוג טקסט, ואז להדביק את הטקסט הזה. הפעולה הזו מתוארת בקטע המרת הלוח לטקסט.
מחלקות לוח העריכה
בקטע הזה מתוארות המחלקות שמשמשות את מסגרת הלוח.
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 או נתוני ה-Intent:
- טקסט
-
CharSequence. - URI
-
Uri. בדרך כלל הוא מכיל URI של ספק לניהול גישה, אבל אפשר להשתמש בכל URI. האפליקציה שמספקת את הנתונים שמה את ה-URI בלוח. אפליקציות שרוצות להדביק את הנתונים מקבלות את ה-URI מהלוח ומשתמשות בו כדי לגשת לספק לניהול גישה או למקור נתונים אחר ולאחזר את הנתונים. - כוונת רכישה
-
Intent. סוג הנתונים הזה מאפשר להעתיק קיצור דרך לאפליקציה ללוח. המשתמשים יכולים להדביק את קיצור הדרך באפליקציות שלהם לשימוש מאוחר יותר.
אפשר להוסיף יותר מאובייקט ClipData.Item אחד לקליפ. כך המשתמשים יכולים להעתיק ולהדביק כמה פריטים שנבחרו כקליפ אחד. לדוגמה, אם יש לכם ווידג'ט של רשימה שמאפשר למשתמש לבחור כמה פריטים בו-זמנית, אתם יכולים להעתיק את כל הפריטים ללוח בבת אחת. כדי לעשות את זה, יוצרים ClipData.Item נפרד לכל פריט ברשימה, ואז מוסיפים את האובייקטים של ClipData.Item לאובייקט ClipData.
שיטות נוחות של ClipData
ה-class 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 הוא לא מסוג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()ממיר אותו ל-Intent URI ומחזיר אותו. הייצוג זהה לזה שמוחזר על ידי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לאפליקציה ואז מכניס אותו לאובייקט הקליפ: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(); } } } }
הדבקת Intent
כדי להדביק Intent, קודם צריך לקבל את הלוח הגלובלי. בודקים את האובייקט ClipData.Item כדי לראות אם הוא מכיל Intent. לאחר מכן, קוראים ל-getIntent() כדי להעתיק את ה-intent לאחסון שלכם. בקטע הקוד הבא אפשר לראות איך עושים את זה:
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
המערכת לא מציגה הודעת Toast כשהאפליקציה מבצעת אחת מהפעולות הבאות:
- גישה אל
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 ששמרתם בלוח כדי שיפנה לרשומה המדויקת שאתם רוצים לספק. בנוסף, כדאי לבדוק את המצב הנוכחי של האפליקציה:
- אם כבר יש לכם ספק לניהול גישה, אתם יכולים להוסיף לפונקציונליות שלו. יכול להיות שתצטרכו לשנות רק את ה-method
query()כדי לטפל בכתובות URI שמגיעות מאפליקציות שרוצות להדביק נתונים. כנראה שתרצו לשנות את ה-method כדי לטפל בתבנית URI של "העתקה". - אם האפליקציה שלכם מנהלת מסד נתונים פנימי, יכול להיות שתרצו להעביר את מסד הנתונים הזה לספק לניהול גישה כדי שיהיה קל יותר להעתיק ממנו.
- אם אתם לא משתמשים במסד נתונים, אתם יכולים להטמיע ספק לניהול גישה פשוט, שהמטרה היחידה שלו היא להציע נתונים לאפליקציות שמדביקות מלוח העריכה.
בספק לניהול גישה, צריך לשנות לפחות את השיטות הבאות:
-
query() - אפליקציות שמדביקים אליהן מניחות שהן יכולות לקבל את הנתונים שלכם באמצעות השיטה הזו, עם ה-URI שהוספתם ללוח. כדי לתמוך בהעתקה, צריך להגדיר שהשיטה הזו תזהה מזהי URI שמכילים נתיב "copy" מיוחד. לאחר מכן, האפליקציה יכולה ליצור URI של "העתקה" כדי להציב בלוח, שמכיל את נתיב ההעתקה ומצביע לרשומה המדויקת שרוצים להעתיק.
-
getType() - השיטה הזו צריכה להחזיר את סוגי ה-MIME של הנתונים שאתם מתכוונים להעתיק. ה-method
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. ... }
-
מגדירים את ה-method
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 לכל סוג של מקור נתונים שאתם מתכננים להציע. אפליקציות שמאפשרות הדבקה צריכות את המידע הזה כדי לקבוע אם הן יכולות להדביק את הנתונים בלוח.
- מטמיעים את אחת מה-methods של
ContentProviderשמחזירה תיאור קובץ לסטרימינג. אם מקודדים מזהים ב-URI של התוכן, משתמשים בשיטה הזו כדי לקבוע איזה סטרימינג לפתוח. - כדי להעתיק את מקור הנתונים ללוח, צריך ליצור את ה-URI של התוכן ולהציב אותו בלוח.
כדי להדביק מקור נתונים, האפליקציה מקבלת את הקליפ מלוח העריכה, מקבלת את ה-URI ומשתמשת בו בקריאה ל-ContentResolver, שיטה לתיאור קובץ שפותחת את מקור הנתונים. ה-method ContentResolver מפעילה את ה-method ContentProvider המתאימה ומעבירה לה את ה-URI של התוכן. הספק מחזיר את תיאור הקובץ ל-method ContentResolver. האפליקציה שמדביקים בה אחראית לקרוא את הנתונים מהסטרימינג.
ברשימה הבאה מוצגות השיטות החשובות ביותר של תיאור קובץ לספק לניהול גישה. לכל אחד מהם יש שיטת ContentResolver תואמת, עם המחרוזת "Descriptor" שנוספת לשם השיטה. לדוגמה, המקבילה של openAssetFile() ב-ContentResolver היא openAssetFileDescriptor().
-
openTypedAssetFile() -
השיטה הזו מחזירה תיאור קובץ של נכס, אבל רק אם סוג ה-MIME שצוין נתמך על ידי הספק. האפליקציה שמבצעת את ההדבקה מספקת תבנית של סוג MIME. הספק לניהול גישה של האפליקציה שמעתיקה URI ללוח מחזיר handle של קובץ
AssetFileDescriptorאם הוא יכול לספק את סוג ה-MIME הזה, וזורק חריגה אם הוא לא יכול.השיטה הזו מטפלת בסעיפי משנה של קבצים. אפשר להשתמש בה כדי לקרוא נכסים שהספק לניהול גישה העתיק ללוח.
-
openAssetFile() -
ה-method הזו היא צורה כללית יותר של
openTypedAssetFile(). הוא לא מסנן לפי סוגי MIME מותרים, אבל הוא יכול לקרוא סעיפי משנה של קבצים. -
openFile() -
זוהי צורה כללית יותר של
openAssetFile(). הוא לא יכול לקרוא סעיפי משנה של קבצים.
אפשר להשתמש ב-method openPipeHelper() עם ה-method של תיאור הקובץ. כך האפליקציה שבה מדביקים יכולה לקרוא את נתוני הסטרימינג בשרשור ברקע באמצעות צינור עיבוד נתונים. כדי להשתמש בשיטה הזו, מטמיעים את הממשק ContentProvider.PipeDataWriter.
תכנון פונקציונליות יעילה של העתקה והדבקה
כדי לתכנן פונקציונליות יעילה של העתקה והדבקה באפליקציה, חשוב לזכור את הנקודות הבאות:
- בכל רגע נתון, יש רק קטע אחד בלוח. פעולת העתקה חדשה של אפליקציה כלשהי במערכת מחליפה את הקליפ הקודם. יכול להיות שהמשתמש יצא מהאפליקציה שלכם, העתיק משהו ואז חזר אליה, אז אל תניחו שהלוח מכיל את הקטע שהמשתמש העתיק קודם באפליקציה שלכם.
-
המטרה של כמה אובייקטים מסוג
ClipData.Itemבכל קליפ היא לתמוך בהעתקה ובהדבקה של כמה קטעים שנבחרו, ולא בצורות שונות של הפניה לקטע אחד שנבחר. בדרך כלל, כדאי שכל האובייקטים מסוגClipData.Itemבקליפ יהיו באותו פורמט. כלומר, כולם חייבים להיות טקסט פשוט, URI של תוכן אוIntent, ולא שילוב שלהם. -
כשאתם מספקים נתונים, אתם יכולים להציע ייצוגים שונים של MIME. מוסיפים את סוגי ה-MIME שנתמכים על ידיכם ל-
ClipDescription, ואז מטמיעים את סוגי ה-MIME בספק לניהול הגישה. -
כשמקבלים נתונים מלוח העריכה, האפליקציה אחראית לבדוק את סוגי ה-MIME הזמינים ואז להחליט באיזה מהם להשתמש, אם בכלל. גם אם יש קטע בלוח והמשתמש מבקש להדביק, האפליקציה לא נדרשת לבצע את ההדבקה. ההדבקה תתבצע אם סוג ה-MIME תואם. אפשר לאלץ את הנתונים
בלוח להפוך לטקסט באמצעות
coerceToText(). אם האפליקציה תומכת ביותר מסוג MIME אחד, אתם יכולים לאפשר למשתמש לבחור באיזה סוג להשתמש.