Android 提供了一個強大的基于剪貼板的框架,用于復制和粘貼。它支持簡單和復雜的數據類型,包括文本字符串、復雜數據結構、文本和二進制流數據,以及應用資源。簡單的文本數據直接存儲在剪貼板中,而復雜的數據則存儲為引用,執行粘貼操作的應用使用 content provider 對其進行解析。復制和粘貼既可以在應用內進行,也可以在實現了該框架的應用之間進行。
由于該框架的一部分使用 content provider,因此本文檔假設對 Android Content Provider API 已有一定了解。我們在content provider?中對該 API 進行了介紹。
用戶希望在將內容復制到剪貼板時獲得反饋,因此除了支持復制和粘貼的框架之外,在 Android 13(API 級別 33)及更高版本中進行復制時,Android 還會向用戶顯示一個默認界面。由于此功能,存在重復通知的風險。如需詳細了解此極端情況,請參閱避免重復通知部分。
對于 Android 12L(API 級別 32)及更低版本中的復制操作,請手動向用戶提供反饋。請參閱本文檔中的相關建議。
剪貼板框架
使用剪貼板框架時,會將數據放入一個剪貼對象中,然后將該剪貼對象放到系統級剪貼板中。剪輯對象可以采用以下三種形式之一:
文本
一個文本字符串。直接將字符串放入剪貼對象中,然后將剪貼對象放到剪貼板中。如需粘貼字符串,請從剪貼板獲取剪貼對象,然后將字符串復制到應用的存儲空間。
URI
一個?Uri
?對象,表示任何形式的 URI。它主要適用于從 content provider 復制復雜的數據。如需復制數據,請將?Uri
?對象放入一個剪貼對象中,然后將該剪貼對象放到剪貼板中。如需粘貼數據,請獲取剪貼對象,獲取?Uri
?對象,將其解析為數據源(例如內容提供程序),然后將數據從源中復制到應用的存儲空間。
Intent
一個?Intent
。它支持復制應用快捷方式。如需復制數據,請創建一個?Intent
,將其放入一個剪貼對象中,然后將該剪貼對象放到剪貼板中。如需粘貼數據,請獲取剪貼對象,然后將?Intent
?對象復制到應用的內存區域。
剪貼板一次只保留一個剪貼對象。當應用將一個剪貼對象放到剪貼板時,上一個剪貼對象會消失。
如果希望允許用戶將數據粘貼到應用中,則無需處理所有類型的數據。可以先檢查剪貼板中的數據,然后再向用戶提供粘貼數據的選項。除了具有特定的數據形式之外,剪輯對象還包含可告知可用的 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
?對象包含 text、URI 或 intent 數據:
文本
一個?CharSequence
。
URI
一個?Uri
。盡管允許使用任何 URI,但它通常包含 content provider URI。提供數據的應用將 URI 放到剪貼板中。需要粘貼數據的應用從剪貼板獲取 URI,并使用它來訪問 content provider 或其他數據源以及檢索數據。
Intent
一個?Intent
。通過此數據類型,可以將應用快捷方式復制到剪貼板。然后,用戶可以將快捷方式粘貼到自己的應用中以供日后使用。
可以向一個剪切添加多個?ClipData.Item
?對象。這樣,用戶就可以將多個選擇復制和粘貼為一個剪切。例如,如果有一個列表微件,可讓用戶一次選擇多個項,則可以同時將所有這些項復制到剪貼板。為此,請分別為每個列表項創建一個?ClipData.Item
,然后將?ClipData.Item
?對象添加到?ClipData
?對象。
ClipData 便捷方法
ClipData
?類提供靜態便捷方法,用于創建具有單個?ClipData.Item
?對象和一個簡單?ClipDescription
?對象的?ClipData
?對象:
newPlainText(label, text)
返回一個?ClipData
?對象,該對象的單個?ClipData.Item
?對象包含一個文本字符串。ClipDescription
?對象的標簽設置為?label
。ClipDescription
?中的單一 MIME 類型為?MIMETYPE_TEXT_PLAIN
。
使用?newPlainText()
?可從文本字符串創建剪輯。
newUri(resolver, label, URI)
返回一個?ClipData
?對象,該對象的單個?ClipData.Item
?對象包含一個 URI。ClipDescription
?對象的標簽設置為?label
。如果該 URI 是內容 URI(即?Uri.getScheme()
?返回?content:
),則此方法會使用?resolver
?中提供的?ContentResolver
?對象從 content provider 檢索可用的 MIME 類型。然后將其存儲在?ClipDescription
?中。對于不是?content:
?URI 的 URI,此方法會將 MIME 類型設置為?MIMETYPE_TEXT_URILIST
。
使用?newUri()
?可從 URI(尤其是?content:
?URI)創建剪輯。
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()
?返回的表示形式相同。
Intent
如果?ClipData.Item
?是?Intent
(即?getIntent()
?不為 null),則?coerceToText()
?會將其轉換為 intent URI 并返回。 該表示形式與?Intent.toUri(URI_INTENT_SCHEME)
?返回的表示形式相同。
圖 2 匯總了剪貼板框架。如需復制數據,應用需要將一個?ClipData
?對象放到?ClipboardManager
?全局剪貼板中。ClipData
?包含一個或多個?ClipData.Item
?對象和一個?ClipDescription
?對象。如需粘貼數據,應用需要獲取?ClipData
,從?ClipDescription
?獲取其 MIME 類型,然后從?ClipData.Item
?或?ClipData.Item
?引用的內容提供程序獲取數據。
復制到剪貼板
如需將數據復制到剪貼板,請獲取全局?ClipboardManager
?對象的句柄,創建一個?ClipData
?對象,然后向其中添加一個?ClipDescription
?和一個或多個?ClipData.Item
?對象。然后,將完成的?ClipData
?對象添加到?ClipboardManager
?對象。以下過程對此進行了詳細介紹:
- 如果要使用內容 URI 復制數據,請設置一個 content provider。
- 獲取系統剪貼板:
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} }
-
將數據復制到新的?
ClipData
?對象:- 對于文本
// Creates a new text clip to put on the clipboard. val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
- 對于 URI
以下代碼段通過將記錄 ID 編碼到提供程序的內容 URI 來構建 URI。在 URI 中對標識符進行編碼部分對此方法進行了更詳細的說明。
// 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)
- 對于 intent
以下代碼段為應用構建一個?
Intent
,然后將其放入剪貼對象中:// 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)
- 對于文本
- 將新的剪貼對象放到剪貼板中:
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
在復制到剪貼板時提供反饋
當應用將內容復制到剪貼板時,用戶希望獲得視覺反饋。在 Android 13 及更高版本中,系統會自動向用戶提供此類反饋,但在低于 Android 13 的版本中必須手動實現此類反饋。
從 Android 13 開始,將內容添加到剪貼板時,系統會顯示標準視覺確認界面。新確認界面會執行以下操作:
- 確認內容已成功復制。
- 提供所復制內容的預覽。
?
在 Android 12L(API 級別 32)及更低版本中,用戶可能不確定他們是否成功復制了內容或者復制了什么內容。此功能可將應用在用戶復制內容后顯示的各種通知標準化,并讓用戶可以更好地控制剪貼板。
避免顯示重復的通知
對于 Android 12L(API 級別 32)及更低版本,我們建議在復制內容后使用?Toast
?或?Snackbar
?等 widget 發出應用內可視反饋,以提醒用戶已成功復制。
為避免重復顯示信息,對于 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()
}
將敏感內容添加到剪貼板
如果應用允許用戶將敏感內容(例如密碼或信用卡信息)復制到剪貼板,則必須在調用?ClipboardManager.setPrimaryClip()
?之前向?ClipData
?中的?ClipDescription
?添加一個標志。添加此標志可阻止敏感內容出現在 Android 13 及更高版本中復制內容的視覺確認中。
?
?
如需標記敏感內容,請向?ClipDescription
?添加一個布爾型 extra。無論應用的目標 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)}
}
?
從剪貼板粘貼
如前所述,可以通過以下方法從剪貼板中粘貼數據:獲取全局剪貼板對象,獲取剪切對象,查看其數據,然后將數據從剪切對象復制到自己的存儲空間(如果可以)。本部分詳細介紹了如何粘貼三種形式的剪貼板數據。
重要提示:對于可編輯的?
TextView
?對象,請遵循接收富媒體內容文檔中的說明,添加對粘貼任何類型內容的支持。以下部分介紹了如何開發用于粘貼內容的自定義界面。
粘貼純文本
如需粘貼純文本,請獲取全局剪貼板并驗證它能否返回純文本。然后獲取剪貼對象,并使用?getText()
?將其文本復制到自己的存儲空間,如以下過程所述:
- 使用?
getSystemService(CLIPBOARD_SERVICE)
?獲取全局?ClipboardManager
?對象。此外,聲明一個全局變量以包含粘貼的文本:var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
- 確定是否需要在當前 activity 中啟用或停用“粘貼”選項。驗證剪貼板是否包含剪切,以及是否可以處理剪切所代表的數據類型:
// 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} }
- 從剪貼板復制數據。只有在“粘貼”菜單項處于啟用狀態時,才能在代碼中執行到這一步,因此可以假設剪貼板包含純文本。目前還不知道它是否包含文本字符串或指向純文本的 URI。 以下代碼段對此進行了測試,但它僅顯示用于處理純文本的代碼:
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.textreturn 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.uriif (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}}} }
從內容 URI 中粘貼數據
如果?ClipData.Item
?對象包含內容 URI,并且已確定自己可以處理它的某種 MIME 類型,請創建一個?ContentResolver
,然后調用相應 Content Provider 方法以檢索數據。
以下過程說明了如何基于剪貼板中的內容 URI 從 content provider 獲取數據。它會檢查提供程序中是否有應用可以使用的 MIME 類型。
- 聲明一個全局變量以包含 MIME 類型:
// 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"
- 獲取全局剪貼板。另外,獲取一個內容解析器,以便可以訪問 Content Provider:
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager// Gets a content resolver instance. val cr = contentResolver
- 從剪貼板中獲取主要剪切,并獲取其內容作為 URI:
// Gets the clipboard data from the clipboard. val clip: ClipData? = clipboard.primaryClipclip?.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
- 通過調用?
getType(Uri)
?測試該 URI 是否為內容 URI。如果?Uri
?不指向有效的內容提供程序,則此方法會返回 null。// If the clipboard contains a URI reference...pasteUri?.let {// ...is this a content URI?val uriMimeType: String? = cr.getType(it)
- 測試 content provider 是否支持應用可以理解的 MIME 類型。如果支持,請調用?
ContentResolver.query()
?以獲取數據。返回值為?Cursor
。// 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.}}} }
粘貼 intent
如需粘貼 intent,請先獲取全局剪貼板。檢查?ClipData.Item
?對象以了解它是否包含?Intent
。然后調用?getIntent()
,以將相應 intent 復制到自己的存儲空間。以下代碼段演示了此過程:
// 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)?.intentif (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()
)進行檢索。
使用 content provider 復制復雜的數據
Content Provider 支持復制數據庫記錄或文件流等復雜的數據。如需復制這類數據,需要將一個內容 URI 放到剪貼板中。然后,粘貼應用會從剪貼板獲取此 URI,并使用它來檢索數據庫數據或文件流描述符。
由于粘貼應用只有數據的內容 URI,因此它需要知道要檢索哪一部分數據。可以通過在 URI 本身中對數據的標識符進行編碼來提供此信息,也可以提供一個會返回要復制的數據的唯一 URI。選擇哪種方法取決于數據的組織方式。
以下各部分介紹了如何設置 URI、提供復雜的數據以及提供文件流。這些介紹假設熟悉 content provider 設計的一般原則。
在 URI 中對標識符進行編碼
一種將數據復制到包含 URI 的剪貼板的實用方法是,在 URI 本身中對數據的標識符進行編碼。然后,?content provider 便可以從 URI 中獲取相應標識符,并使用它來檢索數據。粘貼應用無需知道該標識符是否存在。它只需從剪貼板中獲取“引用”(URI 和標識符),將其提供給 content provider,然后獲取數據。
通常情況下,可以通過將標識符連接到內容 URI 的末尾,將標識符編碼到內容 URI 中。例如,假設將提供程序 URI 設定為以下字符串:
"content://com.example.contacts"
如果希望將某個名稱編碼到此 URI,請使用以下代碼段:
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)
如果已經在使用 content provider,則可能需要添加一個新的 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。
如果已經在使用 content provider、內部數據庫或內部表來整理數據,那么通常會使用該編碼方法。在這些情況下,有多份要復制的數據,且可能每份數據都有一個唯一標識符。為響應來自粘貼應用的查詢,可以按數據標識符查找數據并返回。
如果沒有多份數據,則可能不需要對標識符進行編碼。可以使用一個專屬于提供程序的 URI。為響應查詢,提供程序會返回它當前包含的數據。
復制數據結構
設置一個用于復制和粘貼復雜數據的內容提供程序作為?ContentProvider
?組件的子類。對放到剪貼板中的 URI 進行編碼,使其指向要提供的確切記錄。此外,請考慮應用的現有狀態:
- 如果已有 Content Provider,則可以添加其功能。可能只需修改其?
query()
?方法,以處理來自要粘貼數據的應用的 URI。可能還希望修改該方法以處理“復制”URI 模式。 - 如果應用維護了一個內部數據庫,不妨將此數據庫遷移到 Content Provider,以便從中復制。
- 如果不使用數據庫,則可以實現一個簡單的內容提供程序,它的唯一用途是向從剪貼板粘貼內容的應用提供數據。
在 content provider 中,至少替換以下方法:
query()
粘貼應用假設它們可以利用此方法通過放到剪貼板中的 URI 來獲取數據。如需支持復制,請讓此方法檢測包含特殊“復制”路徑的 URI。然后,應用可以創建要放到剪貼板中的“復制”URI,其中包含復制路徑和指向要復制的確切記錄的指針。
getType()
此方法必須返回要復制的數據的 MIME 類型。方法?newUri()
?會調用?getType()
,以將 MIME 類型放入新的?ClipData
?對象。
Content Provider?介紹了復雜數據的 MIME 類型。
無需擁有任何其他 content provider 方法(例如?insert()
?或?update()
)。粘貼應用只需獲取受支持的 MIME 類型,并從提供程序復制數據。如果已擁有這些方法,它們不會干擾復制操作。
以下代碼段演示了如何設置應用以復制復雜的數據:
-
在應用的全局常量中,聲明一個基本 URI 字符串和一個可標識用于復制數據的 URI 字符串的路徑。另外,聲明復制的數據的 MIME 類型。
// 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"
- 在用戶從中復制數據的 activity 中,設置用于將數據復制到剪貼板的代碼。 為響應復制請求,將 URI 放到剪貼板中。
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 ClipboardManagerval clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)// Sets the clipboard's primary clip.clipboard.setPrimaryClip(clip)} }
-
在 content provider 的全局范圍內,創建一個 URI 匹配器并添加一個與放到剪貼板中的 URI 匹配的 URI 模式。
// 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() {... }
-
設置?
query()
?方法。此方法可以處理不同的 URI 模式(具體取決于編碼它的方式),但系統僅會顯示剪貼板復制操作的模式。// 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.}}... }
-
設置?
getType()
?方法,以返回復制的數據的相應 MIME 類型:// Sets up your provider's getType() method. override fun getType(uri: Uri): String? {...return when(sUriMatcher.match(uri)) {GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT...} }
從內容 URI 粘貼數據部分介紹了如何從剪貼板獲取內容 URI 并使用它來獲取和粘貼數據。
復制數據流
可以以流的形式復制和粘貼大量文本數據和二進制數據。數據可以有如下形式:
- 存儲在實際設備中的文件
- 來自套接字的流
- 存儲在提供程序的底層數據庫系統中的大量數據
數據流的內容提供程序使用?AssetFileDescriptor
?等文件描述符對象(而不是?Cursor
?對象)提供對其數據的訪問權限。粘貼應用使用此文件描述符讀取數據流。
如需設置應用以使用提供程序復制數據流,請按以下步驟操作:
- 為要放到剪貼板中的數據流設置內容 URI。可以使用以下選項執行此操作:
- 將數據流的標識符編碼到 URI 中(如在 URI 中對標識符進行編碼部分中所述),然后在提供程序中維護一個包含標識符及相應流名稱的表。
- 直接在 URI 中對流名稱進行編碼。
- 使用一個始終會從提供程序返回當前流的唯一 URI。如果使用此選項,那么每當使用 URI 將流復制到剪貼板時,請記得將提供程序更新為指向其他流。
- 為計劃提供的每種數據流類型提供 MIME 類型。粘貼應用需要此信息來確定它們是否可以將數據粘貼到剪貼板中。
- 實現一個會返回流的文件描述符的?
ContentProvider
?方法。如果在內容 URI 中對標識符進行編碼,可以使用此方法確定要打開的流。 - 如需將數據流復制到剪貼板,請構建內容 URI 并將其放到剪貼板中。
如需粘貼數據流,應用需要從剪貼板獲取剪切,獲取 URI,然后在對會打開數據流的?ContentResolver
?文件描述符方法的調用中使用該 URI。ContentResolver
?方法會調用相應的?ContentProvider
?方法,并向其傳遞內容 URI。提供程序會將文件描述符返回到?ContentResolver
?方法。然后,粘貼應用負責讀取流中的數據。
以下列表顯示了對于 content provider 而言最重要的文件描述符方法。每一種方法都有一個對應的?ContentResolver
?方法,方法名稱后面附加了字符串“Descriptor”。例如,openAssetFile()
?的?ContentResolver
?等效項為?openAssetFileDescriptor()
。
openTypedAssetFile()
此方法會返回一個資源文件描述符,但前提是提供程序支持提供的 MIME 類型。調用程序(執行粘貼的應用)會提供一個 MIME 類型模式。如果將 URI 復制到剪貼板的應用的內容提供程序可以提供該 MIME 類型,則會返回一個?AssetFileDescriptor
?文件句柄;如果無法提供,則會拋出異常。
此方法可處理文件的子部分。可以使用它來讀取 content provider 已復制到剪貼板的資源。
openAssetFile()
此方法是?openTypedAssetFile()
?更為通用的形式。它不會過濾允許的 MIME 類型,但可以讀取文件的子部分。
openFile()
此方法是?openAssetFile()
?更為通用的形式。它無法讀取文件的子部分。
可以根據需要將?openPipeHelper()
?方法與文件描述符方法配合使用。這讓粘貼應用可以使用管道在后臺線程中讀取流數據。如需使用此方法,請實現?ContentProvider.PipeDataWriter
?接口。
設計有效的復制和粘貼功能
如需為應用設計有效的復制和粘貼功能,請謹記以下幾點:
- 在任何時候,剪貼板中都只能有一個剪切。任何應用在系統中執行的新的復制操作都會覆蓋上一個剪切。由于用戶可能會離開應用并在執行復制操作后返回,因此不能假設剪貼板中包含用戶之前在應用中復制的剪切。
- 每個剪切有多個?
ClipData.Item
?對象的預期目的是,支持復制和粘貼多個選擇,而不是一個選擇的不同引用形式。通常希望一個剪切中的所有?ClipData.Item
?對象都采用相同的形式。也就是說,它們必須都是簡單文本、內容 URI 或?Intent
,而不是這些形式的混合。 - 提供數據時,可以提供不同的 MIME 表示形式。將支持的 MIME 類型添加到?
ClipDescription
,然后在 content provider 中實現這些 MIME 類型。 - 從剪貼板獲取數據時,應用負責檢查可用的 MIME 類型,并決定使用哪種類型(如果有)。即使剪貼板中有剪切且用戶請求粘貼,應用也無需執行粘貼操作。如果 MIME 類型兼容,請執行粘貼操作。可以使用?
coerceToText()
?將剪貼板中的數據強制轉換成文本。如果應用支持多種可用的 MIME 類型,可以允許用戶選擇要使用哪一種。