SAF文件选择、谷歌PhotoPicker图片视频选择与真实路径转换

一、构建选择文件与回调方法

java 复制代码
//文件选择回调
    ActivityResultLauncher<String[]> pickFile = registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri->{
        if (uri != null) {
            Log.e("cxy", "返回的uri:" + uri);
            Log.e("cxy","Path是:"+ FileUtil.getFilePathByUri(context,uri));
        }
    });

//启动选择
pickFile.launch(new String[]{"*/*"})
java 复制代码
//photopicker选择器回调(多选同理)
    ActivityResultLauncher<PickVisualMediaRequest> pickMedia =
            registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), uri -> {
                if (uri != null) {
                    Log.e("cxy", "返回的uri:" + uri);
                    Log.e("cxy","Path是:"+ FileUtil.getFilePathByUri(context,uri));
                } else {
                }
            });


//photopicker选择器启动选择,选择模式:ImageOnly、VideoOnly、ImageAndVideo
pickMedia.launch(new PickVisualMediaRequest.Builder()
                .setMediaType(isImg ? ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE : ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE)
                .build()));

二、uri转真实路径工具类

备注:

1、针对Download目录先判空,为空则复制文件至内部路径再返回

2、SAF选择文件时,存储设备选择返回的uri路径为:

content://com.android.externalstorage.documents/document/primary%3ADocuments%2Fenvironment.pub

通过选择侧边栏中的图片、视频、音频、文档等分类进入选择文件目录时,返回的路径为:

content://com.android.providers.media.documents/document/image%3A1000010017

content://com.android.providers.media.documents/document/video%3A1000008421

content://com.android.providers.media.documents/document/audio%3A1000000212

content://com.android.providers.media.documents/document/document%3A1000009571

这时如果没有授予存储权限,则走复制方法返回路径,Android13以上可按需申请图片、视频、音频读取权限,但是针对doc、pdf等其它类型文件无法返回真实路径,除非授予管理所有文件权限

java 复制代码
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

权限:使用photopicker时,需要Android13以上按需申请以下使用的权限,否则MediaStore不返回真实路径,无权限情况下走复制方法返回路径

java 复制代码
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>


    //Activity中权限申请与回调
    ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), map->{
        //处理权限结果{android.permission.READ_MEDIA_IMAGES=true, android.permission.READ_MEDIA_VIDEO=true}
    });

    if (!PmUtil.hasStoragePm(this)){
        PmUtil.requestStoragePm(permissionLauncher);
    }

//权限申请工具类
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;

import androidx.activity.result.ActivityResultLauncher;
import androidx.core.content.ContextCompat;

public class PmUtil {
    public static boolean hasStoragePm(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED;
        } else {
            return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        }
    }

    public static void requestStoragePm(ActivityResultLauncher<String[]> launcher) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            launcher.launch(new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO});
        } else {
            launcher.launch(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE});
        }
    }
}
java 复制代码
import android.content.ContentResolver;
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.util.Log;

import androidx.loader.content.CursorLoader;

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

public class FileUtil {

    public static String getFilePathByUri(Context context, Uri uri) {
        // 以 file:// 开头的
        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            return uri.getPath();
        }
        // 以/storage开头的也直接返回
        if (isOtherDocument(uri)) {
            return uri.getPath();
        }
        // 版本兼容的获取!
        String path;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            path = getFilePathByUri_BELOWAPI11(context, uri);
            if (path != null) {
                Log.e("cxy", "getFilePathByUri_BELOWAPI11获取到的路径为:" + path);
                return path;
            }
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            path = getFilePathByUri_API11to18(context, uri);
            if (path != null) {
                Log.e("cxy", "getFilePathByUri_API11to18获取到的路径为:" + path);
                return path;
            }
        }
        path = getFilePathByUri_API19(context, uri);
        Log.e("cxy", "getFilePathByUri_API19获取到的路径为:" + path);
        return path;
    }

    private static String getFilePathByUri_BELOWAPI11(Context context, Uri uri) {
        // 以 content:// 开头的,比如 content://media/extenral/images/media/17766
        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            String path = null;
            String[] projection = new String[]{MediaStore.Images.Media.DATA};
            Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
            if (cursor != null) {
                if (cursor.moveToFirst()) {
                    int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                    if (columnIndex > -1) {
                        path = cursor.getString(columnIndex);
                    }
                }
                cursor.close();
            }
            return path;
        }
        return null;
    }

    private static String getFilePathByUri_API11to18(Context context, Uri contentUri) {
        String[] projection = {MediaStore.Images.Media.DATA};
        String result = null;
        CursorLoader cursorLoader = new CursorLoader(context, contentUri, projection, null, null, null);
        Cursor cursor = cursorLoader.loadInBackground();
        if (cursor != null) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            result = cursor.getString(column_index);
            cursor.close();
        }
        return result;
    }

    private static String getFilePathByUri_API19(Context context, Uri uri) {
        // 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700
        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (DocumentsContract.isDocumentUri(context, uri)) {
                if (isExternalStorageDocument(uri)) {
                    // ExternalStorageProvider
                    String docId = DocumentsContract.getDocumentId(uri);
                    String[] split = docId.split(":");
                    String type = split[0];
                    if ("primary".equalsIgnoreCase(type)) {
                        if (split.length > 1) {
                            return Environment.getExternalStorageDirectory() + "/" + split[1];
                        } else {
                            return Environment.getExternalStorageDirectory() + "/";
                        }
                        // This is for checking SD Card
                    }
                } else if (isDownloadsDocument(uri)) {
                    //下载内容提供者时应当判断下载管理器是否被禁用
                    int stateCode = context.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
                    if (stateCode != 0 && stateCode != 1) {
                        return null;
                    }
                    String id = DocumentsContract.getDocumentId(uri);
                    // 如果出现这个RAW地址,我们则可以直接返回!
                    if (id.startsWith("raw:")) {
                        return id.replaceFirst("raw:", "");
                    }
                    if (id.contains(":")) {
                        String[] tmp = id.split(":");
                        if (tmp.length > 1) {
                            id = tmp[1];
                        }
                    }
                    Uri contentUri = Uri.parse("content://downloads/public_downloads");
                    Log.e("cxy", "测试打印Uri: " + uri);
                    try {
                        contentUri = ContentUris.withAppendedId(contentUri, Long.parseLong(id));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    String path = getDataColumn(context, contentUri, null, null);
                    if (path != null) return path;
                    // 兼容某些特殊情况下的文件管理器!
                    String fileName = getFileNameByUri(context, uri);
                    if (fileName != null) {
                        path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
                        File pathFile = new File(path);
                        if (!pathFile.exists()) {
                            path = getFilePathForCopy(context, uri);
                        }
                        return path;
                    }
                } else if (isMediaDocument(uri)) {
                    // MediaProvider
                    String docId = DocumentsContract.getDocumentId(uri);
                    String[] split = docId.split(":");
                    String type = split[0];
                    Uri contentUri;
                    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 {
                        contentUri = MediaStore.Files.getContentUri("external");
                    }
                    String selection = "_id=?";
                    String[] selectionArgs = new String[]{split[1]};
                    String resultPath = getDataColumn(context, contentUri, selection, selectionArgs);
                    if (resultPath == null) {
                        resultPath = getFilePathForCopy(context, uri);
                    }
                    return resultPath;
                }
            } else {
                //content://media/picker/0/com.android.providers.media.photopicker/media/1000008424
                //photopicker情况,有权限则返回真实路径,无权限则返回复制后的内部路径,但复制后的名称是ID
                String uriPath = uri.getPath();
                String id = "";
                if (uriPath != null) {
                    String[] strings = uriPath.split("/");
                    id = strings[strings.length - 1];
                }
                String selection = "_id=?";
                String[] selectionArgs = new String[]{id};
                Uri contentUri = MediaStore.Files.getContentUri("external");
                String resultPath = getDataColumn(context, contentUri, selection, selectionArgs);
                if (resultPath == null) {
                    resultPath = getFilePathForCopy(context, uri);
                }
                return resultPath;
            }
        }
        return null;
    }

    private static String getFileNameByUri(Context context, Uri uri) {
        String relativePath = getFileRelativePathByUri_API18(context, uri);
        if (relativePath == null) relativePath = "";
        final String[] projection = {
                MediaStore.MediaColumns.DISPLAY_NAME
        };
        try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
                String path = relativePath + cursor.getString(index);
                cursor.close();
                return path;
            }
        }
        return null;
    }

    private static String getFileRelativePathByUri_API18(Context context, Uri uri) {
        final String[] projection;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            projection = new String[]{
                    MediaStore.MediaColumns.RELATIVE_PATH
            };
            try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {
                if (cursor != null && cursor.moveToFirst()) {
                    int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH);
                    String path = cursor.getString(index);
                    cursor.close();
                    return path;
                }
            }
        }
        return null;
    }

    private static String getFilePathForCopy(Context context, Uri uri) {
        try {
            Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            returnCursor.moveToFirst();
            String name = (returnCursor.getString(nameIndex));
            File file = new File(context.getCacheDir(), name);
            InputStream inputStream = context.getContentResolver().openInputStream(uri);
            FileOutputStream outputStream = new FileOutputStream(file);
            int read = 0;
            int maxBufferSize = 1024 * 1024;
            int bytesAvailable = inputStream.available();

            int bufferSize = Math.min(bytesAvailable, maxBufferSize);

            final byte[] buffers = new byte[bufferSize];
            while ((read = inputStream.read(buffers)) != -1) {
                outputStream.write(buffers, 0, read);
            }
            returnCursor.close();
            inputStream.close();
            outputStream.close();
            return file.getPath();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        final String column = MediaStore.Images.Media.DATA;
        final String[] projection = {column};
        try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                String path = cursor.getString(column_index);
                cursor.close();
                return path;
            }
        } catch (IllegalArgumentException iae) {
            iae.printStackTrace();
        }
        return null;
    }

    private static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    private static boolean isOtherDocument(Uri uri) {
        // 以/storage开头的也直接返回
        if (uri != null && uri.getPath() != null) {
            String path = uri.getPath();
            if (path.startsWith("/storage")) {
                return true;
            }
            if (path.startsWith("/external_files")) {
                return true;
            }
        }
        return false;
    }

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

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

    //如果是复制的文件,完成后清除缓存
    public static void deleteCacheFile(Context context, String path) {
        if (path == null) {
            return;
        }
        File file = new File(path);
        if (file.exists() && context.getCacheDir().equals(file.getParentFile())) {
            Log.e("cxy", "删除成功?" + file.delete());
        }
    }
}
相关推荐
总是学不会.4 分钟前
【集合】Java 8 - Stream API 17种常用操作与案例详解
java·windows·spring boot·mysql·intellij-idea·java集合
潜意识起点13 分钟前
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
java·spring boot·后端
mxbb.15 分钟前
单点Redis所面临的问题及解决方法
java·数据库·redis·缓存
叶羽西27 分钟前
Android Studio IDE环境配置
android·ide·android studio
云和数据.ChenGuang40 分钟前
《XML》教案 第1章 学习XML基础
xml·java·学习
王·小白攻城狮·不是那么帅的哥·天文1 小时前
Java操作Xml
xml·java
发飙的蜗牛'1 小时前
23种设计模式
android·java·设计模式
music0ant1 小时前
Idean 处理一个项目引用另外一个项目jar 但jar版本低的问题
java·pycharm·jar
陈大爷(有低保)1 小时前
logback日志控制台打印与写入文件
java
繁川1 小时前
深入理解Spring AOP
java·后端·spring