You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 14及以上无需特殊权限从URI获取PDF文件路径

Android API30+ 无需特殊权限获取PDF文件路径解决方案

问题描述

我通过ACTION_OPEN_DOCUMENT Intent获取到PDF文件的URI,但当该URI的媒体类型为document时(调试发现问题出在代码第126行),无法获取其文件路径。获取图片路径时,使用READ_MEDIA_IMAGES或READ_EXTERNAL_STORAGE权限(Android 12及以下)没有问题,但PDF文件存在此问题。我不想申请外部存储全权限,避免谷歌商店审核问题,请问在API 30及以上系统中,能否无需特殊权限获取PDF文件路径?

相关代码

package maximsblog.blogspot.com.visionsender;

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.text.TextUtils;

import androidx.loader.content.CursorLoader;

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

class AndroidUtilities {
    static class IOUtils {
        private static final int BUFFER_SIZE = 1024 * 2;
        static int copy(InputStream input, OutputStream output) throws IOException {
            byte[] buffer = new byte[BUFFER_SIZE];
            BufferedInputStream b = new BufferedInputStream(input, BUFFER_SIZE);
            BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE);
            int  count = 0;
            int n = 0;
            try {
                while((n =input.read(buffer, 0, BUFFER_SIZE))!= -1) {
                    out.write(buffer, 0, n);
                    count += n;
                }
                out.flush();
            } finally {
                try {
                    out.close();
                } catch (IOException e) {
                }
                try {
                    input.close();
                } catch (IOException e) {
                }
            }
            return count;
        }
    }
    static boolean isWhatsappUri(Uri uri) {
        return "com.whatsapp.provider.media".equals(uri.getAuthority());
    }
    static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }

    static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs, String columnName) {
        Cursor cursor =null;
        if (columnName == null)
            columnName = "_data";
        try {
            cursor = context.getContentResolver().query(
                    uri, new String[] {columnName}, selection, selectionArgs,null);
            if (cursor == null) return uri.getPath();
            if (cursor.moveToFirst()) {
                int column_index = cursor.getColumnIndexOrThrow(columnName);
                if (columnName.equals("_data"))
                    return cursor.getString(column_index);
                else
                    return uri.toString() + "/" + cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    static String getDataColumn(Context context, Uri uri, String selection,
                                String[] selectionArgs) {
        return getDataColumn(context, uri, selection, selectionArgs, null);
    }

    @SuppressLint("NewApi")
    static String getRealPathFromURI_API19(Context context, Uri uri) {
        String result = null;
        if (DocumentsContract.isDocumentUri(context, uri)) {
            String[] split =null;
            String docId=null;
            if (isExternalStorageDocument(uri)) {
                docId = DocumentsContract.getDocumentId(uri);
                String[] splitParts = docId.split(":");
                String type = splitParts[0];
                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory().toString() + "/" + splitParts[1];
                }
            } else if (isDownloadsDocument(uri)) {
                docId = DocumentsContract.getDocumentId(uri);
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
                return getDataColumn(context, contentUri, null, null);
            } else if (isMediaDocument(uri)) {
                docId = DocumentsContract.getDocumentId(uri);
                split = docId.split(":");
                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;
                }
                else if("document".equals(type)) { //in this line (126) we have a problems. How to get path of file for this case?
//contentUri =MediaStore.getDocumentUri(context,uri); //crash,when we call this method.
}
                String selection = "_id=?";
            return getDataColumn(context, contentUri, selection, new String[] {split[1]});
            }
        }
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            if (isGooglePhotosUri(uri))
                return uri.getLastPathSegment();
            return getDataColumn(context, uri, null, null);
        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        } else if (isWhatsappUri(uri)) {
            return getDataColumn(context, uri, null, null, "_display_name");
        }
        return null;
    }

    @SuppressLint("NewApi")
    static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
        if (isWhatsappUri(contentUri)) {
            return new File(contentUri.getPath()).getAbsolutePath();
        }
        String[] proj = new String[] {MediaStore.Images.Media.DATA};
        String result = null;
        CursorLoader cursorLoader = new CursorLoader(
                context,
                contentUri, proj, null, null, null
        );
        Cursor cursor = cursorLoader.loadInBackground();
        if (cursor != null) {
            cursor.moveToFirst();
            result = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
        }
        cursor.close();
        return result;
    }

    static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
        if (isWhatsappUri(contentUri)) {
            return new File(contentUri.getPath()).getAbsolutePath();
        }
                Cursor cursor = context.getContentResolver().query(contentUri, new String[] {MediaStore.Images.Media.DATA}, null, null, null);
        cursor.moveToFirst();
        String result = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
        cursor.close();
        return result;
    }

    static String getRealPath(Context context, Uri uri) {
        if(Build.VERSION.SDK_INT < 11) return getRealPathFromURI_BelowAPI11(context, uri);
        else if(Build.VERSION.SDK_INT < 19) return getRealPathFromURI_API11to18(context, uri);
        else return getRealPathFromURI_API19(context, uri);
    }
    static String getFileName(String Fpath) {
        String[] result=Fpath.split("/");
        return result[result.length-1];
    }
    static void copy(Context context, Uri srcUri, File dstFile) {
        try {
            InputStream inputStream = context.getContentResolver().openInputStream(
                    srcUri);
            FileOutputStream outputStream = new FileOutputStream(dstFile);
            IOUtils.copy(inputStream, outputStream);
            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static String getFilePathFromURI(Context context, Uri contentUri) {
        String fileName = getFileName(contentUri.getPath());
        if (!TextUtils.isEmpty(fileName)) {
            File copyFile = new File(context.getCacheDir().toString() + "/" + fileName);
            copy(context, contentUri, copyFile);
            return copyFile.getAbsolutePath();
        }
        return null;
    }
}

解决方案

1. API30+无法直接获取PDF的原始文件路径

Android 10及以上的分区存储机制,加上ACTION_OPEN_DOCUMENT返回的是文档提供者的URI(并非直接文件路径),导致你无法在无特殊权限的前提下拿到PDF的真实系统路径:

  • 对于document类型的媒体条目,MediaStore并未暴露_data列(存储文件路径的字段)
  • 非媒体类文件(如PDF)可能未被收录到MediaStore的媒体表中,无法通过查询获取路径

2. 正确的处理方式:放弃获取原始路径,直接操作URI

不需要执着于拿到系统文件路径,有两种合规的处理方式:

(1)直接读取URI内容

通过ContentResolver.openInputStream(uri)直接读取PDF内容,无需路径,也不需要任何外部存储权限:

InputStream inputStream = context.getContentResolver().openInputStream(pdfUri);
// 用inputStream读取PDF内容,比如传入PDF解析库

(2)复制到APP私有目录获取本地路径

如果你的业务逻辑必须要文件路径(比如某些第三方库要求),可以将PDF复制到APP的私有目录(cacheDir或filesDir),你的代码里已经实现了getFilePathFromURI方法,这个方法不需要额外权限,因为私有目录属于APP自身空间,系统允许直接操作。

3. 修复代码第126行的问题

修改getRealPathFromURI_API19方法中document类型的分支,直接调用getFilePathFromURI返回私有目录的路径即可:

else if("document".equals(type)) {
    // 复制到APP缓存目录并返回路径
    return getFilePathFromURI(context, uri);
}

这样修改后,既不需要申请外部存储全权限,也能满足业务对文件路径的需求,同时符合谷歌商店的审核规范。

内容的提问来源于stack exchange,提问作者alexandr kozlovskiy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 05:34:53