Android文件管理器选择文件,获得文件路径URI转File

 

前情描述:

使用系统文件管理器,选择指定文件类型上传。

基础知识
  • MIME
  • 调起文件管理器
  • 指定浏览位置(路径转URI)
  • 设置多种文件类型
  • URI转路径

1. MIME

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。

final String DOC = "application/msword";
final String XLS = "application/vnd.ms-excel";
final String PPT = "application/vnd.ms-powerpoint";
final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
final String XLSX = "application/x-excel";
final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
final String PDF = "application/pdf";
final String MP4 = "video/mp4";
final String M3U8 = "application/x-mpegURL";
...
更多文件类型,自行百度
文件类型

 

2. 调起文件管理器

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
//任意类型文件
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent,1);

//-------常用类型
    //图片
//intent.setType(“image/*”);
    //音频
//intent.setType(“audio/*”);
    //视频
//intent.setType(“video/*”); 
//intent.setType(“video/*;image/*”);
1.所有类型文件
 Intent intent= new Intent(Intent.ACTION_PICK, null);
 intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
 startActivityForResult(intent, REQUEST_CODE_FILE); 
2.系统的相冊
    @Override
    public void initData() {
        // 请求文件选择权限
        if (ContextCompat.checkSelfPermission(getContext(), android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(getActivity(), new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_FILE);
        } else {
            initUpload(); // 如果权限已授予,直接开始选择文件
        }
 
    }

    private void initUpload() {
        // 创建一个ActivityResultLauncher来处理返回的结果
        ActivityResultLauncher launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            // 在这里可以执行上传附件的操作
            if (result.getResultCode() == Activity.RESULT_OK) {
                viewModel.fileUploadByPath(result.getData().getData());
            }
        });
        binding.btUpload.setOnClickListener(view -> openFilePicker(launcher));
    }

    private void openFilePicker(ActivityResultLauncher launcher) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("*/*");//"image/*":可以指定MIME类型,这里以选择图片为例
        // 启动文件选择器
        launcher.launch(intent);
    }
示例

 

3. 指定浏览位置(路径转URI)

跳转到指定路径下,涉及到将路径转为URI,考虑Android版本区别

/**
 * file --> uri
 * @param context
 * @param file
 *
 * @return
 */
public static Uri getUriFromFile(Context context, File file) {
    if (context == null || file == null) {
        throw new NullPointerException();
    }
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(context.getApplicationContext(), BuildConfig.APPLICATION_ID + ".fileprovider", file);
    } else {
        uri = Uri.fromFile(file);
    }
    return uri;
}
getUriFromFile

由于7.0的升级还需要在AndroidManifest.xml中配置FileProvider

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
FileProvider

${applicationId}.fileprovider这个配置要记牢,后期遇到大坑就靠这个值了。

 xml/file_paths 文件如下: 参考CSDN
 
<!--物理路径相当于Context.getFilesDir() + /path/-->
<files-path name="name" path="path" />

 <!--物理路径相当于Context.getCacheDir() + /path/-->
<cache-path name="name" path="path" /> 
 <!-- 物理路径相当于Environment.getExternalStorageDirectory() + /path/-->
<external-path name="name" path="path" />
 <!-- 物理路径相当于Context.getExternalFilesDir(String) + /path/-->
<external-files-path name="name" path="path" />
 <!-- 物理路径相当于Context.getExternalCacheDir() + /path/-->
<external-cache-path name="name" path="path" />
file_paths

将文件路径转(使用微信下载目录做测试)为URI后,设置到Intent中。

/**
 * 根据类型,加载文件选择器
 */
private void chooseFileWithPath() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    //跳转指定路径,如果路径不存在,切到sdcard
    //需要读权限
    String path = getSDPath();
    if (!TextUtils.isEmpty(path)) {
        //使用微信下载目录测试
        path = path + File.separator + "tencent/MicroMsg/Download";
        File file = new File(path);
        if (file.exists()) {
            //主要代码
            intent.setDataAndType(FileUtil.getUriFromFile(this, new File(path)), "*/*");
        } else {
            intent.setType("*/*");
        }
    } else {
        intent.setType("*/*");
    }
    startActivityForResult(intent, REQUEST_CODE_FILE);
}
根据类型,加载文件选择器

主要生效代码:

Intent intent = new Intent(action,uri);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_CODE_FILE);

或者

Intent intent = new Intent(action);
intent.setDataAndType(uri, "*/*");
startActivityForResult(intent, REQUEST_CODE_FILE);
特别注意:
指定目录这种方式调起,在原生的管理器没有其他注意的,但是如果用户使用三方的管理器,例如QQ浏览器,那么在进入到指定目录下,不可以执行返回操作。
例如:
如果路径设置的是/sdcard/tencent/MicroMsg/Download,在进入download目录下,如果该目录下没有文件,那么想返回到其上层目录MicroMsg,是不可以的。

4. 设置多种文件类型

通过intent.setType()方式设置的文件类型,只能生效一次,所以如果想只选择doc、excel、pdt、ppt等办公文档,过滤掉其他文件,就不能使用intent .setType()方式,而是使用Intent.EXTRA_MIME_TYPES来传值。

final String DOC = "application/msword";
final String XLS = "application/vnd.ms-excel";
final String PPT = "application/vnd.ms-powerpoint";
final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
final String XLSX = "application/x-excel";
final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
final String PDF = "application/pdf";

/**
 * 多种文件类型
*/
private void chooseMoreTypes() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    String[] mimeTypes = {DOC, DOCX, PDF, PPT, PPTX, XLS, XLS1, XLSX};
    intent.setType("application/*");

    intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
    startActivityForResult(intent, REQUEST_CODE_FILE);
}
多种文件类型

以上是准备工作,调起文件管理器进行选择文件,对于一个Android开发者来说,以上步骤只是相当于一小步,不要忘记适配。

接下来才是长征路。


5. URI转路径

在这一步主要解决的是将返回的URI转换为File,大坑也往往就在这一步出现。

问题1:Android 7.0以上获取文件地址,报错 column ‘_data’ does not exist. Available columns: []

问题2:在DocumentProvider中我在调试的过程发现有些文件路径不是primary而是以home开头的路径
content://com.android.externalstorage.documents/document/home:A20220419194337xzspj_chenxiaobin.docx

问题3:在DownloadsProvider中uri路径中返回的不是content://com.android.providers.downloads.documents/document/158
这种常规类型的 地址而是返回的是:content://com.android.providers.downloads.documents/document/raw:/storage/emulated/0/Download/Browser/12345_1.0.apk
。。。
问题

上代码

  1 import android.annotation.SuppressLint;
  2 import android.content.ContentResolver;
  3 import android.content.ContentUris;
  4 import android.content.Context;
  5 import android.content.CursorLoader;
  6 import android.database.Cursor;
  7 import android.net.Uri;
  8 import android.os.Build;
  9 import android.os.Environment;
 10 import android.provider.DocumentsContract;
 11 import android.provider.MediaStore;
 12 import android.text.TextUtils;
 13 import android.util.Log;
 14 
 15 import java.io.File;
 16 import java.io.InputStream;
 17 import java.util.Locale;
 18 
 19 import cn.hutool.core.io.FileUtil;
 20 
 21 public class FileUtils {
 22 
 23     /**
 24      * 根据Uri获取文件绝对路径
 25      *
 26      * @param context
 27      * @param fileUri
 28      * @return
 29      */
 30     public static String getPath(Context context, Uri fileUri) {
 31         if (context == null || fileUri == null) {
 32             return null;
 33         }
 34         String realPath;
 35         // SDK < API11
 36         if (Build.VERSION.SDK_INT < 11) {
 37             realPath = FileUtils.getRealPathFromURI_BelowAPI11(context, fileUri);
 38         }
 39         // SDK >= 11 && SDK < 19
 40         else if (Build.VERSION.SDK_INT < 19) {
 41             realPath = FileUtils.getRealPathFromURI_API11to18(context, fileUri);
 42         }
 43         // SDK > 19 (Android 4.4) and up
 44         else {
 45             realPath = FileUtils.getRealPathFromURI_API19(context, fileUri);
 46         }
 47         return realPath;
 48     }
 49 
 50 
 51     @SuppressLint("NewApi")
 52     public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
 53         String[] proj = {MediaStore.Images.Media.DATA};
 54         String result = null;
 55 
 56         CursorLoader cursorLoader = new CursorLoader(context, contentUri, proj, null, null, null);
 57         Cursor cursor = cursorLoader.loadInBackground();
 58 
 59         if (cursor != null) {
 60             int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
 61             cursor.moveToFirst();
 62             result = cursor.getString(column_index);
 63             cursor.close();
 64         }
 65         return result;
 66     }
 67 
 68     public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
 69         String[] proj = {MediaStore.Images.Media.DATA};
 70         Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
 71         int column_index = 0;
 72         String result = "";
 73         if (cursor != null) {
 74             column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
 75             cursor.moveToFirst();
 76             result = cursor.getString(column_index);
 77             cursor.close();
 78             return result;
 79         }
 80         return result;
 81     }
 82 
 83     @SuppressLint("NewApi")
 84     public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
 85 
 86         final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
 87         // DocumentProvider
 88         if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
 89             // ExternalStorageProvider
 90             if (isExternalStorageDocument(uri)) {
 91                 final String docId = DocumentsContract.getDocumentId(uri);
 92                 final String[] split = docId.split(":");
 93                 final String type = split[0];
 94                 // This is for checking Main Memory
 95                 if ("primary".equalsIgnoreCase(type)) {
 96                     if (split.length > 1) {
 97                         return Environment.getExternalStorageDirectory() + "/" + split[1];
 98                     } else {
 99                         return Environment.getExternalStorageDirectory() + "/";
100                     }
101                     // This is for checking SD Card
102                 } else if ("home".equalsIgnoreCase(type)) {
103                     return Environment.getExternalStorageDirectory() + "/documents/" + split[1];
104                 } else {
105                     return "storage" + "/" + docId.replace(":", "/");
106                 }
107             }
108             // DownloadsProvider
109             else if (isDownloadsDocument(uri)) {
110                 String fileName = getFilePath(context, uri);
111                 if (fileName != null) {
112                     return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
113                 }
114 
115                 String id = DocumentsContract.getDocumentId(uri);
116                 if (id.startsWith("raw:")) {
117                     id = id.replaceFirst("raw:", "");
118                     File file = new File(id);
119                     if (file.exists())
120                         return id;
121                 }
122 
123                 final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
124                 return getDataColumn(context, contentUri, null, null);
125             }
126             // MediaProvider
127             else if (isMediaDocument(uri)) {
128                 final String docId = DocumentsContract.getDocumentId(uri);
129                 final String[] split = docId.split(":");
130                 final String type = split[0];
131                 Uri contentUri = MediaStore.Files.getContentUri("external");
132                 if ("image".equals(type)) {
133                     contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
134                 } else if ("video".equals(type)) {
135                     contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
136                 } else if ("audio".equals(type)) {
137                     contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
138                 }
139 
140                 final String selection = "_id=?";
141                 final String[] selectionArgs = new String[]{split[1]};
142                 return getDataColumn(context, contentUri, selection, selectionArgs);
143             }
144         }
145         // MediaStore (and general)
146         else if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) {
147             // Return the remote address
148             if (isGooglePhotosUri(uri)) {
149                 return uri.getLastPathSegment();
150             }
151 
152             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
153                 return getFilePathFromURI(context, uri);
154             } else {
155                 return getDataColumn(context, uri, null, null);
156             }
157         }
158         // File
159         else if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
160             return uri.getPath();
161         }
162 
163         return null;
164     }
165 
166     private static String getFilePathFromURI(Context context, Uri contentUri) {
167         File rootDataDir = context.getFilesDir();
168         String fileName = getFileName(contentUri);
169         if (TextUtils.isEmpty(fileName)) {
170             return null;
171         }
172         return copyFile(context, contentUri, rootDataDir + File.separator + fileName);
173     }
174 
175     /**
176      * 获取文件名称
177      *
178      * @param uri
179      * @return
180      */
181     private static String getFileName(Uri uri) {
182         if (uri == null) {
183             return null;
184         }
185         String fileName = null;
186         String path = uri.getPath();
187         assert path != null;
188         int cut = path.lastIndexOf('/');
189         if (cut != -1) {
190             fileName = path.substring(cut + 1);
191         }
192         return fileName;
193     }
194 
195     private static String copyFile(Context context, Uri srcUri, String dstFilePath) {
196         File dstFile = new File(dstFilePath);
197         try (InputStream inputStream = context.getContentResolver().openInputStream(srcUri);) {
198             if (inputStream == null) {
199                 return null;
200             }
201             FileUtil.writeFromStream(inputStream, dstFile);
202             return dstFile.getAbsolutePath();
203         } catch (Exception e) {
204             Log.e("FileUtils", String.format(Locale.getDefault(), "copyFile: [%s]", e.getMessage()), e);
205         }
206         return null;
207     }
208 
209     /**
210      * 通过游标获取当前文件路径
211      *
212      * @return 路径,未找到返回null
213      */
214     private static String getDataColumn(Context context, Uri uri, String selection,
215                                         String[] selectionArgs) {
216 
217         final String column = MediaStore.Images.Media.DATA;
218         final String[] projection = {column};
219         try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) {
220             if (cursor != null && cursor.moveToFirst()) {
221                 final int index = cursor.getColumnIndexOrThrow(column);
222                 return cursor.getString(index);
223             }
224         } catch (Exception e) {
225             Log.e("FileUtils", String.format(Locale.getDefault(), "getDataColumn: [%s]", e.getMessage()), e);
226         }
227         return null;
228     }
229 
230 
231     public static String getFilePath(Context context, Uri uri) {
232 
233         Cursor cursor = null;
234         final String[] projection = {
235                 MediaStore.MediaColumns.DISPLAY_NAME
236         };
237 
238         try {
239             cursor = context.getContentResolver().query(uri, projection, null, null,
240                     null);
241             if (cursor != null && cursor.moveToFirst()) {
242                 final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
243                 return cursor.getString(index);
244             }
245         } finally {
246             if (cursor != null)
247                 cursor.close();
248         }
249         return null;
250     }
251 
252     /**
253      * @param uri The Uri to check.
254      * @return Whether the Uri authority is ExternalStorageProvider.
255      */
256     private static boolean isExternalStorageDocument(Uri uri) {
257         return "com.android.externalstorage.documents".equals(uri.getAuthority());
258     }
259 
260     /**
261      * @param uri The Uri to check.
262      * @return Whether the Uri authority is DownloadsProvider.
263      */
264     private static boolean isDownloadsDocument(Uri uri) {
265         return "com.android.providers.downloads.documents".equals(uri.getAuthority());
266     }
267 
268     /**
269      * @param uri The Uri to check.
270      * @return Whether the Uri authority is MediaProvider.
271      */
272     private static boolean isMediaDocument(Uri uri) {
273         return "com.android.providers.media.documents".equals(uri.getAuthority());
274     }
275 
276     /**
277      * @param uri The Uri to check.
278      * @return Whether the Uri authority is Google Photos.
279      */
280     private static boolean isGooglePhotosUri(Uri uri) {
281         return "com.google.android.apps.photos.content".equals(uri.getAuthority());
282     }
283 }
FileUtils

 

 
 
参考:https://www.jianshu.com/p/dc560ff4280d
https://blog.csdn.net/jklwan/article/details/91650806
 https://blog.csdn.net/weixin_43840649/article/details/104021296
 https://blog.csdn.net/qq_32114025/article/details/129319259
 https://blog.csdn.net/qq_18571109/article/details/127452296
https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8#file-fileutils-java
 
posted on 2024-04-03 14:20  腾逸  阅读(550)  评论(0编辑  收藏  举报