Android使用Intent选取文件

手头上的项目需要实现让用户选取文件并上传的功能。综合考虑时间和稳定性等因素,决定采取使用Intent向系统请求文件的方法。

根据官网介绍,Intent定义的动作中涉及选取文件的有两个,分别是ACTION_PICK和ACTION_GET_CONTENT。二者的区别是ACTION_PICK通常用于用户已经获取文件Uri的情况,ACTION_GET_CONTENT则侧重于让用户选取未知实际路径文件的场景。显然ACTION_PICK未能满足我们的需求,因此下面讲解ACTION_GET_CONTENT的相关内容。

先提前了解一下ACTION_GET_CONTENT的边界条件:

  1. 不同厂商实现的ROM会有不同的界面与使用流程
  2. 在SDK版本号为18(4.3)及以后版本有提供一次选取多个文件的功能
  3. 无法设置最大可选择数目
  4. 无法跟踪已经选择过的文件状态

上述边界条件最大的问题就是无法保持界面的一致性,其次也可能导致部分设计无法完整实现。若能接受这些限制牺牲部分体验,请继续阅读下面的内容,了解详细步骤。

向系统发出选取文件的请求

String title = getString(R.string.select_file);  
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);  
intent.setType("*/*");  
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);  
// 这里请处理版本兼容问题
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);  
Intent target = Intent.createChooser(intent, title);  
// 这里请处理系统无法响应的异常
startActivityForResult(target, requestId);  

接收返回结果并获取Uri,需要根据用户选取单个还是多个做分支处理:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {  
    ClipData clipData = data.getClipData();
    if (null != clipData) {
        for (int i = 0; i < clipData.getItemCount(); i++) {
            ClipData.Item item = clipData.getItemAt(i);
            Uri uri = item.getUri();
            // Do what you want
        }
    } else {
        Uri uri = data.getData();
        // Do what you want
    }
} else {
    Uri uri = data.getData();
    // Do what you want
}

官网说明当EXTRA_ALLOW_MULTIPLE被设置时,用户选取多个文件将会通过getClipData返回。经过实践发现,当用户选取单个文件的时候,通过getData返回;当用户选取多个文件的时候,通过getClipData返回。另需注意:getClipData尽在SDK16之后提供,注意版本兼容处理。

解析Uri,获取文件的真实路径。这里抛出一个实现代码。它比较完整地处理了各种形式返回的文件Uri,最终返回文件路径,失败返回null。

package com.maqv.utils;

import android.content.ContentUris;  
import android.content.Context;  
import android.database.Cursor;  
import android.net.Uri;  
import android.os.Build;  
import android.os.Environment;  
import android.provider.DocumentsContract;  
import android.provider.MediaStore;

/**
 * Created by liuhuobing on 2016/5/13.
 *
 */
public class UriUtil {

    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     */
    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];
                }
                // TODO handle non-primary volumes
            }
            // 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 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.
     */
    public 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 column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } catch (Exception e) {
            LogUtil.e("UriUtil", "Unable to get path");
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }


    /**
     * Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }
}

至此选取文件的流程基本完毕,后续针对文件的真是路径进行处理不在此次讨论范围之内。

ChardLau

继续阅读此作者的更多文章