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());
        }
    }
}
相关推荐
阿伟*rui41 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj3 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck3 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei3 小时前
java的类加载机制的学习
java·学习
Yaml45 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~5 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616885 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java6 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~6 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust