【问题标题】:Failing to get actual file path from storage in android无法从android中的存储中获取实际文件路径
【发布时间】:2022-08-04 17:13:01
【问题描述】:

我一直在尝试使用多部分 API 将文件上传到服务器。为此,我正在触发一个隐式意图,以便用户可以从设备的存储中选择一个文件。 在onActivityResult 中,我执行以下步骤:

  1. 从Intent 对象中获取Uri
  2. 从获取的Uri获取文件路径
  3. 从文件路径创建File对象
  4. 调用API并上传文件

    现在我在步骤 2 中获取 pdf 文件路径时面临挑战。我浏览了很多文章,发现了一段代码,每个人都共享但不适用于所有设备。这是sn-p:

    public class FileUtils {
        private static Uri contentUri = null;
    
        /**
         * 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.<br>
         * <br>
         *
         * @param context The context.
         * @param uri     The Uri to query.
         */
        @SuppressLint(\"NewApi\")
        public static String getPath(final Context context, final Uri uri) {
            // check here to KITKAT or new version
            final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
            String selection = null;
            String[] selectionArgs = null;
            // 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];
    
                    String fullPath = getPathFromExtSD(split);
                    if (fullPath != \"\") {
                        return fullPath;
                    } else {
                        return null;
                    }
                }
    
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        final String id;
                        Cursor cursor = null;
                        try {
                            cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);
                            if (cursor != null && cursor.moveToFirst()) {
                                String fileName = cursor.getString(0);
                                String path = Environment.getExternalStorageDirectory().toString() + \"/Download/\" + fileName;
                                if (!TextUtils.isEmpty(path)) {
                                    return path;
                                }
                            }
                        } finally {
                            if (cursor != null)
                                cursor.close();
                        }
                        id = DocumentsContract.getDocumentId(uri);
                        if (!TextUtils.isEmpty(id)) {
                            if (id.startsWith(\"raw:\")) {
                                return id.replaceFirst(\"raw:\", \"\");
                            }
                            String[] contentUriPrefixesToTry = new String[]{
                                    \"content://downloads/public_downloads\",
                                    \"content://downloads/my_downloads\"
                            };
                            for (String contentUriPrefix : contentUriPrefixesToTry) {
                                try {
                                    final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
    
                             /*   final Uri contentUri = ContentUris.withAppendedId(
                                        Uri.parse(\"content://downloads/public_downloads\"), Long.valueOf(id));*/
    
                                    return getDataColumn(context, contentUri, null, null);
                                } catch (NumberFormatException e) {
                                    //In Android 8 and Android P the id is not a number
                                    return uri.getPath().replaceFirst(\"^/document/raw:\", \"\").replaceFirst(\"^raw:\", \"\");
                                }
                            }
    
    
                        }
    
                    } else {
                        final String id = DocumentsContract.getDocumentId(uri);
                        final boolean isOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
                        if (id.startsWith(\"raw:\")) {
                            return id.replaceFirst(\"raw:\", \"\");
                        }
                        try {
                            contentUri = ContentUris.withAppendedId(
                                    Uri.parse(\"content://downloads/public_downloads\"), Long.valueOf(id));
    
                        } catch (NumberFormatException e) {
                            e.printStackTrace();
                        }
                        if (contentUri != null) {
                            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;
                    }
                      selection = \"_id=?\";
                     selectionArgs = new String[]{split[1]};
    
    
                    return getDataColumn(context, contentUri, selection,
                            selectionArgs);
                } else if (isGoogleDriveUri(uri)) {
                    return getDriveFilePath(uri, context);
                }
            }
    
    
            // MediaStore (and general)
            else if (\"content\".equalsIgnoreCase(uri.getScheme())) {
    
                if (isGooglePhotosUri(uri)) {
                    return uri.getLastPathSegment();
                }
    
                if (isGoogleDriveUri(uri)) {
                    return getDriveFilePath(uri, context);
                }
                 if( Build.VERSION.SDK_INT == Build.VERSION_CODES.N)
                 {
                    // return getFilePathFromURI(context,uri);
                     return getMediaFilePathForN(uri, context);
                    // return getRealPathFromURI(context,uri);
                 }else
                 {
    
                     return getDataColumn(context, uri, null, null);
                 }
    
    
            }
            // File
            else if (\"file\".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }
    
            return null;
        }
    
        /**
         * Check if a file exists on device
         *
         * @param filePath The absolute file path
         */
        private static boolean fileExists(String filePath) {
            File file = new File(filePath);
    
            return file.exists();
        }
    
    
        /**
         * Get full file path from external storage
         *
         * @param pathData The storage type and the relative path
         */
        private static String getPathFromExtSD(String[] pathData) {
            final String type = pathData[0];
            final String relativePath = \"/\" + pathData[1];
            String fullPath = \"\";
    
            // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string
            // something like \"71F8-2C0A\", some kind of unique id per storage
            // don\'t know any API that can get the root path of that storage based on its id.
            //
            // so no \"primary\" type, but let the check here for other devices
            if (\"primary\".equalsIgnoreCase(type)) {
                fullPath = Environment.getExternalStorageDirectory() + relativePath;
                if (fileExists(fullPath)) {
                    return fullPath;
                }
            }
    
            // Environment.isExternalStorageRemovable() is `true` for external and internal storage
            // so we cannot relay on it.
            //
            // instead, for each possible path, check if file exists
            // we\'ll start with secondary storage as this could be our (physically) removable sd card
            fullPath = System.getenv(\"SECONDARY_STORAGE\") + relativePath;
            if (fileExists(fullPath)) {
                return fullPath;
            }
    
            fullPath = System.getenv(\"EXTERNAL_STORAGE\") + relativePath;
            if (fileExists(fullPath)) {
                return fullPath;
            }
    
            return fullPath;
        }
    
        private static String getDriveFilePath(Uri uri, Context context) {
            Uri returnUri = uri;
            Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
            /*
             * Get the column indexes of the data in the Cursor,
             *     * move to the first row in the Cursor, get the data,
             *     * and display it.
             * */
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
            returnCursor.moveToFirst();
            String name = (returnCursor.getString(nameIndex));
            String size = (Long.toString(returnCursor.getLong(sizeIndex)));
            File file = new File(context.getCacheDir(), name);
            try {
                InputStream inputStream = context.getContentResolver().openInputStream(uri);
                FileOutputStream outputStream = new FileOutputStream(file);
                int read = 0;
                int maxBufferSize = 1 * 1024 * 1024;
                int bytesAvailable = inputStream.available();
    
                //int bufferSize = 1024;
                int bufferSize = Math.min(bytesAvailable, maxBufferSize);
    
                final byte[] buffers = new byte[bufferSize];
                while ((read = inputStream.read(buffers)) != -1) {
                    outputStream.write(buffers, 0, read);
                }
                Log.e(\"File Size\", \"Size \" + file.length());
                inputStream.close();
                outputStream.close();
                Log.e(\"File Path\", \"Path \" + file.getPath());
                Log.e(\"File Size\", \"Size \" + file.length());
            } catch (Exception e) {
                Log.e(\"Exception\", e.getMessage());
            }
            return file.getPath();
        }
    
        private static String getMediaFilePathForN(Uri uri, Context context) {
            Uri returnUri = uri;
            Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
            /*
             * Get the column indexes of the data in the Cursor,
             *     * move to the first row in the Cursor, get the data,
             *     * and display it.
             * */
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
            returnCursor.moveToFirst();
            String name = (returnCursor.getString(nameIndex));
            String size = (Long.toString(returnCursor.getLong(sizeIndex)));
            File file = new File(context.getFilesDir(), name);
            try {
                InputStream inputStream = context.getContentResolver().openInputStream(uri);
                FileOutputStream outputStream = new FileOutputStream(file);
                int read = 0;
                int maxBufferSize = 1 * 1024 * 1024;
                int bytesAvailable = inputStream.available();
    
                //int bufferSize = 1024;
                int bufferSize = Math.min(bytesAvailable, maxBufferSize);
    
                final byte[] buffers = new byte[bufferSize];
                while ((read = inputStream.read(buffers)) != -1) {
                    outputStream.write(buffers, 0, read);
                }
                Log.e(\"File Size\", \"Size \" + file.length());
                inputStream.close();
                outputStream.close();
                Log.e(\"File Path\", \"Path \" + file.getPath());
                Log.e(\"File Size\", \"Size \" + file.length());
            } catch (Exception e) {
                Log.e(\"Exception\", e.getMessage());
            }
            return file.getPath();
        }
    
    
        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());
        }
    
    
        /**
         * @param uri The Uri to check.
         * @return Whether the Uri authority is Google Drive.
         */
        private static boolean isGoogleDriveUri(Uri uri) {
            return \"com.google.android.apps.docs.storage\".equals(uri.getAuthority()) || \"com.google.android.apps.docs.storage.legacy\".equals(uri.getAuthority());
        }
    
    
    }
    

    但是这段代码并不适用于所有设备。它尝试使用内容解析器从_data 列(存储实际文件路径)中获取内容。

    经过一番尝试和尝试,我能够通过在下面的代码中添加一个案例来获取更多设备上的路径:

    else if (isMediaDocument(uri)){
          ...
          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;
          } 
          // Below case is the new addition
          else if (\"document\".equals(type)) {
                contentUri = MediaStore.getMediaUri(context, uri);
          }
          ...
    }
    

    为了解决所有设备,我挖得更深了。在进一步调试并获取所有列后,我知道这个_data 在某些设备中不存在,而这就是它失败并导致崩溃的地方。

    java.lang.IllegalStateException: java.io.FileNotFoundException: No root for document 在这行代码上 contentUri = MediaStore.getMediaUri(context, uri);

    寻找解决所有设备的解决方案。谢谢 !

  • 我建议您使用ContentResolver 获取文件内容,将其复制到缓存中的临时文件,然后上传该文件。 Uris 有很多方案和可能性,考虑每种情况几乎是不可能的。如果您需要从Uri 获取内容的帮助,请查看this 答案。
  • 同意!在这里感谢您的时间和投入。当您说缓存时,您的意思是 getExternalCacheDir() 路径吗?
  • 不一定是外部的,可以是正常的cacheDir

标签: android


【解决方案1】:

感谢@momvart 和https://stackoverflow.com/a/60538917/3876954

我能够解决这个问题。这是答案:

public class FileSelectionUtils {

public static void copyStream(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[8192];
    int length;
    while ((length = source.read(buf)) != -1) {
        target.write(buf, 0, length);
    }
}

public static Uri getFilePathFromUri(Context context, Uri uri) throws IOException {
    String fileName = getFileName(context, uri);
    File file = new File(context.getExternalCacheDir(), fileName);
    file.createNewFile();
    try (OutputStream outputStream = new FileOutputStream(file);
         InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
        FileSelectionUtils.copyStream(inputStream, outputStream); //Simply reads input to output stream

        outputStream.flush();
    }
    return Uri.fromFile(file);
}

public static String getFileName(Context context, Uri uri) {
    String fileName = getFileNameFromCursor(context, uri);
    if (fileName == null) {
        String fileExtension = getFileExtension(context, uri);
        fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
    } else if (!fileName.contains(".")) {
        String fileExtension = getFileExtension(context, uri);
        fileName = fileName + "." + fileExtension;
    }
    return fileName;
}

public static String getFileExtension(Context context, Uri uri) {
    String fileType = context.getContentResolver().getType(uri);
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
}

public static String getFileNameFromCursor(Context context, Uri uri) {
    Cursor fileCursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    String fileName = null;
    if (fileCursor != null && fileCursor.moveToFirst()) {
        int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (cIndex != -1) {
            fileName = fileCursor.getString(cIndex);
        }
    }
    return fileName;
}
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-23
    • 2018-03-27
    相关资源
    最近更新 更多