Android cung cấp một khung toàn diện về bảng nhớ tạm cho thao tác sao chép và dán. Công cụ này hỗ trợ và các kiểu dữ liệu phức tạp, bao gồm cả chuỗi văn bản, cấu trúc dữ liệu phức tạp, văn bản và luồng nhị phân dữ liệu và nội dung ứng dụng. Dữ liệu văn bản đơn giản được lưu trữ trực tiếp trong bảng nhớ tạm, trong khi dữ liệu phức tạp dữ liệu được lưu trữ dưới dạng tham chiếu mà ứng dụng dán phân giải với nhà cung cấp nội dung. Đang sao chép và dán sẽ hoạt động cả trong một ứng dụng và giữa các ứng dụng triển khai khung.
Vì một phần của khung này sử dụng nhà cung cấp nội dung, nên tài liệu này giả định rằng bạn đã quen thuộc với Android Content Provider API (API Trình cung cấp nội dung của Android) được mô tả trong Trình cung cấp nội dung.
Người dùng mong đợi phản hồi khi sao chép nội dung vào bảng nhớ tạm, vì vậy, ngoài khung hỗ trợ tính năng sao chép và dán, Android sẽ hiển thị giao diện người dùng mặc định cho người dùng khi sao chép trong Android 13 (API cấp 33) và cao hơn. Do tính năng này, bạn có nguy cơ nhận được thông báo trùng lặp. Bạn có thể tìm hiểu thêm về trường hợp đặc biệt này Tránh thông báo trùng lặp .
Cung cấp phản hồi cho người dùng theo cách thủ công khi sao chép trong Android 12L (API cấp 32) trở xuống. Xem các đề xuất cho việc này trong tài liệu này.
Khung bảng nhớ tạm
Khi bạn sử dụng khung bảng nhớ tạm, hãy đặt dữ liệu vào một đối tượng clip rồi đặt đối tượng clip đó trên bảng nhớ tạm trên toàn hệ thống. Đối tượng clip có ba dạng sau:
- Văn bản
- Chuỗi văn bản. Đặt chuỗi trực tiếp vào đối tượng clip, sau đó bạn đặt vào đối tượng này bảng nhớ tạm. Để dán chuỗi, hãy lấy đối tượng clip từ bảng nhớ tạm rồi sao chép vào bộ nhớ của ứng dụng.
- URI
-
Đối tượng
Uri
đại diện cho bất kỳ dạng URI. Việc này chủ yếu dùng để sao chép dữ liệu phức tạp từ một trình cung cấp nội dung. Để sao chép hãy đặt đối tượngUri
vào đối tượng clip rồi đặt đối tượng clip đó lên bảng nhớ tạm. Để dán dữ liệu, hãy lấy đối tượng clip, đối tượngUri
, phân giải nó thành một nguồn dữ liệu, chẳng hạn như nhà cung cấp nội dung và sao chép dữ liệu từ nguồn vào bộ nhớ của ứng dụng. - Intent
-
Một
Intent
(ý định). Chiến dịch này hỗ trợ sao chép các lối tắt của ứng dụng. Để sao chép dữ liệu, hãy tạo mộtIntent
, đặt nó vào một đối tượng clip rồi đặt đối tượng clip đó vào bảng nhớ tạm. Để dán dữ liệu, hãy tải đối tượng clip rồi sao chép đối tượngIntent
vào đối tượng của ứng dụng vùng bộ nhớ.
Bảng nhớ tạm chỉ chứa một đối tượng clip tại mỗi thời điểm. Khi một ứng dụng đặt đối tượng clip trên bảng nhớ tạm, đối tượng clip trước đó sẽ biến mất.
Nếu muốn cho phép người dùng dán dữ liệu vào ứng dụng, bạn không cần phải xử lý tất cả các loại . Bạn có thể kiểm tra dữ liệu trên bảng nhớ tạm trước khi cho phép người dùng dán dữ liệu đó. Ngoài việc có một dạng dữ liệu nhất định, đối tượng clip cũng chứa siêu dữ liệu cho bạn biết MIME nào có sẵn. Siêu dữ liệu này giúp bạn quyết định xem ứng dụng của mình có thể làm điều gì hữu ích hay không với dữ liệu bảng nhớ tạm. Ví dụ: nếu có một ứng dụng chủ yếu xử lý văn bản, bạn có thể muốn bỏ qua các đối tượng clip chứa URI hoặc ý định.
Bạn cũng có thể cho phép người dùng dán văn bản bất kể dạng dữ liệu trên bảng nhớ tạm. Người nhận bạn có thể thực hiện thao tác này, buộc dữ liệu bảng nhớ tạm thành dưới dạng văn bản rồi dán văn bản này. Đây là được mô tả trong phần Chuyển bảng nhớ tạm thành văn bản.
Lớp bảng nhớ tạm
Phần này mô tả các lớp mà khung bảng nhớ tạm sử dụng.
ClipboardManager
Bảng nhớ tạm của hệ thống Android được biểu thị bằng
Lớp ClipboardManager
.
Đừng trực tiếp tạo thực thể cho lớp này. Thay vào đó, hãy lấy thông tin tham chiếu đến bằng cách gọi
getSystemService(CLIPBOARD_SERVICE)
.
ClipData, ClipData.Item và ClipDescription
Để thêm dữ liệu vào bảng nhớ tạm, hãy tạo một
Đối tượng ClipData
chứa
phần mô tả về dữ liệu và chính dữ liệu đó. Bảng nhớ tạm lưu một ClipData
tại một
bất cứ lúc nào. ClipData
chứa
Đối tượng ClipDescription
và một hoặc nhiều
Đối tượng ClipData.Item
.
Đối tượng ClipDescription
chứa siêu dữ liệu về đối tượng clip. Đặc biệt, việc này
chứa một mảng các loại MIME có sẵn cho dữ liệu của đoạn video. Ngoài ra, trên
Android 12 (API cấp 31) trở lên, siêu dữ liệu bao gồm thông tin về việc đối tượng
chứa
văn bản cách điệu và giới thiệu về
loại văn bản trong đối tượng.
Khi bạn đặt một đoạn video vào bảng nhớ tạm, thông tin này sẽ có sẵn để dán các ứng dụng,
có thể kiểm tra xem chúng có thể xử lý dữ liệu đoạn video hay không.
Đối tượng ClipData.Item
chứa dữ liệu văn bản, URI hoặc ý định:
- Văn bản
-
Một
CharSequence
. - URI
-
Một
Uri
. Đối tượng này thường chứa URI của nhà cung cấp nội dung, mặc dù bất kỳ URI nào cũng được phép. Ứng dụng cung cấp dữ liệu sẽ đặt URI trên bảng nhớ tạm. Đơn đăng ký muốn dán dữ liệu. Lấy URI từ bảng nhớ tạm và sử dụng URI đó để truy cập nội dung hoặc nguồn dữ liệu khác và truy xuất dữ liệu. - Intent
-
Một
Intent
. Loại dữ liệu này cho phép bạn sao chép lối tắt của ứng dụng vào bảng nhớ tạm. Sau đó, người dùng có thể dán lối tắt vào ứng dụng để sử dụng sau.
Bạn có thể thêm nhiều đối tượng ClipData.Item
vào một clip. Việc này cho phép người dùng sao chép và
dán nhiều lựa chọn dưới dạng một đoạn video duy nhất. Ví dụ: nếu bạn có tiện ích danh sách cho phép
người dùng chọn nhiều mục cùng một lúc, bạn có thể sao chép tất cả mục vào bảng nhớ tạm cùng một lúc. Việc cần làm
thao tác này, hãy tạo một ClipData.Item
riêng cho từng mục trong danh sách, sau đó thêm phương thức
Các đối tượng ClipData.Item
cho đối tượng ClipData
.
Phương thức ClipData thuận tiện
Lớp ClipData
cung cấp các phương thức tĩnh thuận tiện để tạo một
Đối tượng ClipData
có một đối tượng ClipData.Item
và một đối tượng đơn giản
Đối tượng ClipDescription
:
-
newPlainText(label, text)
- Trả về một đối tượng
ClipData
có một đối tượngClipData.Item
duy nhất chứa chuỗi văn bản. Nhãn của đối tượngClipDescription
được đặt thànhlabel
Loại MIME duy nhất trongClipDescription
làMIMETYPE_TEXT_PLAIN
.Dùng
newPlainText()
để tạo một đoạn video qua một chuỗi văn bản. -
newUri(resolver, label, URI)
- Trả về một đối tượng
ClipData
có một đối tượngClipData.Item
duy nhất chứa URI. Nhãn của đối tượngClipDescription
được đặt thànhlabel
Nếu URI là một URI nội dung – tức là nếuUri.getScheme()
trả vềcontent:
– phương thức này sử dụngContentResolver
được cung cấp trongresolver
để truy xuất các loại MIME có sẵn từ nhà cung cấp nội dung của Google. Sau đó, ứng dụng sẽ lưu trữ các mật khẩu đó trongClipDescription
. Đối với URI không phải URIcontent:
, phương thức này sẽ đặt loại MIME thànhMIMETYPE_TEXT_URILIST
Dùng
newUri()
để tạo một đoạn video qua một URI – đặc biệt là một URIcontent:
. -
newIntent(label, intent)
- Trả về một đối tượng
ClipData
có một đối tượngClipData.Item
duy nhất chứa mộtIntent
. Nhãn của đối tượngClipDescription
được đặt thànhlabel
Loại MIME được đặt thànhMIMETYPE_TEXT_INTENT
.Sử dụng
newIntent()
để tạo một đoạn video từ đối tượngIntent
.
Chuyển đổi dữ liệu trong bảng nhớ tạm thành văn bản
Ngay cả khi ứng dụng của bạn chỉ xử lý văn bản, bạn vẫn có thể sao chép dữ liệu không phải văn bản từ bảng nhớ tạm bằng cách
chuyển đổi quảng cáo bằng
ClipData.Item.coerceToText()
.
Phương thức này chuyển đổi dữ liệu trong ClipData.Item
thành văn bản và trả về một
CharSequence
Giá trị mà ClipData.Item.coerceToText()
trả về là dựa trên
về dạng dữ liệu trong ClipData.Item
:
- Văn bản
-
Nếu
ClipData.Item
là văn bản, nghĩa là nếugetText()
không rỗng—coerceToText() trả về văn bản. - URI
-
Nếu
ClipData.Item
là một URI – tức là nếugetUri()
không phải là giá trị rỗng—coerceToText()
cố gắng sử dụng nó làm URI nội dung.- Nếu URI là một URI nội dung và nhà cung cấp có thể trả về một luồng văn bản,
coerceToText()
trả về một luồng văn bản. - Nếu URI là một URI nội dung nhưng nhà cung cấp không cung cấp luồng văn bản,
coerceToText()
trả về bản trình bày của URI. Biểu diễn là giống như giá trị được trả về bởiUri.toString()
. - Nếu URI không phải là URI nội dung,
coerceToText()
sẽ trả về giá trị biểu diễn URI. Kết quả biểu diễn giống với kết quả được trả về bởiUri.toString()
.
- Nếu URI là một URI nội dung và nhà cung cấp có thể trả về một luồng văn bản,
- Intent
- Nếu
ClipData.Item
làIntent
—tức là nếugetIntent()
không rỗng—coerceToText()
chuyển đổi nó thành URI Ý định và trả về. Kết quả biểu diễn giống với kết quả được trả về bởiIntent.toUri(URI_INTENT_SCHEME)
.
Khung bảng nhớ tạm được tóm tắt trong hình 2. Để sao chép dữ liệu, ứng dụng sẽ đặt một
Đối tượng ClipData
trên bảng nhớ tạm toàn cục ClipboardManager
. Chiến lược phát hành đĩa đơn
ClipData
chứa một hoặc nhiều đối tượng ClipData.Item
và một đối tượng
Đối tượng ClipDescription
. Để dán dữ liệu, ứng dụng sẽ nhận được ClipData
,
lấy loại MIME từ ClipDescription
và nhận dữ liệu từ
ClipData.Item
hoặc từ nhà cung cấp nội dung được tham chiếu bởi
ClipData.Item
.
Sao chép vào bảng nhớ tạm
Để sao chép dữ liệu vào bảng nhớ tạm, hãy dùng một đối tượng xử lý cho đối tượng ClipboardManager
toàn cục,
tạo một đối tượng ClipData
rồi thêm ClipDescription
cùng một hoặc nhiều
ClipData.Item
đối tượng với lớp đó. Sau đó, hãy thêm đối tượng ClipData
hoàn chỉnh vào đối tượng
Đối tượng ClipboardManager
. Điều này được mô tả kỹ hơn trong quy trình sau:
- Nếu bạn đang sao chép dữ liệu bằng URI nội dung, hãy thiết lập nhà cung cấp nội dung.
- Lấy bảng nhớ tạm thời của hệ thống:
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);
-
Sao chép dữ liệu vào đối tượng
ClipData
mới:-
Đối với văn bản
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!");
-
Đối với URI
Đoạn mã này tạo URI bằng cách mã hoá mã nhận dạng bản ghi vào URI nội dung cho cho nhà cung cấp. Kỹ thuật này được đề cập chi tiết hơn trong Mã hoá giá trị nhận dạng trên phần 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);
-
Đối với ý định
Đoạn mã này tạo một
Intent
cho một ứng dụng rồi đặt trong đối tượng 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);
-
Đối với văn bản
-
Đặt đối tượng clip mới vào bảng nhớ tạm:
Kotlin
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Java
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
Cung cấp ý kiến phản hồi khi sao chép vào bảng nhớ tạm
Người dùng mong muốn phản hồi bằng hình ảnh khi ứng dụng sao chép nội dung vào bảng nhớ tạm. Đã xong tự động cho người dùng phiên bản Android 13 trở lên, nhưng phải tự triển khai của Google.
Kể từ Android 13, hệ thống sẽ hiện một thông báo xác nhận tiêu chuẩn bằng hình ảnh khi nội dung được thêm vào vào bảng nhớ tạm. Xác nhận mới thực hiện những việc sau:
- Xác nhận nội dung đã được sao chép thành công.
- Cung cấp bản xem trước nội dung đã sao chép.
Trong Android 12L (API cấp 32) trở xuống, người dùng có thể không chắc liệu họ có sao chép thành công hay không hay những gì chúng sao chép. Tính năng này chuẩn hoá nhiều thông báo mà ứng dụng hiển thị sau sao chép và cung cấp cho người dùng nhiều quyền kiểm soát hơn đối với bảng nhớ tạm.
Tránh thông báo trùng lặp
Trong Android 12L (API cấp 32) trở xuống, bạn nên thông báo cho người dùng khi họ sao chép thành công
bằng cách đưa ra phản hồi trực quan trong ứng dụng, thông qua một tiện ích như Toast
hoặc
Snackbar
sau khi sao chép.
Để tránh hiển thị thông tin trùng lặp, bạn nên xoá thông báo ngắn hoặc thanh thông báo nhanh xuất hiện sau bản sao trong ứng dụng dành cho Android 13 trở lên.
Sau đây là một ví dụ về cách triển khai phương thức này:
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() }
Thêm nội dung nhạy cảm vào bảng nhớ tạm
Nếu ứng dụng của bạn cho phép người dùng sao chép nội dung nhạy cảm vào bảng nhớ tạm, chẳng hạn như mật khẩu hoặc tín dụng
thông tin thẻ, bạn phải thêm cờ vào ClipDescription
trong ClipData
trước khi gọi ClipboardManager.setPrimaryClip()
. Việc thêm cờ này sẽ ngăn chặn quảng cáo nhạy cảm
hiển thị trong phần xác nhận bằng hình ảnh của nội dung đã sao chép trong Android 13 trở lên.
Để gắn cờ nội dung nhạy cảm, hãy thêm một boolean bổ sung vào ClipDescription
. Mọi ứng dụng đều phải thực hiện
điều này, bất kể cấp độ API được nhắm mục tiêu.
// 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) } }
Dán từ bảng nhớ tạm
Như đã mô tả trước đó, hãy dán dữ liệu từ bảng nhớ tạm bằng cách lấy đối tượng bảng nhớ tạm toàn cục, lấy đối tượng clip, xem dữ liệu của đối tượng đó và nếu có thể sao chép dữ liệu từ đối tượng clip vào bộ nhớ của riêng bạn. Phần này giải thích chi tiết cách dán ba dạng bảng nhớ tạm .
Dán văn bản thuần tuý
Để dán văn bản thuần tuý, hãy lấy bảng nhớ tạm chung và xác minh rằng bảng nhớ tạm có thể trả về văn bản thuần tuý. Sau đó nhận
đối tượng clip và sao chép văn bản của đối tượng đó vào bộ nhớ của riêng bạn bằng getText()
, như mô tả trong
quy trình sau:
- Lấy đối tượng
ClipboardManager
toàn cục bằng cách sử dụnggetSystemService(CLIPBOARD_SERVICE)
Ngoài ra, hãy khai báo biến toàn cục để chứa văn bản được dán:Kotlin
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Java
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
- Xác định xem bạn cần bật hay tắt tính năng "dán" trong trường hợp hiện tại
của bạn. Xác minh rằng bảng nhớ tạm có chứa đoạn video và bạn có thể xử lý loại dữ liệu
được thể hiện bằng đoạn video:
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); }
- Sao chép dữ liệu từ bảng nhớ tạm. Điểm này trong mã chỉ đến được nếu
"dán" mục trong trình đơn được bật, vì vậy bạn có thể giả định rằng bảng nhớ tạm chỉ chứa
. Bạn chưa biết liệu thành phần này có chứa chuỗi văn bản hoặc URI trỏ đến văn bản thuần tuý hay không.
Đoạn mã sau đây kiểm thử điều này, nhưng chỉ hiển thị mã để xử lý văn bản thuần tuý:
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; } }
Dán dữ liệu từ URI nội dung
Nếu đối tượng ClipData.Item
chứa một URI nội dung và bạn xác định rằng mình có thể
xử lý một trong các loại MIME, tạo một ContentResolver
và gọi nội dung thích hợp
nhà cung cấp để truy xuất dữ liệu.
Quy trình sau đây mô tả cách lấy dữ liệu từ trình cung cấp nội dung dựa trên URI nội dung trên bảng nhớ tạm. Tính năng này kiểm tra xem loại MIME mà ứng dụng có thể sử dụng có sẵn trong Google Cloud.
-
Khai báo biến toàn cục chứa loại 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";
- Lấy bảng nhớ tạm toàn cục. Ngoài ra, hãy sử dụng trình phân giải nội dung để bạn có thể truy cập vào nhà cung cấp nội dung:
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();
- Lấy đối tượng cắt chính trong bảng nhớ tạm và lấy nội dung của đối tượng đó dưới dạng 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();
- Kiểm tra xem URI có phải là URI nội dung hay không bằng cách gọi
getType(Uri)
. Phương thức này trả về giá trị rỗng nếuUri
không trỏ đến một trình cung cấp nội dung hợp lệ.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);
- Kiểm tra xem trình cung cấp nội dung có hỗ trợ loại MIME mà ứng dụng hiểu hay không. Nếu
có, gọi
ContentResolver.query()
để lấy dữ liệu. Giá trị trả về là mộtCursor
.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(); } } } }
Dán một ý định
Để dán một ý định, trước tiên, hãy lấy bảng nhớ tạm chung. Kiểm tra đối tượng ClipData.Item
để xem tệp đó có chứa Intent
hay không. Sau đó, gọi getIntent()
để sao chép
với bộ nhớ của riêng bạn. Sau đây là đoạn mã minh hoạ:
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. }
Hệ thống hiển thị thông báo khi ứng dụng truy cập vào dữ liệu bảng nhớ tạm
Trên Android 12 (API cấp 31) trở lên, hệ thống thường hiển thị một thông báo ngắn khi ứng dụng của bạn
các cuộc gọi
getPrimaryClip()
.
Văn bản bên trong thông báo có định dạng sau:
APP pasted from your clipboard
Hệ thống không hiện thông báo ngắn khi ứng dụng thực hiện một trong các thao tác sau:
- Quyền truy cập
ClipData
trong ứng dụng của chính bạn. - Liên tục truy cập vào
ClipData
qua một ứng dụng cụ thể. Thông báo ngắn chỉ xuất hiện khi ứng dụng của bạn truy cập vào dữ liệu từ ứng dụng đó lần đầu tiên. - Truy xuất siêu dữ liệu cho đối tượng clip, chẳng hạn như bằng cách gọi
getPrimaryClipDescription()
thay vìgetPrimaryClip()
.
Sử dụng trình cung cấp nội dung để sao chép dữ liệu phức tạp
Nhà cung cấp nội dung hỗ trợ sao chép dữ liệu phức tạp như bản ghi cơ sở dữ liệu hoặc dòng tệp. Để sao chép dữ liệu, đặt URI nội dung vào bảng nhớ tạm. Dán các ứng dụng sau đó lấy URI này từ bảng nhớ tạm và sử dụng để truy xuất dữ liệu cơ sở dữ liệu hoặc chỉ số mô tả luồng tệp.
Do ứng dụng dán chỉ có URI nội dung cho dữ liệu của bạn nên ứng dụng cần biết URL nào phần dữ liệu nào cần truy xuất. Bạn có thể cung cấp thông tin này bằng cách mã hoá giá trị nhận dạng cho dữ liệu trên chính URI, hoặc bạn có thể cung cấp một URI duy nhất trả về dữ liệu mà bạn muốn sao chép. Mục nào mà bạn chọn phụ thuộc vào cách sắp xếp dữ liệu của bạn.
Các phần sau đây mô tả cách thiết lập URI, cung cấp dữ liệu phức tạp và cách cung cấp tệp phát trực tuyến. Những phần mô tả này giả định rằng bạn đã nắm rõ các nguyên tắc chung về nhà cung cấp nội dung thiết kế của bạn.
Mã hoá giá trị nhận dạng trên URI
Kỹ thuật hữu ích để sao chép dữ liệu vào bảng nhớ tạm bằng URI là mã hoá giá trị nhận dạng cho dữ liệu trên chính URI. Nhà cung cấp nội dung của bạn sau đó có thể lấy giá trị nhận dạng từ URI và sử dụng để truy xuất dữ liệu. Ứng dụng dán không cần phải biết rằng giá trị nhận dạng đó tồn tại. Nó chỉ cần lấy "tham chiếu" của bạn (URI cộng với giá trị nhận dạng) từ bảng nhớ tạm, cung cấp cho nhà cung cấp nội dung và lấy lại dữ liệu.
Bạn thường mã hoá giá trị nhận dạng trên một URI nội dung bằng cách ghép nối giá trị nhận dạng đó vào cuối URI. Ví dụ: giả sử bạn định nghĩa URI nhà cung cấp như chuỗi sau:
"content://com.example.contacts"
Nếu bạn muốn mã hoá tên trên URI này, hãy sử dụng đoạn mã sau:
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);
Nếu đang sử dụng một nhà cung cấp nội dung, bạn nên thêm một đường dẫn URI mới cho biết URI để sao chép. Ví dụ: giả sử bạn đã có đường dẫn URI sau:
"content://com.example.contacts/people" "content://com.example.contacts/people/detail" "content://com.example.contacts/people/images"
Bạn có thể thêm một đường dẫn khác để sao chép URI:
"content://com.example.contacts/copying"
Sau đó, bạn có thể phát hiện một "bản sao" URI bằng cách so khớp mẫu và xử lý bằng mã cụ thể để sao chép và dán.
Bạn thường sử dụng kỹ thuật mã hoá nếu bạn đã sử dụng trình cung cấp nội dung, hoặc bảng nội bộ để sắp xếp dữ liệu. Trong những trường hợp này, bạn có nhiều phần dữ liệu bạn muốn sao chép và có thể là một giá trị nhận dạng riêng biệt cho từng nội dung. Để phản hồi truy vấn từ bạn có thể tra cứu dữ liệu theo giá trị nhận dạng của ứng dụng và trả về dữ liệu đó.
Nếu bạn không có nhiều dữ liệu thì có thể không cần phải mã hoá giá trị nhận dạng. Bạn có thể sử dụng URI dành riêng cho nhà cung cấp của mình. Để phản hồi một truy vấn, nhà cung cấp của bạn sẽ trả về dữ liệu hiện có trong tệp đó.
Sao chép cấu trúc dữ liệu
Thiết lập trình cung cấp nội dung để sao chép và dán dữ liệu phức tạp làm lớp con của
ContentProvider
thành phần. Mã hoá URI bạn đặt trên bảng nhớ tạm để trỏ đến bản ghi chính xác mà bạn muốn
cung cấp. Ngoài ra, hãy xem xét trạng thái hiện tại của đơn đăng ký:
- Nếu đã có nhà cung cấp nội dung, bạn có thể thêm vào phần chức năng của nhà cung cấp đó. Bạn chỉ có thể
cần sửa đổi phương thức
query()
để xử lý URI đến từ các ứng dụng muốn dán dữ liệu. Bạn nên chỉnh sửa phương thức để xử lý việc "sao chép" URI . - Nếu ứng dụng của bạn duy trì cơ sở dữ liệu nội bộ, bạn có thể muốn di chuyển cơ sở dữ liệu này vào một trình cung cấp nội dung để dễ dàng sao chép từ trình cung cấp đó.
- Nếu không sử dụng cơ sở dữ liệu, bạn có thể triển khai một trình cung cấp nội dung đơn giản có mục đích duy nhất là cung cấp dữ liệu cho các ứng dụng đang dán từ bảng nhớ tạm.
Trong trình cung cấp nội dung, hãy ghi đè ít nhất các phương thức sau:
-
query()
- Các ứng dụng dán giả định rằng chúng có thể lấy dữ liệu của bạn bằng cách dùng phương thức này với URI bạn vào bảng nhớ tạm. Để hỗ trợ sao chép, hãy thiết lập phương thức này để phát hiện các URI chứa "sao chép" đường dẫn. Sau đó, ứng dụng của bạn có thể tạo một "bản sao" URI cần đặt vào bảng nhớ tạm, chứa đường dẫn sao chép và con trỏ đến chính xác bản ghi mà bạn muốn sao chép.
-
getType()
- Phương thức này phải trả về các loại MIME cho dữ liệu mà bạn định sao chép. Phương thức
newUri()
gọigetType()
để đặt các loại MIME vàoClipData
mới .Các loại MIME cho dữ liệu phức tạp được mô tả trong Trình cung cấp nội dung.
Bạn không cần phải có bất kỳ phương thức nào khác dành cho trình cung cấp nội dung, chẳng hạn như
insert()
hoặc
update()
Một ứng dụng dán chỉ cần lấy các loại MIME được hỗ trợ và sao chép dữ liệu từ nhà cung cấp của bạn.
Nếu bạn đã sử dụng các phương thức này, chúng sẽ không ảnh hưởng đến hoạt động sao chép.
Các đoạn mã sau đây minh hoạ cách thiết lập ứng dụng để sao chép dữ liệu phức tạp:
-
Trong các hằng số toàn cục cho ứng dụng, hãy khai báo chuỗi URI cơ sở và đường dẫn xác định chuỗi URI bạn đang sử dụng để sao chép dữ liệu. Đồng thời, hãy khai báo loại MIME cho loại dữ liệu được sao chép .
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";
- Trong hoạt động mà người dùng sao chép dữ liệu, hãy thiết lập mã để sao chép dữ liệu vào bảng nhớ tạm.
Để phản hồi một yêu cầu sao chép, hãy đặt URI vào bảng nhớ tạm.
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);
-
Trong phạm vi toàn cầu của nhà cung cấp nội dung, hãy tạo trình so khớp URI và thêm mẫu URI khớp với các URI bạn đặt trên bảng nhớ tạm.
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);
-
Thiết lập
query()
. Phương thức này có thể xử lý các mẫu URI khác nhau, tuỳ thuộc vào cách bạn lập trình, nhưng chỉ mẫu cho thao tác sao chép bảng nhớ tạm sẽ cho thấy.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. ... }
-
Thiết lập phương thức
getType()
để trả về một loại MIME thích hợp để sao chép dữ liệu: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); ... } }
Phần Dán dữ liệu từ một URI nội dung mô tả cách lấy một URI nội dung từ bảng nhớ tạm rồi sử dụng URI này để nhận và dán dữ liệu.
Sao chép luồng dữ liệu
Bạn có thể sao chép và dán lượng lớn văn bản và dữ liệu tệp nhị phân dưới dạng luồng. Dữ liệu có thể có biểu mẫu chẳng hạn như:
- Tệp được lưu trữ trên thiết bị thực
- Luồng từ socket
- Lượng dữ liệu lớn được lưu trữ trong hệ thống cơ sở dữ liệu cơ bản của nhà cung cấp
Trình cung cấp nội dung cho luồng dữ liệu cung cấp quyền truy cập vào dữ liệu bằng đối tượng chỉ số mô tả tệp,
chẳng hạn như
AssetFileDescriptor
,
thay vì đối tượng Cursor
. Ứng dụng dán sẽ đọc luồng dữ liệu bằng cách dùng hàm này
chỉ số mô tả tệp.
Để thiết lập ứng dụng của bạn cho việc sao chép luồng dữ liệu với nhà cung cấp, hãy làm theo các bước sau:
-
Thiết lập URI nội dung cho luồng dữ liệu mà bạn đang đặt vào bảng nhớ tạm. Các phương án để thực hiện việc này bao gồm:
- Mã hoá giá trị nhận dạng cho luồng dữ liệu vào URI, như mô tả trong Mã hoá giá trị nhận dạng trên phần URI rồi duy trì trong nhà cung cấp của bạn, trong đó có chứa giá trị nhận dạng và tên luồng tương ứng.
- Mã hoá tên luồng trực tiếp trên URI.
- Sử dụng một URI độc nhất luôn trả về luồng hiện tại từ nhà cung cấp. Nếu bạn hãy sử dụng tuỳ chọn này, hãy nhớ cập nhật nhà cung cấp của bạn để trỏ đến một luồng khác bất cứ khi nào bạn sao chép luồng vào bảng nhớ tạm bằng URI.
- Cung cấp một loại MIME cho mỗi loại luồng dữ liệu mà bạn định cung cấp. Ứng dụng dán cần thông tin này để xác định xem họ có thể dán dữ liệu vào bảng nhớ tạm hay không.
- Triển khai một trong các phương thức
ContentProvider
trả về chỉ số mô tả tệp cho một luồng. Nếu bạn mã hoá giá trị nhận dạng trên URI nội dung, hãy sử dụng phương pháp này để xác định để mở luồng. - Để sao chép luồng dữ liệu vào bảng nhớ tạm, hãy tạo URI nội dung và đặt URI này vào bảng nhớ tạm.
Để dán luồng dữ liệu, ứng dụng sẽ lấy đối tượng cắt từ bảng nhớ tạm, lấy URI và sử dụng
trong lệnh gọi đến phương thức chỉ số mô tả tệp ContentResolver
để mở luồng. Chiến lược phát hành đĩa đơn
Phương thức ContentResolver
gọi phương thức ContentProvider
tương ứng,
truyền cho đó URI nội dung. Nhà cung cấp của bạn trả về chỉ số mô tả tệp cho
ContentResolver
. Sau đó, ứng dụng dán có trách nhiệm đọc
khỏi luồng.
Danh sách sau đây liệt kê các phương thức chỉ số mô tả tệp quan trọng nhất đối với nhà cung cấp nội dung. Một
trong số này có phương thức ContentResolver
tương ứng với chuỗi
"Mô tả" được thêm vào tên phương thức. Ví dụ: ContentResolver
giá trị tương tự của
openAssetFile()
là
openAssetFileDescriptor()
.
-
openTypedAssetFile()
-
Phương thức này trả về chỉ số mô tả tệp tài sản, nhưng chỉ khi loại MIME đã cung cấp là được nhà cung cấp hỗ trợ. Phương thức gọi (ứng dụng thực hiện thao tác dán) cung cấp mẫu loại MIME. Trình cung cấp nội dung của ứng dụng sao chép URI vào bảng nhớ tạm trả về tên người dùng tệp
AssetFileDescriptor
nếu có thể cung cấp thông tin đó MIME và gửi một ngoại lệ nếu không thể.Phương thức này xử lý các mục con trong tệp. Bạn có thể sử dụng báo cáo này để đọc những nội dung mà nhà cung cấp nội dung đã sao chép vào bảng nhớ tạm.
-
openAssetFile()
-
Phương thức này là một phương thức chung của
openTypedAssetFile()
. Không lọc cho các loại MIME được cho phép, nhưng có thể đọc các phần phụ của tệp. -
openFile()
-
Đây là hình thức chung hơn của
openAssetFile()
. Trợ lý không thể đọc các mục phụ của tệp.
Bạn có thể tuỳ ý sử dụng
openPipeHelper()
bằng phương thức chỉ số mô tả tệp của mình. Thao tác này cho phép ứng dụng dán đọc dữ liệu luồng trong một
luồng trong nền bằng cách sử dụng một đường ống. Để sử dụng phương thức này, hãy triển khai
ContentProvider.PipeDataWriter
.
Thiết kế chức năng sao chép và dán hiệu quả
Để thiết kế chức năng sao chép và dán hiệu quả cho ứng dụng của bạn, hãy nhớ những điểm sau:
- Tại bất kỳ thời điểm nào, bảng nhớ tạm chỉ chứa một đối tượng clip duy nhất. Một thao tác sao chép mới của bất kỳ trong hệ thống sẽ ghi đè đoạn video trước đó. Vì người dùng có thể điều hướng ra khỏi ứng dụng và sao chép trước khi quay lại, bạn không thể giả định bảng nhớ tạm chứa đoạn video mà người dùng đã sao chép trước đó trong ứng dụng của bạn.
-
Mục đích của nhiều đối tượng
ClipData.Item
trong mỗi đoạn video là để hỗ trợ sao chép và dán nhiều lựa chọn thay vì các dạng khác nhau tham chiếu đến một lựa chọn duy nhất. Bạn thường muốn có tất cảClipData.Item
các đối tượng trong một đoạn mã (clip) có cùng dạng thức. Tức là tất cả các quảng cáo đều phải là văn bản, nội dung đơn giản URI hoặcIntent
và không kết hợp. -
Khi cung cấp dữ liệu, bạn có thể biểu diễn các loại MIME khác nhau. Thêm loại MIME
mà bạn hỗ trợ cho
ClipDescription
, sau đó triển khai các loại MIME trong nhà cung cấp nội dung của bạn. -
Khi bạn nhận được dữ liệu từ bảng nhớ tạm, ứng dụng của bạn chịu trách nhiệm kiểm tra loại MIME có sẵn rồi quyết định sử dụng loại MIME nào (nếu có). Ngay cả khi có
trên bảng nhớ tạm và người dùng yêu cầu dán, thì bạn không cần dùng ứng dụng của mình
để dán. Thực hiện thao tác dán nếu loại MIME đó tương thích. Bạn có thể ép buộc dữ liệu
trên bảng nhớ tạm để nhắn tin bằng
coerceToText()
. Nếu ứng dụng của bạn hỗ trợ nhiều loại MIME hiện có, bạn có thể cho phép người dùng chọn một loại MIME để sử dụng.