Android 选择文件返回路径怎么就这么难?

Android 选择文件返回路径怎么就这么难?

这个问题纠结了我很长时间,终于有了比较好的解决方案。目前已知适配所有的机型,有问题就留言吧。

正确姿势来一遍

1. 打开系统自带的文件浏览器并限制类型

public void OpenFile(View view) {
        // 指定类型
        String[] mimeTypes = {"*/*"};
       // String[] mimeTypes = {"application/octet-stream"}; // 指定bin类型
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        StringBuilder mimeTypesStr = new StringBuilder();
        for (String mimeType : mimeTypes) {
            mimeTypesStr.append(mimeType).append("|");
        }
        intent.setType(mimeTypesStr.substring(0, mimeTypesStr.length() - 1));
        startActivityForResult(Intent.createChooser(intent, "ChooseFile"), REQUEST_FILE_CODE);
    } 

其中mimeTypes是指定文件的类型,比如图片,比如Excel,即使设置了该属性,在某些机型上没有效果,所以在选择文件后还需要判断格式是否正确。

为了大家嫌麻烦,我把类型举例放在了文章末尾,方便大家进行查询。

2. 返回选择的数据

final int REQUEST_FILE_CODE = 1000;

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_FILE_CODE && resultCode == RESULT_OK) {
            Uri uri = data.getData();
            Log.d(TAG, "uri:" + uri);
            //String filePath = PickUtils.getPath(this, uri);
            //textView.setText(filePath);
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

重点来了,我们可以很轻松的拿到Uri, 但是转absolute path 在不同的机型却遇到了奇奇怪怪的问题,至今没有完全弄明白。

大概的意思就是有些机型转path时会失败,比如在华为10.0的机型上,通过下载内容中打开Download的文件就会失败,场景是原本的documentid可以转为long型,但它变成了这个样子msf:123,导致转换失败。

有很多人都存在这个问题,比如这个地址:https://stackoverflow.com/questions/58660420/api-level-29-intent-action-get-content-returning-wrong-id-from-downloads-folder

解决方法:在转换绝对路径失败的情况,将文件拷贝到自己的app的缓存目录下,然后返回路径。 代码在文章末尾。

3. 思考与拓展

我们可以轻松的拿到Uri之后,实际上在不转绝对路径的情况下,用Uri已经可以做很多事情了,比如获取文件类型、比如获取文件的byte[]、获取文件名 等等。

  • 获取文件类型,返回的结果需要在类型对应中去对照。
/**
     * 获取文件的类型。
     * @param context 上下文
     * @param uri uri
     * @return
     */
    public static String getFormatByUri(@NonNull Context context, @NonNull Uri uri) {
        String type = context.getContentResolver().getType(uri);
        if (TextUtils.isEmpty(type)) return null;
        return type;
    }
    
  • 获取字节数组
/**
     * 根据uri获取 byte[]
     * @param context
     * @param uri
     * @return
     * @throws IOException
     */
    public static byte[] getBytesByUri(Context context, Uri uri) throws IOException {
        InputStream iStream = context.getContentResolver().openInputStream(uri);
        if (iStream == null) return null;
        return getBytes(iStream);
    }

    private static byte[] getBytes(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];

        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            byteBuffer.write(buffer, 0, len);
        }
        return byteBuffer.toByteArray();
    }
    
  • 获取文件名
/**
     * 获取文件名
     * @param uri uri
     * @return
     */
    public String getFileNameByUri(Uri uri) {
        String filename = "";
        Cursor returnCursor = getContentResolver().query(uri, null,
                null, null, null);
        if (returnCursor != null) {
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            returnCursor.moveToFirst();
            filename = returnCursor.getString(nameIndex);
            returnCursor.close();
        }
        return filename;
    }

4. URI 转 Path

当然,这个完美的工具类还是提供给大家。出处我不记得了,我稍微改了一点,目前用下来非常完美。 只是在转path不成功的情况下,会拷贝文件放到应用的缓存目录,大家需要注意下清理缓存的时机。

import android.annotation.SuppressLint;
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;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created by pangw on 2018/5/23.
 */
public class PickUtils {

    public static final String DOCUMENTS_DIR = "documents";

    @SuppressLint("NewApi")
    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && 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);

                if (id != null && id.startsWith("raw:")) {
                    return id.substring(4);
                }

                String[] contentUriPrefixesToTry = new String[]{
                        "content://downloads/public_downloads",
                        "content://downloads/my_downloads"
                };

                for (String contentUriPrefix : contentUriPrefixesToTry) {
                    try {
                        // note: id 可能为字符串,如在华为10.0系统上,选择文件后id为:"msf:254",导致转Long异常
                        Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.parseLong(id));
                        String path = getDataColumn(context, contentUri, null, null);
                        if (path != null && !path.equals("")) {
                            return path;
                        }
                    } catch (Exception e) {
                    }
                }

                // path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams
                String fileName = getFileName(context, uri);
                File cacheDir = getDocumentCacheDir(context);
                File file = generateFileName(fileName, cacheDir);
                String destinationPath = null;
                if (file != null) {
                    destinationPath = file.getAbsolutePath();
                    saveFileFromUri(context, uri, destinationPath);
                }

                return destinationPath;
            }
            // 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())) {
            String path = getDataColumn(context, uri, null, null);
            if (path != null && !path.equals("")) return path;

            // path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams
            String fileName = getFileName(context, uri);
            File cacheDir = getDocumentCacheDir(context);
            File file = generateFileName(fileName, cacheDir);
            String destinationPath = null;
            if (file != null) {
                destinationPath = file.getAbsolutePath();
                saveFileFromUri(context, uri, destinationPath);
            }
            return destinationPath;
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public 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.
     */
    public 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.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column};
        String path = "";
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                path = cursor.getString(column_index);
                return path;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return path;
    }

    public static String getFileName(@NonNull Context context, Uri uri) {
        String mimeType = context.getContentResolver().getType(uri);
        String filename = null;
        if (mimeType == null && context != null) {
            String path = getPath(context, uri);
            if (path == null) {
                filename = getName(uri.toString());
            } else {
                File file = new File(path);
                filename = file.getName();
            }
        } else {
            Cursor returnCursor = context.getContentResolver().query(uri, null,
                    null, null, null);
            if (returnCursor != null) {
                int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                returnCursor.moveToFirst();
                filename = returnCursor.getString(nameIndex);
                returnCursor.close();
            }
        }

        return filename;
    }

    public static String getName(String filename) {
        if (filename == null) {
            return null;
        }
        int index = filename.lastIndexOf('/');
        return filename.substring(index + 1);
    }

    public static File getDocumentCacheDir(@NonNull Context context) {
        Log.d("PickUtils", "getDocumentCacheDir");
        File dir = new File(context.getCacheDir(), DOCUMENTS_DIR);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        return dir;
    }

    @Nullable
    public static File generateFileName(@Nullable String name, File directory) {
        if (name == null) {
            return null;
        }

        File file = new File(directory, name);

        if (file.exists()) {
            String fileName = name;
            String extension = "";
            int dotIndex = name.lastIndexOf('.');
            if (dotIndex > 0) {
                fileName = name.substring(0, dotIndex);
                extension = name.substring(dotIndex);
            }

            int index = 0;

            while (file.exists()) {
                index++;
                name = fileName + '(' + index + ')' + extension;
                file = new File(directory, name);
            }
        }

        try {
            if (!file.createNewFile()) {
                return null;
            }
        } catch (IOException e) {
            return null;
        }

        return file;
    }


    private static void saveFileFromUri(Context context, Uri uri, String destinationPath) {
        InputStream is = null;
        BufferedOutputStream bos = null;
        try {
            is = context.getContentResolver().openInputStream(uri);
            bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false));
            byte[] buf = new byte[1024];
            is.read(buf);
            do {
                bos.write(buf);
            } while (is.read(buf) != -1);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) is.close();
                if (bos != null) bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}


5. 类型对应

private final String[][] MIME_MapTable={
 
  //{后缀名,MIME类型}
 
  {".3gp", "video/3gpp"},
 
  {".apk", "application/vnd.android.package-archive"},
 
  {".asf", "video/x-ms-asf"},
 
  {".avi", "video/x-msvideo"},
 
  {".bin", "application/octet-stream"},
 
  {".bmp", "image/bmp"},
 
  {".c", "text/plain"},
 
  {".class", "application/octet-stream"},
 
  {".conf", "text/plain"},
 
  {".cpp", "text/plain"},
 
  {".doc", "application/msword"},
 
  {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
 
  {".xls", "application/vnd.ms-excel"},
 
  {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
 
  {".exe", "application/octet-stream"},
 
  {".gif", "image/gif"},
 
  {".gtar", "application/x-gtar"},
 
  {".gz", "application/x-gzip"},
 
  {".h", "text/plain"},
 
  {".htm", "text/html"},
 
  {".html", "text/html"},
 
  {".jar", "application/java-archive"},
 
  {".java", "text/plain"},
 
  {".jpeg", "image/jpeg"},
 
  {".jpg", "image/jpeg"},
 
  {".js", "application/x-javascript"},
 
  {".log", "text/plain"},
 
  {".m3u", "audio/x-mpegurl"},
 
  {".m4a", "audio/mp4a-latm"},
 
  {".m4b", "audio/mp4a-latm"},
 
  {".m4p", "audio/mp4a-latm"},
 
  {".m4u", "video/vnd.mpegurl"},
 
  {".m4v", "video/x-m4v"},
 
  {".mov", "video/quicktime"},
 
  {".mp2", "audio/x-mpeg"},
 
  {".mp3", "audio/x-mpeg"},
 
  {".mp4", "video/mp4"},
 
  {".mpc", "application/vnd.mpohun.certificate"},
 
  {".mpe", "video/mpeg"},
 
  {".mpeg", "video/mpeg"},
 
  {".mpg", "video/mpeg"},
 
  {".mpg4", "video/mp4"},
 
  {".mpga", "audio/mpeg"},
 
  {".msg", "application/vnd.ms-outlook"},
 
  {".ogg", "audio/ogg"},
 
  {".pdf", "application/pdf"},
 
  {".png", "image/png"},
 
  {".pps", "application/vnd.ms-powerpoint"},
 
  {".ppt", "application/vnd.ms-powerpoint"},
 
  {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
 
  {".prop", "text/plain"},
 
  {".rc", "text/plain"},
 
  {".rmvb", "audio/x-pn-realaudio"},
 
  {".rtf", "application/rtf"},
 
  {".sh", "text/plain"},
 
  {".tar", "application/x-tar"},
 
  {".tgz", "application/x-compressed"},
 
  {".txt", "text/plain"},
 
  {".wav", "audio/x-wav"},
 
  {".wma", "audio/x-ms-wma"},
 
  {".wmv", "audio/x-ms-wmv"},
 
  {".wps", "application/vnd.ms-works"},
 
  {".xml", "text/plain"},
 
  {".z", "application/x-compress"},
 
  {".zip", "application/x-zip-compressed"},
 
  {"", "*/*"}
 
  };

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值