前情描述:
使用系统文件管理器,选择指定文件类型上传。
基础知识
- 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 标签:文件,return,String,uri,URI,intent,管理器,null,final From: https://www.cnblogs.com/zt007/p/18112618