複製及貼上

Android 平台透過強大的剪貼簿架構,提供複製及貼上功能。支援 和複雜資料類型,包括文字字串、複雜的資料結構、文字和二進位資料流 資料與應用程式資產簡單文字資料會直接儲存在剪貼簿中,複雜程度則 資料會被儲存為參照,方便貼上執行貼上操作的應用程式透過內容供應器解析。複製中 無論是在應用程式或實作 這個架構的重點在於

部分架構使用內容供應器,因此本文假設您對課程有一定程度的瞭解 Android Content Provider API, 內容供應器

使用者預期在將內容複製到剪貼簿時會獲得回饋,因此除了 允許複製及貼上,在 Android 13 (API 級別 33) 中複製內容時,Android 會向使用者顯示預設 UI 以及更高版本由於這項功能的緣故,因此可能會有重複通知的風險。歡迎進一步瞭解 這個極端案例 避免重複通知 專區。

動畫:顯示 Android 13 剪貼簿通知
圖 1. 在 Android 中將內容進入剪貼簿時顯示的 UI 13 以上。

在 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.Item 物件的 ClipData 物件 包含文字字串。ClipDescription 物件的標籤設定為 labelClipDescription 中的單一 MIME 類型為 MIMETYPE_TEXT_PLAIN

使用 newPlainText() 從文字字串建立剪輯。

newUri(resolver, label, URI)
傳回其單一 ClipData.Item 物件的 ClipData 物件 包含 URIClipDescription 物件的標籤設定為 label。如果 URI 是內容 URI Uri.getScheme() 會傳回 content:,方法會使用 ContentResolver resolver 中提供的物件,從 內容供應器。然後儲存在 ClipDescription 中。如果不是 content: URI,這個方法會將 MIME 類型設為 MIMETYPE_TEXT_URILIST

使用 newUri() 從 URI 建立剪輯,尤其是 content: URI。

newIntent(label, intent)
傳回其單一 ClipData.Item 物件的 ClipData 物件 包含 IntentClipDescription 物件的標籤設定為 label。MIME 類型設為 MIMETYPE_TEXT_INTENT

使用 newIntent()Intent 物件建立剪輯。

將剪貼簿資料強制轉換為文字

即使應用程式只會處理文字,你也可以透過下列方式複製剪貼簿中的非文字資料: 並以 ClipData.Item.coerceToText() 方法。

這個方法會將 ClipData.Item 中的資料轉換為文字,並傳回 CharSequenceClipData.Item.coerceToText() 傳回的值取決於 以 ClipData.Item 中的資料形式:

文字
如果 ClipData.Item 是文字,也就是說 getText() not null—coerceToText() 會傳回文字。
URI
如果 ClipData.Item 是 URI,也就是 getUri() 不是空值,coerceToText() 會嘗試將其當做內容 URI。
  • 如果 URI 是內容 URI,且供應器可以傳回文字串流, coerceToText() 會傳回文字串流。
  • 如果 URI 是內容 URI,但供應器未提供文字串流, coerceToText() 會傳回 URI 的表示形式。此表示法 與 Uri.toString()
  • 如果 URI 不是內容 URI,coerceToText() 會傳回 URI。此表示形式與 Uri.toString()
意圖
如果 ClipData.ItemIntent,也就是如果 getIntent() 不是空值,coerceToText() 會將其轉換為意圖 URI 並傳回。 此表示形式與 Intent.toUri(URI_INTENT_SCHEME)

圖 2 匯總了剪貼簿架構,為了複製資料,應用程式會 ClipboardManager 全域剪貼簿上的 ClipData 物件。 ClipData 包含一或多個 ClipData.Item 物件和 1 ClipDescription 物件。如要貼上資料,應用程式會取得 ClipData, 從 ClipDescription 取得其 MIME 類型,並從 ClipData.Item 或引用的內容供應器 ClipData.Item

顯示複製及貼上架構的區塊圖的圖片
圖 2.Android 剪貼簿架構。

複製到剪貼簿

如要將資料複製到剪貼簿,請取得全域 ClipboardManager 物件的控制代碼, 建立 ClipData 物件,然後新增 ClipDescription 和一或多個 ClipData.Item 物件。然後,將完成的 ClipData 物件新增至 ClipboardManager 物件。以下程序會進一步說明這項功能:

  1. 如要使用內容 URI 複製資料,請設定內容供應器。
  2. 取得系統剪貼簿:

    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);
    
  3. 將資料複製到新的 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

      這段程式碼會將記錄 ID 編碼到內容 URI,藉此建構 URI 。有關這項技巧的詳情,請參閱 在 URI 區段中將 ID 編碼

      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);
      
  4. 將新的剪輯物件放在剪貼簿中:

    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 13 剪貼簿通知
圖 3. 在 Android 中將內容進入剪貼簿時顯示的 UI 13 以上。

在 Android 12L (API 級別 32) 以下版本中,使用者可能不確定是否成功複製 或是他們複製的內容這項功能可將應用程式顯示的各種通知標準化 複製,可讓使用者進一步控管剪貼簿。

避免重複通知

在 Android 12L (API 級別 32) 以下版本中,我們建議在使用者成功複製時通知使用者 利用圖像、應用程式內意見回饋、利用 Toast 等小工具、 Snackbar (複製後)。

為避免重複顯示資訊,強烈建議您移除浮動式訊息 或 Snackbar。

在應用程式內複製後發布 Snackbar。
圖 4. 如果 Android 13 顯示複製確認 Snackbar, 使用者會看到重複訊息。
,瞭解如何調查及移除這項存取權。
在應用程式內文案後方發布浮動式訊息。
圖 5.如果 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()
}

將機密內容加入剪貼簿

如果您的應用程式允許使用者將機密內容 (例如密碼或信用卡資料) 複製到剪貼簿 信用卡資訊,您必須在 ClipData 中將標記新增至 ClipDescription 再呼叫 ClipboardManager.setPrimaryClip()。新增此標記可防止機密性 內容,以確認複製的內容出現在 Android 13 以上版本中。

敏感內容未加上標記時的複製文字預覽
圖 6. 複製不含機密內容標記的複製文字預覽畫面。
,瞭解如何調查及移除這項存取權。
標示為敏感內容的複製文字預覽。
圖 7.包含機密內容標記的複製文字預覽。

如要標記敏感內容,請在 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() 將文字複製到自己的儲存空間,如 下列程序:

  1. 使用以下方式取得全域 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 = "";
    
  2. 決定是否需要啟用或停用「貼上」功能 活動。確認剪貼簿含有剪輯,而且你可以處理資料類型 特定片段 所代表的

    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);
    }
    
  3. 複製剪貼簿中的資料。只有在 「paste」已啟用選單項目,因此你可以假設剪貼簿包含純文字 文字。您還不知道包含的是文字字串,還是指向純文字的 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 類型是否可在 。

  1. 宣告全域變數以包含 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";
    
  2. 取得全域剪貼簿。此外,您還需要取得內容解析器,以存取內容供應器:

    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();
    
  3. 從剪貼簿取得主要剪輯,並將其內容做為 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();
    
  4. 呼叫 getType(Uri)。 如果 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);
    
  5. 測試內容供應器是否支援應用程式可理解的 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,因此必須知道 要擷取的資料您可以透過編碼資料 ID 來提供這項資訊 ,您也可以提供專屬 URI,傳回您想要複製的資料。哪一個? 請根據資料的架構,選用合適的技術。

以下各節說明如何設定 URI、提供複雜資料,以及提供檔案 串流。這些說明假設您熟悉內容供應器的一般原則 設計。

在 URI 上將 ID 編碼

使用 URI 將資料複製到剪貼簿的一項實用技巧,是將 讀取 URI 本身中的資料接著,內容供應器就能從 URI 取得 ID 並使用 以便擷取資料執行貼上操作的應用程式不需要知道 ID 是否存在。這項服務 只需要從 貼上內容供應器,然後取回資料。

您通常會將 ID 串連於 URI 結尾,藉此將 ID 編碼到內容 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 比對,並使用 且專為複製與貼上而設計

如果您已在使用內容供應器 或內部資料表來整理資料在這種情況下 並假設每個部分都有專屬 ID回應 貼上應用程式,即可根據 ID 查詢資料並傳回。

如果您沒有多份資料,可能就不需要為 ID 編碼。 您可以使用供應器專屬的 URI。為回應查詢,您的供應商會傳回 資料庫目前包含的資料

複製資料結構

設定一個用於複製及貼上複雜資料的內容供應器,做為 ContentProvider 元件。將您所放入剪貼簿的 URI 進行編碼,使其指向您要的確切記錄 因此,使用 Kubernetes Engine 即可。此外,請思考應用程式目前的狀態:

  • 如果您已有內容供應器,可以新增其功能。只有在 您需要修改 query() 方法,才能處理來自應用程式的 進行所需複製和貼上資料時您可能需要修改方法來處理「複製」URI 。
  • 如果應用程式維護內部資料庫,建議您移動這個資料庫 複製到內容供應器以方便複製
  • 如果您沒有使用資料庫,可以實作一個簡單的內容供應器,其 只是為了提供資料給從剪貼簿貼上的應用程式。

在內容供應器中,至少覆寫以下方法:

query()
執行貼上操作的應用程式會假設他們可以透過這個方法,搭配您的 URI 取得資料 放入剪貼簿如要支援複製作業,請讓這個方法偵測含有特殊程式碼的 URI 「複製」路徑。接著,應用程式會建立「副本」要放入 剪貼簿,內含複製路徑,並指向您要複製的確切記錄。
getType()
這個方法必須傳回待複製資料的 MIME 類型。方法 newUri() 呼叫 getType() 以將 MIME 類型放入新的 ClipData 物件。

如需複雜資料的 MIME 類型,請參閱 內容供應器

你不需要使用任何其他內容供應器方法,例如 insert()update()。 執行貼上操作的應用程式只需取得支援的 MIME 類型,然後複製供應器的資料即可。 如果您已採用這些方法,就不會幹擾複製作業。

下列程式碼片段示範如何設定應用程式來複製複雜資料:

  1. 在應用程式的全域常數中,宣告基本 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";
    
  2. 在使用者複製資料的活動中,設定將資料複製到剪貼簿的程式碼。 為了回應複製要求,請將 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);
    
  3. 在內容供應器的全域範圍內,建立 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);
    
  4. 設定 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.
        ...
    }
    
  5. 設定 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 物件執行貼上操作的應用程式會使用這個 檔案描述元

如要設定應用程式以使用供應器複製資料串流,請按照下列步驟操作:

  1. 為您要放入剪貼簿的資料串流設定內容 URI。做法包括:
    • 將資料串流的 ID 編碼到 URI,如 在 URI 區段中將 ID 編碼,然後維護 表格,內含 ID 和相應的串流名稱。
    • 直接在 URI 上編碼串流名稱。
    • 使用始終會從供應器傳回目前串流的專屬 URI。如果發生以下情況: 使用這個選項,請記得將供應器更新為指向其他串流 使用 URI 將串流複製到剪貼簿。
  2. 針對您打算提供的每種資料串流提供 MIME 類型。正在貼上應用程式 需要這項資訊來判斷使用者能否將資料貼到剪貼簿。
  3. 實作任一 ContentProvider 方法,以傳回其檔案描述元 再透過串流如果您在內容 URI 中將 ID 編碼,請使用這個方法判斷 點選「直播」即可開啟
  4. 如要將資料串流複製到剪貼簿,請建構內容 URI 並放到 剪貼簿。

如要貼上資料串流,應用程式會從剪貼簿取得剪輯、取得 URI,並使用 並呼叫可開啟串流的 ContentResolver 檔案描述元方法。 ContentResolver 方法會呼叫對應的 ContentProvider 方法。 並傳遞內容 URI您的供應程式會將檔案描述元傳回 ContentResolver 方法。接著,執行貼上操作的應用程式會負責 串流資料。

以下清單列出了對於內容供應器而言,最重要的檔案描述元方法。每項 這些物件的對應 ContentResolver 方法和字串 「描述元」附加在方法名稱後方。例如:ContentResolver 類比 openAssetFile()openAssetFileDescriptor()

openTypedAssetFile()

這個方法會傳回資產檔案描述元,但前提是提供的 MIME 類型必須為 廣告。呼叫端—執行貼上操作的應用程式—提供 以及 MIME 類型模式將 URI 複製到 如果可提供 AssetFileDescriptor 檔案控制代碼,就會傳回這個控制代碼 MIME 類型,如果不可以,則會擲回例外狀況。

這個方法可以用來處理檔案的子區段。您可以運用它來讀取 內容供應器已複製到剪貼簿。

openAssetFile()
這個方法是 openTypedAssetFile() 的一般形式。未篩選 ,但能讀取檔案的子部分。
openFile()
這是較通用的 openAssetFile() 形式。無法讀取 檔案。

您也可以選擇使用 openPipeHelper() 方法與檔案描述元方法相容。這麼做可讓執行貼上操作的應用程式讀取 建立背景執行緒如要使用這個方法,請實作 ContentProvider.PipeDataWriter 存取 API

設計有效的複製及貼上功能

如要為應用程式設計有效的複製及貼上功能,請注意以下幾點:

  • 確保剪貼簿中始終只有一個剪輯。新的複製作業 應用程式中的元件會覆寫上一個片段由於使用者可能會前往 離開應用程式並在返回前複製,就不能假設剪貼簿 包含使用者先前在「您的應用程式」中複製的剪輯片段。
  • 每個片段多個 ClipData.Item 物件的預期用途為 支援複製及貼上多個選取項目 單一選項的參照通常您會想用所有的 ClipData.Item 系統會將物件中的物件轉換為相同形式。也就是說,所有內容都必須是純文字和內容 URI 或 Intent,且非混合
  • 提供資料時,您可以提供不同的 MIME 表示法。新增 MIME 類型 支援 ClipDescription,然後在 中實作 MIME 類型 內容供應者。
  • 從剪貼簿取得資料時,您的應用程式會負責檢查可用的 MIME 類型,然後決定要使用的類型 (如果有的話)。即使 時,只要使用者要求貼上圖片, 就可以直接貼上如果 MIME 類型相容,請貼上。您可以強迫資料 使用 coerceToText() 將剪貼簿中的訊息內容轉成文字。如果您的應用程式支援 有多種可用的 MIME 類型,您可以讓使用者自行選擇要使用的 MIME 類型。