Android——ClipBoard(复制和粘贴)

1. ClipBoard 简介

ClipBoard框架,会将数据放入一个剪贴对象中,然后将该剪贴对象放到系统级剪贴板中。剪贴对象可以采用以下三种形式之一:

  • Text:
    文本字符串,您可以直接将字符串放入剪贴对象中,然后将剪贴对象放到剪贴板中。如需粘贴字符串,您需要从剪贴板获取剪贴对象,然后将字符串复制到应用的存储空间。
  • URI:
    一Uri 对象,表示任何形式的 URI。它主要适用于从 Content Provider 复制复杂的数据。如需复制数据,您需要将一个 Uri 对象放入一个剪贴对象中,并将该剪贴对象放到剪贴板中。如需粘贴数据,您需要获取该剪贴对象,获取 Uri 对象,将其解析为数据源(例如某个 Content Provider),然后将数据从源中复制到应用的存储空间。
  • Intent:
    Intent,它支持复制应用快捷方式。如需复制数据,您需要创建一个 Intent,将其放入一个剪贴对象中,然后将该剪贴对象放到剪贴板中。如需粘贴数据,您需要获取该剪贴对象,然后将 Intent 对象复制到您应用的内存区域。

ClipBoard一次只保留一个剪贴对象。当应用将一个剪贴对象放到剪贴板时,上一个剪贴对象会消失。

此外,您可能还希望无论剪贴板中的数据采用何种形式,用户都可以粘贴文本。为此,您可以将剪贴板数据强制转换为文本表示形式,然后粘贴相应文本

《Android——ClipBoard(复制和粘贴)》

  • 如需复制数据,应用需要将一个 ClipData 对象放到 ClipboardManager 全局剪贴板中。
    ClipData 包含一个或多个 ClipData.Item 对象和一个 ClipDescription 对象。
  • 如需粘贴数据,应用需要获取 ClipData,从 ClipDescription 获取其 MIME 类型,然后从 ClipData.Item 或 ClipData.Item 引用的 Content Provider 获取数据。

1.1 ClipBoard类

ClipboardManager:

  • 在 Android 系统中,系统剪贴板由全局 ClipboardManager 类表示。您不能直接实例化此类;而是应通过调用 getSystemService(CLIPBOARD_SERVICE) 获取对它的引用

ClipData、ClipData.Item 和 ClipDescription:

  • ClipData :如需将数据添加到剪贴板,您需要创建一个 ClipData 对象,其中包含数据的说明和数据本身。剪贴板一次只保留一个 ClipData。ClipData 包含一个 ClipDescription 对象以及一个或多个 ClipData.Item 对象。
  • ClipDescription: 对象包含关于剪切的元数据。具体来说,它包含剪切的数据的可用 MIME 类型数组。当您将一个剪切放到剪贴板中时,系统会向粘贴应用提供此数组。粘贴应用可以检查该数组,以解自己能否处理任何可用 MIME 类型。
  • ClipData.Item: 对象包含 Text、URI 或 Intent 数据:

可以向一个剪切添加多个 ClipData.Item 对象。这让用户可以将多个选择复制和粘贴为一个剪切。例如,如果您有一个列表微件,可让用户一次选择多个项,则您可以同时将所有这些项复制到剪贴板。为此,您需要分别为每个列表项创建一个 ClipData.Item,然后将 ClipData.Item 对象添加到 ClipData 对象。

1.2 ClipBoard方法

ClipData 类提供静态便捷方法,用于创建具有单个 ClipData.Item 对象和一个简单 ClipDescription 对象的 ClipData 对象

  • newPlainText(label, text):
    • 返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象包含一个文本字符串。
    • ClipDescription 对象的标签设置为 label。
    • ClipDescription 中的单一 MIME 类型为 MIMETYPE_TEXT_PLAIN。
    • 使用 newPlainText() 可从文本字符串创建剪切。
  • newUri(resolver, label, URI)
    • 返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象包含一个 URI。ClipDescription 对象的标签设置为 label。
    • 如果该 URI 是内容 URI(Uri.getScheme() 返回 content:),则此方法会使用 resolver 中提供的 ContentResolver 对象从 Content Provider 检索可用的 MIME 类型,并将其存储在 ClipDescription 中。对于不是 content: URI 的 URI,此方法会将 MIME 类型设置为 MIMETYPE_TEXT_URILIST。
    • 使用 newUri() 可从 URI(尤其是 content: URI)创建剪切。
  • newIntent(label, intent)
    • 返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象包含一个 Intent。
    • ClipDescription 对象的标签设置为 label。MIME 类型设置为 MIMETYPE_TEXT_INTENT。
    • 使用 newIntent() 可从 Intent 对象创建剪切。

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

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

此方法可将 ClipData.Item 中的数据转换为文本并返回一个 CharSequence。ClipData.Item.coerceToText() 返回的值基于 ClipData.Item 中的数据的形式:

  • Text:
    如果 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) 返回的表示形式相同。

1.4 复制到剪贴板

如需将数据复制到剪贴板,您需要获取全局 ClipboardManager 对象的句柄,创建一个 ClipData 对象,向其中添加一个 ClipDescription 和一个或多个 ClipData.Item 对象,然后将已完成的 ClipData 对象添加到 ClipboardManager 对象。以下过程对此进行了详细介绍:

  • (1)如果您要使用内容 URI 复制数据,请设置一个 Content Provider。
  • (2)获取系统剪贴板:
// 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 对象:
对于 Text// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");


对于URI:
以下代码段通过将记录 ID 编码到提供程序的内容 URI 来构建 URI。在 URI 中对标识符进行编码部分对此方法进行了更详细的说明
// Creates a Uri based on 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 that you use 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:
以下代码段为应用构建一个 Intent,然后将其放入剪贴对象中:
// 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)将新的剪贴对象放到剪贴板中:
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);

1.5 从剪贴板粘贴

可以通过以下方法从剪贴板中粘贴数据:获取全局剪贴板对象,获取剪贴对象,查看其数据,然后将数据从剪贴对象复制到您自己的存储空间(如果可以)。

1.5.1 粘贴纯文本

如需粘贴纯文本,首先请获取全局剪贴板并验证它能否返回纯文本。然后获取剪贴对象,并使用 getText() 将其文本复制到您自己的存储空间,如以下过程所述:

  • (1)使用 getSystemService(CLIPBOARD_SERVICE) 获取全局 ClipboardManager 对象。另外,声明一个全局变量以包含粘贴的文本:
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData = "";
  • (2)接下来,确定您是否应在当前 Activity 中启用或停用“粘贴”选项。您应验证剪贴板是否包含剪切,以及您是否可以处理剪切所代表的数据类型:
// 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 if you can handle the data.
if (!(clipboard.hasPrimaryClip())) { 

    pasteItem.setEnabled(false);

} else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) { 

    // This disables the paste menu item, since the clipboard has data but it is not plain text
    pasteItem.setEnabled(false);
} else { 

    // This enables the paste menu item, since the clipboard contains plain text.
    pasteItem.setEnabled(true);
}
  • (3)从剪贴板复制数据。只有在“粘贴”菜单项处于启用状态时,才能在项目中执行到这一步,因此您可以假设剪贴板包含纯文本。您目前还不知道它是否包含文本字符串或指向纯文本的 URI。以下代码段对此进行了测试,但它仅显示用于处理纯文本的代码:
// 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 does not 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 is not
        // presented here.
        pasteData = resolveUri(Uri);
        return true;
    } else { 

        // Something is wrong. The MIME type was plain text, but the clipboard does not contain either
        // text or a Uri. Report an error.
        Log.e(TAG, "Clipboard contains an invalid data type");
        return false;
    }
}

1.5.2 从内容 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
public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
  • (2)获取全局剪贴板。另外,获取一个内容解析器,以便您可以访问 Content Provider:
// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Gets a content resolver instance
ContentResolver cr = getContentResolver();
  • (3)从剪贴板中获取主要剪切,并获取其内容作为 URI:
// 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 是否为内容 URI。如果 Uri 不指向有效的 Content Provider,则此方法会返回 null:
    // If the clipboard contains a URI reference
    if (pasteUri != null) { 

        // Is this a content URI?
        String uriMimeType = cr.getType(pasteUri);
  • (5)测试 Content Provider 是否支持当前应用可以理解的 MIME 类型。如果支持,请调用 ContentResolver.query() 以获取数据。返回值是 Cursor:
        // If the return value is not 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 will vary according to the
                    // format of the data model.
                    }
                }

                // close the Cursor
                pasteCursor.close();
             }
         }
     }
}

1.5.3 粘贴 Intent

如需粘贴 Intent,首先请获取全局剪贴板。检查 ClipData.Item 对象以了解它是否包含 Intent。然后调用 getIntent(),以将相应 Intent 复制到您自己的存储空间。以下代码段演示了此过程:

// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Checks to see if the clip item contains an Intent, by testing to see if 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 your application was expecting an Intent to be
    // on the clipboard
}

1.6 使用 Content Provider 复制复杂的数据

Content Provider 支持复制数据库记录或文件流等复杂的数据。如需复制这类数据,您需要将一个内容 URI 放到剪贴板中。然后,粘贴应用会从剪贴板获取此 URI,并使用它来检索数据库数据或文件流描述符。

由于粘贴应用只有您的数据的内容 URI,因此它需要知道要检索哪一部分数据。您可以通过在 URI 本身中对数据的标识符进行编码来提供此信息,也可以提供一个会返回您要复制的数据的唯一 URI。选择哪种方法取决于数据的组织方式。

1.6.1 在 URI 中对标识符进行编码:

一种将数据复制到包含 URI 的剪贴板的实用方法是,在 URI 本身中对数据的标识符进行编码。然后,您的 Content Provider 便可以从 URI 中获取相应标识符,并使用它来检索数据。粘贴应用无需知道该标识符是否存在;它只需从剪贴板中获取您的“引用”(URI 和标识符),将其提供给 Content Provider,然后获取数据。

通常情况下,您可以通过将标识符连接到内容 URI 的末尾,将标识符编码到内容 URI 中。例如,假设您将提供程序 URI 设定为以下字符串:

"content://com.example.contacts"

如果您希望将某个名称编码到此 URI,则应使用以下代码段:

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);

如果您已经在使用 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。为响应查询,您的提供程序会返回它当前包含的数据。

1.6.2 复制数据结构

介绍了如何从剪贴板获取内容 URI 并使用它来获取和粘贴数据。

您应设置一个用于复制和粘贴复杂数据的 Content Provider 作为 ContentProvider 组件的子类。您还应对放到剪贴板中的 URI 进行编码,使其指向您要提供的确切记录。此外,您还必须考虑应用的现有状态:

  • 如果您已有 Content Provider,则可以添加其功能。您可能只需修改其 query() 方法,以处理来自要粘贴数据的应用的 URI。您可能还希望修改该方法以处理“复制”URI 模式。
  • 如果您的应用维护了一个内部数据库,不妨将此数据库迁移到 Content Provider,以便从中复制。
  • 如果您当前没有使用数据库,则可以实现一个简单的 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 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)在用户从中复制数据的 Activity 中,设置用于将数据复制到剪贴板的代码。为响应复制请求,将 URI 放到剪贴板中
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);

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip);
  • (3)在 Content Provider 的全局范围内,创建一个 URI 匹配器并添加一个与您放到剪贴板中的 URI 匹配的 URI 模式:
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 模式(具体取决于您编码它的方式),但系统仅会显示剪贴板复制操作的模式:
// 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:

        // query and return the contact for the requested name. Here you would decode
        // the incoming URI, query the data model based on the last name, and return the result
        // as a Cursor.

    ...

}
  • (5)设置 getType() 方法,以返回复制的数据的相应 MIME 类型:
// Sets up your provider's getType() method.
public String getType(Uri uri) { 
    ...

    switch (sUriMatcher.match(uri)) { 
    case GET_SINGLE_CONTACT:
        return (MIME_TYPE_CONTACT);
    ...
    }
}

1.7 注意:

  • 在任何时候,剪贴板中都只能有一个剪切。任何应用在系统中执行的新的复制操作都会覆盖上一个剪切。由于用户可能会离开您的应用并在执行复制操作后返回,因此您不能假设剪贴板中包含用户之前在您的应用中复制的剪切。
  • 每个剪切有多个 ClipData.Item 对象的预期目的是,支持复制和粘贴多个选择,而不是一个选择的不同引用形式。您通常希望一个剪切中的所有 ClipData.Item 对象都采用相同的形式,也就是说,它们应该都是简单文本、内容 URI 或 Intent,而不是这些形式的混合。
  • 提供数据时,您可以提供不同的 MIME 表示形式。将您支持的 MIME 类型添加到 ClipDescription,然后在Content Provider中实现这些 MIME 类型。
  • 当您从剪贴板获取数据时,您的应用负责检查可用的 MIME 类型,并决定使用哪种类型(如果有)。即使剪贴板中有剪切且用户请求粘贴,您的应用也无需执行粘贴操作。您应该在 MIME 类型兼容时执行粘贴操作。您可以选择使用 coerceToText()(如果已选择)将剪贴板中的数据强制转换成文本。如果您的应用支持多种可用的 MIME 类型,您可以允许用户选择要使用哪一种。

2. 简单例子,相关知识

2.1 Clipboard 简单实例

  • (1)获取剪贴板里的内容
final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData data = cm.getPrimaryClip();
// ClipData 里保存了一个ArryList 的 Item 序列, 可以用 getItemCount() 来获取个数
ClipData.Item item = data.getItemAt(0);
 String text = item.getText().toString();// 注意 item.getText 可能为空
  • (2)设置剪贴板内容变化的监听器
    直接给 ClipboardManager 加一个ClipboardManager.OnPrimaryClipChangedListener 监听器就可以
final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
        cm.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() { 
            @Override
            public void onPrimaryClipChanged() { 
                ClipData data = cm.getPrimaryClip();
                ClipData.Item item = data.getItemAt(0);
                String text = item.getText().toString();
                Log.i(TAG, text);
            }
        });
  • (3)往剪贴板里添加内容
    private void setClipDate() { 
        final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
        ClipData clipData = cm.getPrimaryClip();
        clipData.addItem(new ClipData.Item("hello"));
        cm.setPrimaryClip(clipData);
    }

这样添加的时候,在数据结构里是一个一个 Item 的形式,但是在获取的时候,例如粘贴,则会把所有 Item 拼接一起复制出来

2.2 URI转真实路径

public static String getPathFromUri(final Context context, final Uri uri) { 
        if (uri == null) { 
            return null;
        }
        // Check whether the version is later than Android 4.4
        final boolean after44 = Build.VERSION.SDK_INT >= 19;
        if (after44 && DocumentsContract.isDocumentUri(context, uri)) { 
            // If it is later than Android 4.4 and belongs to the file URI
            final String authority = uri.getAuthority();
            // Check whether Authority is used by local files
            if ("com.android.externalstorage.documents".equals(authority)) { 
                // External storage space
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] divide = docId.split(":");
                final String type = divide[0];
                if ("primary".equals(type)) { 
                    String path = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/").concat(divide[1]);
                    return path;
                } else { 
                    String path = "/storage/".concat(type).concat("/").concat(divide[1]);
                    return path;
                }
            } else if ("com.android.providers.downloads.documents".equals(authority)) { 
                // Download directory
                final String docId = DocumentsContract.getDocumentId(uri);
                if (docId.startsWith("raw:")) { 
                    final String path = docId.replaceFirst("raw:", "");
                    return path;
                }
                final Uri downloadUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(docId));
                String path = queryAbsolutePath(context, downloadUri);
                return path;
            } else if ("com.android.providers.media.documents".equals(authority)) { 
                //image,video,audio
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] divide = docId.split(":");
                final String type = divide[0];
                Uri mediaUri = null;
                if ("image".equals(type)) { 
                    mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) { 
                    mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) { 
                    mediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                } else { 
                    return null;
                }
                mediaUri = ContentUris.withAppendedId(mediaUri, Long.parseLong(divide[1]));
                String path = queryAbsolutePath(context, mediaUri);
                return path;
            }
        } else { 
            // generic URI
            final String scheme = uri.getScheme();
            String path = null;
            if ("content".equals(scheme)) { 
                path = queryAbsolutePath(context, uri);
            } else if ("file".equals(scheme)) { 
                path = uri.getPath();
            }
            return path;
        }
        return null;
    }

    public static String queryAbsolutePath(final Context context, final Uri uri) { 
        final String[] projection = { MediaStore.MediaColumns.DATA};
        Cursor cursor = null;
        try { 
            cursor = context.getContentResolver().query(uri, projection, null, null, null);
            if (cursor != null && cursor.moveToFirst()) { 
                final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
                return cursor.getString(index);
            }
        } catch (final Exception ex) { 
            ex.printStackTrace();
            if (cursor != null) { 
                cursor.close();
            }
        }
        return null;
    }

可能某些机型会存在download返回的path始终为null。这是因为不同机型下载目录不一致导致的:

“content://downloads/public_downloads”,
“content://downloads/my_downloads”,
“content://downloads/all_downloads”

2.3 真实路径转URI

public static Uri getUriFromPath(Context context, String path) { 
        Uri mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        Cursor cursor = context.getContentResolver().query(mediaUri,
                null,
                MediaStore.Images.Media.DISPLAY_NAME + "= ?",
                new String[] { path.substring(path.lastIndexOf("/") + 1)},
                null);

        Uri uri = null;
        if(cursor.moveToFirst()) { 
            uri = ContentUris.withAppendedId(mediaUri,
                    cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
        }
        cursor.close();
        return uri;

    }
    原文作者:Yawn__
    原文地址: https://blog.csdn.net/ly0724ok/article/details/121401552
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞