Android - 打开、分享文件的实现

本文详细介绍了在Android中如何实现打开和分享文件的功能。通过在AndroidManifest.xml中配置<intent-filter>,<action>,<category>和<data>标签,使App能够接受并处理不同类型和来源的文件。内容涵盖了如何声明Activity处理图片和文本文件,以及如何处理不同API级别下的Intent。同时,文章还提到了华为Mate 10和三星设备上可能遇到的不同URI前缀问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基础知识

我们需要让自己的 App 能打开文件或者接受其它 App 分享出来的文件,实际上就是 App 隐式调用的一种应用,需要使用 <intent-filter>、<data>、<action> 等标签,在 AndroidManifest.xml 中定义我们的 App 可以进行的操作,以及类型限制。

<intent-filter> 标签

官方介绍:

Specifies the types of intents that an activity, service, or broadcast receiver can respond to. An intent filter declares the capabilities of its parent component — what an activity or service can do and what types of broadcasts a receiver can handle. It opens the component to receiving intents of the advertised type, while filtering out those that are not meaningful for the component.

Most of the contents of the filter are described by its <action>, <category>, and <data> subelements.

<intent-filter> 标签,是用来过滤我们 App 可以进行的活动和接受的广播。里面可以声明作用范围(使用 MimeType )以及 URL 地址(使用 scheme)等。

可以理解为我们对于当前 Activity 可以接受的 Intent 的过滤器,里面的标签限制了我们能执行的动作,如 <action> 就是我们在创建 Intent 时,setAction() 设置进去的动作。

<action> 标签

官方介绍:

Adds an action to an intent filter. An <intent-filter> element must contain one or more <action> elements. If there are no <action> elements in an intent filter, the filter doesn’t accept any Intent objects. See Intents and Intent Filters for details on intent filters and the role of action specifications within a filter.

<action> 标签,是用来指定 App 可以执行的 Intent 的操作类型。比如 android.intent.action.ACTION_SEND 表示当前 Activity 可以接受“分享”的操作。

一个 <intent-filter> 标签下,至少应该包含一个 <action> 标签,并且如果 <action> 标签的内容和其它的标签冲突,比如 ACTION_SEND 和 这个标签的内容就是冲突的,最终会导致这个 <action> 的定义失效。

我们应该尽量不要在 <intent-filter> 中定义多个 <action>,避免相互冲突。

<category> 标签

官方介绍:

Adds a category name to an intent filter. See Intents and Intent Filters for details on intent filters and the role of category specifications within a filter.

<category> 标签,用来声明接受的 Intent 的类型。

为了接收隐式 Intent,必须将 CATEGORY_DEFAULT 类别包括在 Intent 过滤器中。 方法 startActivity() 和 startActivityForResult() 将按照已申明 CATEGORY_DEFAULT 类别的方式处理所有 Intent。 如果未在 Intent 过滤器中声明此类别,则隐式 Intent 不会解析为您的 Activity。

<data> 标签

<data> 标签可以包含的内容:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />

官方介绍:

Adds a data specification to an intent filter. The specification can be just a data type (the mimeType attribute), just a URI, or both a data type and a URI. A URI is specified by separate attributes for each of its parts:

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

These attributes that specify the URL format are optional, but also mutually dependent:

  • If a scheme is not specified for the intent filter, all the other URI attributes are ignored.
  • If a host is not specified for the filter, the port attribute and all the path attributes are ignored.

<data> 标签,用来定义可以接受的数据链接的格式和类型。这个格式规范可以是单纯的类型限制,也可以是单纯的 URI 地址格式限制,也可以是混合的。

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>] 这些标签用来限制 URL 的链接格式。在对 URL 进行限制时, 是必须的内容,如果 没有定义,则后面的 等标签也没用。这里的 URL 不仅仅是网络数据的前缀,本地的数据也可能会带有”content:”和”file:”的前缀。

用来限制数据格式。比如 限制数据类型为图片类型。

二、可被选择为打开文件的方式

要我们的 App 支持可以打开文件,或者说希望让自己的 App 显示在“选择文件的打开方式”列表中,需要在接收的 activity 中做如下配置:

<activity>
    ...
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT" />

        <data android:scheme="file" />
        <data android:scheme="content" />

        <data android:mimeType="image/*" /><!--图片-->
        <data android:mimeType="text/*" /><!--文本-->
    </intent-filter>
</activity>

这样声明了我们的这个 Activity 可以打开图片和文本类型的文件,当通过系统文件夹,点击图片或者文本文件时,我们的 App 就会出现在“选择文件的打开方式”的可选择列表中。

三、可被选择为分享的方式

要我们的 App 支持系统的分享功能,可以从文件管理器或其它 App 将文件分享到我们的 App ,需要在接受的 activity 中做如下配置:

<activity>
    ...
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT" />

        <data android:mimeType="image/*" /><!--图片-->
        <data android:mimeType="text/*" /><!--文本-->
    </intent-filter>
</activity>

这样声明了我们的这个 Activity 可以接受图片文件和文本文件的分享内容,当分享文件时,我们的 App 会出现在“分享文件”的可选择列表中。

四、接受打开/分享的 Intent

在 Activity 中接受打开文件、分享文件的 Intent 时,API > 16 时,有些系统文件夹的文件分享,内容会放在 ClipData 中,而不是放在 mData 中我们需要根据不同的版本做不同的处理:

    public void getIntentData() {
        Intent intent = getIntent();
        Uri uri = intent.getData();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            /*
             * API > 16 时,有些系统的文件夹的文件分享,内容会放在 ClipData 中,而不是放在 mData 中
             */
            if(uri == null && intent.getClipData() != null){
                ClipData.Item item = intent.getClipData().getItemAt(0);
                if(item != null) {
                    uri = item.getUri();
                }
            }
        }
        //从其他app跳入逻辑
        if(uri!=null) {
            String path = ContentUriUtil.getPath(this,uri);
            Toast.makeText(this,"获得path:"+path,Toast.LENGTH_SHORT).show();
        }else {
            Toast.makeText(this,"外部传入的uri为null",Toast.LENGTH_SHORT).show();
        }

    }

当我们分享的文件为系统资源文件,如图片、下载文件、录制的视频等时,分享时传入的 Intent 中 URI 为资源库的映射路径,而不是实际文件的路径,所以需要下面的方法转化为实际的路径:

/**
 * Created by icm on 2018/7/16.
 */
public class ContentUriUtil {
    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     * Framework Documents, as well as the _data field for the MediaStore and
     * other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @author paulburke
     */
    public static String getPath(final Context context, final Uri uri) {
        // DocumentProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {

            // Return the remote address
            if (isGooglePhotosUri(uri)) {
                return uri.getLastPathSegment();
            }

            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }
    /**
     **
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    private static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return null;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    private static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    private static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }
}

六、注意点:

  • <action android:name=”android.intent.action.SEND”/> 和 <data android:scheme=”” /> 两个字段是有冲突,因为 scheme 字段去校验的是 Intent 中 mData 中的 URL ,而第四点中讲到了,当 API > 16 时,分享的 Intent 中 mData 字段是空的。
  • 在第二点中,需要定义两个 scheme ,因为在不同的手机上,在文件管理器中点击文件时,Intent 中的 URL 可能出现“file:”和“content:”开头的两种情况,目前不确定是否与App版本有关,在8.0系统的 华为 mete 10上出现了“content:”开头的这个情况,据说三星手机的部分机型也有这个情况。
  • 我们可以定义更多的 mimeType 来支持更多的类型,具体的类型参考这里:Android 中常用 MimeType 及对应文件类型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值