当前位置: 首页 > news >正文

Android开发中的复制和粘贴

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 对象,其中包含数据的说明和数据本身。剪贴板一次只保留一个 ClipDataClipData 包含一个 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 对象的标签设置为 labelClipDescription 中的单一 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 对象包含一个 IntentClipDescription 对象的标签设置为 label。MIME 类型设置为 MIMETYPE_TEXT_INTENT

使用 newIntent() 可基于 Intent 对象创建剪辑。

将剪贴板数据强制转换为文本

即使应用仅处理文本,也可以从剪贴板复制非文本数据,只需使用 ClipData.Item.coerceToText() 方法对其进行转换即可。

此方法可将 ClipData.Item 中的数据转换为文本并返回一个 CharSequenceClipData.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 对象。以下过程对此进行了详细介绍:

  1. 如果要使用内容 URI 复制数据,请设置一个 content provider。
  2. 获取系统剪贴板:
    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}
    }
  3. 将数据复制到新的 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)
  4. 将新的剪贴对象放到剪贴板中:
    // 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() 将其文本复制到自己的存储空间,如以下过程所述:

  1. 使用 getSystemService(CLIPBOARD_SERVICE) 获取全局 ClipboardManager 对象。此外,声明一个全局变量以包含粘贴的文本:
    var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    var pasteData: String = ""
  2. 确定是否需要在当前 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}
    }
  3. 从剪贴板复制数据。只有在“粘贴”菜单项处于启用状态时,才能在代码中执行到这一步,因此可以假设剪贴板包含纯文本。目前还不知道它是否包含文本字符串或指向纯文本的 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 类型。

  1. 声明一个全局变量以包含 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"
  2. 获取全局剪贴板。另外,获取一个内容解析器,以便可以访问 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
  3. 从剪贴板中获取主要剪切,并获取其内容作为 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
  4. 通过调用 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)
  5. 测试 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 类型,并从提供程序复制数据。如果已拥有这些方法,它们不会干扰复制操作。

以下代码段演示了如何设置应用以复制复杂的数据:

  1. 在应用的全局常量中,声明一个基本 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"
  2. 在用户从中复制数据的 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)}
    }
  3. 在 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() {...
    }
  4. 设置 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.}}...
    }
  5. 设置 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 对象)提供对其数据的访问权限。粘贴应用使用此文件描述符读取数据流。

如需设置应用以使用提供程序复制数据流,请按以下步骤操作:

  1. 为要放到剪贴板中的数据流设置内容 URI。可以使用以下选项执行此操作:
    • 将数据流的标识符编码到 URI 中(如在 URI 中对标识符进行编码部分中所述),然后在提供程序中维护一个包含标识符及相应流名称的表。
    • 直接在 URI 中对流名称进行编码。
    • 使用一个始终会从提供程序返回当前流的唯一 URI。如果使用此选项,那么每当使用 URI 将流复制到剪贴板时,请记得将提供程序更新为指向其他流。
  2. 为计划提供的每种数据流类型提供 MIME 类型。粘贴应用需要此信息来确定它们是否可以将数据粘贴到剪贴板中。
  3. 实现一个会返回流的文件描述符的 ContentProvider 方法。如果在内容 URI 中对标识符进行编码,可以使用此方法确定要打开的流。
  4. 如需将数据流复制到剪贴板,请构建内容 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 类型,可以允许用户选择要使用哪一种。

相关文章:

Android开发中的复制和粘贴

Android 提供了一个强大的基于剪贴板的框架&#xff0c;用于复制和粘贴。它支持简单和复杂的数据类型&#xff0c;包括文本字符串、复杂数据结构、文本和二进制流数据&#xff0c;以及应用资源。简单的文本数据直接存储在剪贴板中&#xff0c;而复杂的数据则存储为引用&#xf…...

使用 inobounce 解决 iOS 皮筋效果导致的无法下拉刷新

使用 inobounce 解决 iOS 皮筋效果导致的无法下拉刷新 在移动端 H5 页面开发中&#xff0c;iOS 设备的“皮筋效果”&#xff08;Rubber Band Effect&#xff09;是一个常见的挑战。当用户在页面顶部下拉或底部上拉时&#xff0c;iOS 会触发整个页面的回弹效果&#xff0c;这不…...

特征选择与类不平衡处理

特征选择与类不平衡处理技术 一、特征选择方法 1. 过滤法&#xff08;Filter Methods&#xff09; 原理&#xff1a; 基于统计学方法或特征本身的分布特性独立于模型进行特征筛选&#xff0c;通过计算特征与目标变量的相关性或特征的发散性进行排序选择。 典型方法&#xf…...

24、ASP.NET⻚⾯之间传递值的⼏种⽅式

1. QueryString&#xff08;查询字符串&#xff09; 描述&#xff1a;通过 URL 参数传递数据&#xff0c;例如 Page2.aspx?id123。 适用场景&#xff1a;简单、非敏感数据&#xff0c;页面跳转时使用。 2. Session&#xff08;会话&#xff09; 描述&#xff1a;在服务器端…...

【扩展卡尔曼滤波器实际运用案例】

扩展卡尔曼滤波器 算法描述实际案例 算法描述 考虑离散时间非线性动态系统 { x k 1 f k ( x k , w k ) z k h k ( x k , v k ) \left\{\begin{matrix} x_{k1}f_{k}(x_k,w_k)\\ z_{k}h_{k}(x_k,v_k) \end{matrix}\right. {xk1​fk​(xk​,wk​)zk​hk​(xk​,vk​)​ 其中是…...

Centos9 安装 nginx 及配置

1. 安装nginx 安装依赖软件&#xff0c;安装之前可以看一下是否已经安装过以下软件&#xff0c;dnf list installed | grep zlib dnf install gcc-c dnf install zlib dnf install pcre pcre-devel dnf install openssl openssl-devel下载nginx&#xff0c;这里是下载到opt文…...

总结设计测试用例的万能公式

现在有⼀款产品&#xff0c;要求我们对“⻔锁”设计测试⽤例&#xff0c;假如你是测试⼈员&#xff0c;你会怎么设计呢&#xff1f; 1 常规思考逆向思维发散性思维 设计测试⽤例的原则⼆&#xff1a; 1.测试⽤例的编写不仅应当根据有效和预料到的输⼊情况&#xff0c;⽽且也…...

Android RK356X TVSettings USB调试开关

Android RK356X TVSettings USB调试开关 平台概述操作-打开USB调试实现源码补充说明 平台 RK3568 Android 11 概述 RK3568 是瑞芯微&#xff08;Rockchip&#xff09;推出的一款高性能处理器&#xff0c;支持 USB OTG&#xff08;On-The-Go&#xff09;和 USB Host 功能。US…...

python生成动态库在c++中调用

一.Windows下生成动态库.pyd 在setup.py的同目录下使用python setup.py build_ext --inplace 二.在vscode的c中使用.pyd文件&#xff08;动态库&#xff09; 1&#xff09;配置python的环境 python -c "import sys; print(sys.executable)" #确定python安装位置 2…...

大模型数据味蕾论

大模型数据味蕾论 大模型的成长路径&#xff1a;从婴儿到专家预训练数据的"四维口味"模型从文本到模型&#xff1a;数据处理的关键步骤"大模型数据味蕾论"结语 AI大模型就像一位厨师&#xff0c;预训练数据就是这位厨师的味蕾。 没有经过训练的味蕾&#x…...

网络编程4

day4 一、Modbus 1.分类 (1).Modbus RTU: 运行在串口上的协议&#xff0c;采用二进制表现形式以及紧凑型数据结构&#xff0c;通信效率高&#xff0c;应用广泛。(2).Modbus ASCII: 运行在串口上的协议&#xff0c;采用ASCII码传输&#xff0c;并且利用特殊字符作为其字节的开始…...

neo4j-community-3.5.5-unix.tar.gz安装

从官网找了下包&#xff0c;哎&#xff0c;奈何访问不了github,那就找镜像吧&#xff0c;哎&#xff0c;也是不通。 # docker search neo4j Error response from daemon: Get "https://index.docker.io/v1/search?qneo4j&n25": dial tcp 202.160.128.40:443: i…...

高防IP能抵御哪些类型的网络攻击?

高防IP&#xff08;High Defense IP&#xff09;是一种专门针对网络攻击设计的防护服务&#xff0c;主要通过流量清洗、协议分析、行为检测等技术抵御多种网络攻击。以下是其能防御的主要攻击类型及原理&#xff1a; ​​一、常见防御的攻击类型​​ ​​DDoS攻击&#xff08;分…...

动态监控进程

1.介绍: top和ps命令很相似,它们都是用来显示正在执行的进程,top和ps最大的不同之处,在于top在执行中可以更新正在执行的进程. 2.基本语法&#xff1a; top [选项] 选项说明 ⭐️僵死进程&#xff1a;内存没有释放,但是进程已经停止工作了,需要及时清理 交互操作说明 应用案…...

你学会了些什么220622?--搭建UI自动化

jenkins访问地址&#xff1a;http://192.168.82.129:8080/ 账号密码&#xff1a;admin/a123456a ***** 什么是UI自动化** 使用工具或者脚本对需要测试的软件的前端界面在预设的条件下&#xff0c;在已有的测试数据下运行系统或者应用程序&#xff0c;并获取其前端页面UI显示的…...

深入理解自监督学习(Self-Supervised Learning):理论与实践

&#x1f4cc; 友情提示&#xff1a; 本文内容由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;创作平台的gpt-4o-mini模型生成&#xff0c;旨在提供技术参考与灵感启发。文中观点或代码示例需结合实际情况验证&#xff0c;建议读者通过官方文档或实践进一步确认其准…...

时序逻辑入门指南:LTL、CTL与PTL的概念介绍与应用场景

引言 在计算机科学和形式化方法中,**时序逻辑(Temporal Logic)**是描述系统动态行为的核心工具,它允许我们形式化地表达“时间”相关的性质,例如“某事件最终会发生”或“系统始终满足安全条件”。其中,LTL(线性时序逻辑)、**CTL(计算树逻辑)和PTL(命题时序逻辑)*…...

Spring Boot 整合 JavaFX 核心知识点详解

1. 架构设计与集成模式 1.1 Spring Boot 与 JavaFX 的分层架构设计 Spring Boot 与 JavaFX 的整合需要精心设计的分层架构,以充分利用两个框架的优势。 标准分层架构 ┌────────────────────────────────────────────────…...

进程与线程:02 多进程图像

多进程图像的起源与核心地位 上节课我们开启了对操作系统核心概念——多进程图像的学习&#xff0c;探讨了其产生的原因。操作系统的核心职责之一是管理CPU&#xff0c;CPU作为实现取指执行的硬件自动化部件&#xff0c;只有执行取指操作&#xff08;即取出并执行程序指令 &am…...

基于SIMMECHANICS的单自由度磁悬浮隔振器PID控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 单自由度磁悬浮减振器工作原理简介 4.2 SIMMECHANICS工具箱 5.完整工程文件 1.课题概述 基于SIMMECHANICS的单自由度磁悬浮隔振器PID控制系统simulink建模与仿真。其中&#xff0c;SIMMECHANICS是M…...

FreeRTOS互斥信号量解决优先级翻转实战教程

FreeRTOS互斥信号量解决优先级翻转实战教程 大家好&#xff01;今天我们来深入探讨FreeRTOS中的优先级翻转问题&#xff0c;并通过互斥信号量来解决这个问题。上一篇文章我们已经了解了优先级翻转的现象&#xff0c;今天我们将动手实践&#xff0c;通过代码对比来直观感受互斥…...

Spark-SQL 四(实验)

用idea实验hive的常用代码 将数据放到项目的目录下 代码实现 运行结果&#xff1a; 实验 统计有效数据条数及用户数量最多的前二十个地址 将数据放到Spark-SQL/input目录下 代码实现&#xff1a; 运行结果&#xff1a;...

前端技术未来的发展趋势分析

以下是关于前端技术未来发展趋势的深度分析&#xff0c;结合行业动态和技术演进方向&#xff0c;从多个维度展开&#xff1a; 一、核心发展趋势 1. 框架融合与性能极致化 趋势&#xff1a;React/Vue/Solid 等框架在编译时优化&#xff08;如React Forget编译器&#xff09;和…...

字节扣子空间开启内测!附免费邀请码!

3月初&#xff0c;当Manus作为首个通用智能体横空出世时&#xff0c;整个科技圈都沸腾了。 当时我就预言过&#xff1a;这种创新产品要真正普及&#xff0c;还得看大厂动作&#xff08;毕竟创业公司的资源有限啊&#xff09;。 这不&#xff0c;字节跳动最近就悄悄放出了大招—…...

高并发场景下的淘宝 API 开发实践:商品数据实时采集与性能优化

在电商行业竞争激烈的当下&#xff0c;实时获取海量商品数据成为企业把握市场动态、制定精准策略的关键。然而&#xff0c;高并发场景下对淘宝 API 的调用极易引发性能瓶颈与稳定性问题。本文将围绕高并发场景下淘宝 API 开发&#xff0c;深入讲解商品数据实时采集的技术要点&a…...

如何将Qt程序打包成应用程序?

1、使用release模式&#xff0c;编译项目 2、新建一个文件夹&#xff08;不要有中文路径&#xff09;&#xff0c;将刚才编译生成的可执行文件&#xff08;.exe&#xff09;放入新建的文件夹下。 可执行文件通常生成在项目目录下的构建文件夹中&#xff0c;如 build-项目名-套…...

AI重塑网络安全:机遇与威胁并存的“双刃剑”时代

一、引言 人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;正在深刻改变网络安全行业的格局。从ChatGPT生成钓鱼邮件到AI驱动的漏洞挖掘&#xff0c;从零信任架构的普及到安全大模型的实战应用&#xff0c;AI既是攻击者的“新武器”&#xff0c;也是防御者的“新…...

c++基础·列表初始化

目录 一、列表初始化的核心优势 二、基础数据类型与数组初始化 1. 基础类型初始化 2. 数组初始化 三、类与结构体初始化 1. 构造函数匹配规则 2. 注意事项 四、标准容器初始化 五、聚合类型&#xff08;Aggregate Types&#xff09;初始化 1. 聚合类型定义 2. 初始化…...

颠覆传统!毫秒级响应的跨平台文件同步革命,远程访问如本地操作般丝滑

文章目录 前言1. 安装Docker2. Go File使用演示3. 安装cpolar内网穿透4. 配置Go File公网地址5. 配置Go File固定公网地址 前言 在这个信息爆炸的时代&#xff0c;谁不曾遭遇过类似的窘境呢&#xff1f;试想&#xff0c;当你正于办公室中埋首案牍时&#xff0c;手机突然弹出一…...

目标检测中的损失函数(二) | BIoU RIoU α-IoU

BIoU来自发表在2018年CVPR上的文章&#xff1a;《Improving Object Localization With Fitness NMS and Bounded IoU Loss》 论文针对现有目标检测方法只关注“足够好”的定位&#xff0c;而非“最优”的框&#xff0c;提出了一种考虑定位质量的NMS策略和BIoU loss。 这里不赘…...

RS232 串行通信:C++ 实现指南

文章目录 一、RS232 简介1. 电气特性2. 传输速率3. 传输距离 二、在 C 中实现 RS232 通信1. Windows 平台&#xff08;1&#xff09;打开串行端口&#xff08;2&#xff09;配置串行通信参数&#xff08;3&#xff09;发送数据&#xff08;4&#xff09;接收数据&#xff08;5&…...

电控---SWD协议

SWD协议是烧录调试常用的协议&#xff0c;本文对SWD协议进行了&#xff0c;覆盖物理层、协议层、寄存器结构、信号时序、安全特性、实际应用及最新发展趋势的讲解。 一、物理层架构与信号特性 1. 引脚定义与电气规范 核心引脚&#xff1a; SWDIO&#xff08;双向数据线&…...

Linux系统下docker 安装 redis

docker安装最新版的redis 一、docker拉取最新版redis镜像 拉取镜像若没有指定版本&#xff0c;代表拉取最新版本 二、查询redis镜像 三、挂载配置文件 在docker容器内修改redis配置文件不方便&#xff0c;所以挂载配置文件&#xff0c;这样可以在外边修改redis配置 3.1 创建…...

第 2 篇:初探时间序列 - 可视化与基本概念

第 2 篇&#xff1a;初探时间序列 - 可视化与基本概念 (图片来源: Luke Chesser on Unsplash) 在上一篇《你好&#xff0c;时间序列&#xff01;》中&#xff0c;我们了解了什么是时间序列数据以及学习它的重要性。现在&#xff0c;是时候卷起袖子&#xff0c;真正开始接触和探…...

适配器模式:化解接口不兼容的桥梁设计

适配器模式&#xff1a;化解接口不兼容的桥梁设计 一、模式核心&#xff1a;让不兼容的接口协同工作 在软件开发中&#xff0c;经常会遇到接口不兼容的情况&#xff1a;如旧系统提供的接口无法被新系统直接调用&#xff0c;或第三方库的接口与当前系统设计不匹配。适配器模式…...

科大讯飞Q1营收46.6亿同比增长27.7%,扣非净利同比增长48.3%

4月21日盘后&#xff0c;AI龙头科大讯飞&#xff08;002230.SZ&#xff09;发布2024年报&#xff0c;公司全年实现营业收入233.43亿元&#xff0c;同比增长18.79%&#xff0c;同期归母净利润为5.6亿元。 公司核心赛道业务保持快速增长&#xff0c;消费者、教育、汽车、医疗业务…...

基于大模型的血栓性外痔全流程风险预测与治疗管理研究报告

目录 一、引言 1.1 研究背景与目的 1.2 研究意义 二、血栓性外痔概述 2.1 定义与发病机制 2.2 临床表现与诊断方法 2.3 现有治疗手段综述 三、大模型在血栓性外痔预测中的应用原理 3.1 大模型技术简介 3.2 模型构建与训练数据来源 3.3 模型预测血栓性外痔的工作流程…...

Transformer系列(三):编码器—解码器架构

Transformer架构 一、多头自注意力 &#xff08;Multi-head self-attention&#xff09;将矩阵维度从d降到d/k的优点 二、层归一化 (Lary Norm)三、残差连接 (Residual Connections)Add and Norm 四、注意力对数几率缩放 &#xff08;Attention logit scaling&#xff09;五、T…...

C++入门小馆: 深入string类(二)

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…...

MCU开发学习记录10 - 高级定时器学习与实践(HAL库)—PWM互补输出、死区控制、刹车控制 - STM32CubeMX

本文将介绍高级定时器的概念&#xff08;只讲解高级定时器特有功能&#xff09;、相关函数以及STM32CubeMX生成定时器的配置函数以及对生成定时器的配置函数进行分析&#xff08;包括结构体配置、相关寄存器配置&#xff09;。针对于高级定时器实践&#xff1a;实现输出PWM互补…...

pytest基础-new

规范 1、首先创建 py 文件命名以 test_ 开始或者以 _test 结尾 2、若是新建类&#xff0c;测试类需要以 Test_开头 3、测试用例&#xff08;方法&#xff09;需要以 test_开头 assert 断言 assert xx&#xff1a;判断 xx 为真 assert not xx&#xff1a;判断 xx 不为真 asse…...

利用Qt创建一个模拟问答系统

界面&#xff1a; 添加了聊天显示区域&#xff08;QTextEdit&#xff09; 添加了发送按钮和清空对话按钮 优化了布局和窗口大小添加了时间戳显示 2、功能&#xff1a; 支持实时对话可以清空对话历史 支持按回车发送消息 添加了简单的关键词匹配响应系统 交互体验&#x…...

CCF CSP 第37次(2025.03)(1_数值积分_C++)

CCF CSP 第37次&#xff08;2025.03&#xff09;&#xff08;1_数值积分_C&#xff09; 解题思路&#xff1a;思路一&#xff1a; 代码实现代码实现&#xff08;思路一&#xff09;&#xff1a; 时间限制&#xff1a; 1.0 秒 空间限制&#xff1a; 512 MiB 原题链接 解题思路…...

使用 Flutter 遇坑小计

前言 首先, 谷哥很贴心地为国内用户准备了一份使用手册 不过很遗憾 就算你照着它的手册来了, 还是会在后续使用中遇到其它的坑 今天我踩了, 保不齐明天就是其他人(lol) running gradle task ‘assembledebug’ stuck 首先去确定下当下Android Studio(或者说你目前的Flutter项…...

C语言中的双链表和单链表详细解释与实现

C语言中的双链表详细解释与实现 双链表&#xff08;Doubly Linked List&#xff09;是一种常见的数据结构&#xff0c;它比单链表更灵活&#xff0c;因为每个节点都包含指向前驱和后继节点的指针。 双链表的基本结构 节点定义 c 复制 下载 typedef struct Node {int dat…...

CSS零基础入门笔记:狂神版

前言 本笔记是学习狂神的java教程&#xff0c;建议配合视频&#xff0c;学习体验更佳。 【狂神说Java】HTML5完整教学通俗易懂_哔哩哔哩_bilibili 第1-2章&#xff1a;Java零基础入门笔记&#xff1a;(1-2)入门&#xff08;简介、基础知识&#xff09;-CSDN博客 第3章&…...

使用python调用deepseek 多轮对话,详细讲解

以下代码实现在python中与deepseek实现多轮对话。 Messages参数是对话历史和上下文的核心载体。 数据结构&#xff1a; • 必须是包含消息对象的数组 • 每个消息对象必须包含&#xff1a; o role&#xff1a;发言者身份&#xff08;system/user/assistant&#xff09; o con…...

requestAnimationFrame是什么?【前端】

亲爱的读者&#xff0c;希望今天的你好心情~ 目录 requestAnimationFrame是什么&#xff1f;目的&#xff1f;举个栗子&#xff1a; requestAnimationFrame是什么&#xff1f; requestAnimationFrame 是一种用于优化动画性能的 JavaScript API。它允许你在浏览器的下一次重绘之…...

SQL Server基础

二. SQL Server数据库 1. 数据库简介 数据库本质是写成磁盘文件&#xff0c;在硬盘中存储用处 存储大量数据&#xff0c;方便检索和访问保持数据信息的一致、完整共享和安全通过组合分析&#xff0c;产生新的有用信息 数据库发展史 萌芽阶段&#xff1a;文件系统&#xff0c;…...

Vue-组件的懒加载,按需加载

在Vue项目中实现组件的懒加载&#xff08;也称为按需加载或代码分割&#xff09;&#xff0c;可以大大提升应用的加载速度和性能。懒加载主要通过Webpack的代码分割功能实现&#xff0c;特别是使用动态导入&#xff08;import()语法&#xff09;。 为什么要使用懒加载&#xf…...