Android Studio中如何创建带相机选项的文件选择器
带相机入口的文件选择器实现方案
原有代码问题
- 存在大量重复逻辑:两个结果Launcher、两个文件打开方法逻辑完全一致,可合并复用
- MIME类型配置不全:缺少word格式(doc/docx)支持
- 文件路径获取逻辑错误:直接对
content://类型Uri调用new File(uri.toString())无法拿到真实文件 - 未集成相机入口,也没有统一的格式校验逻辑
实现步骤
1. 配置权限与FileProvider
安卓高版本直接使用file://Uri会触发FileUriExposedException,必须配置FileProvider才能访问拍照生成的文件。
- 在
AndroidManifest.xml中添加权限与Provider配置:
<!-- 相机权限 --> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="false" /> <application ...> <!-- FileProvider配置 --> <provider android:name="androidx.core.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> </application>
- 在
res/xml目录下新建file_paths.xml,配置可访问的文件路径:
<?xml version="1.0" encoding="utf-8"?> <paths> <external-files-path name="camera_images" path="Pictures/" /> </paths>
2. 定义全局变量与通用逻辑
替换原来两个重复的ActivityResultLauncher,统一处理文件选择、拍照返回、权限申请逻辑:
// 存储拍照生成的临时文件Uri private Uri cameraImageUri = null; // 记录当前触发选择的目标TextView ID private int currentTargetTvId = -1; // 统一处理文件选择/拍照结果 private final ActivityResultLauncher<Intent> filePickLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() != Activity.RESULT_OK) { resetTempVar(); return; } Intent resultData = result.getData(); Uri targetUri = null; // 拍照返回时data通常为空,直接使用提前生成的cameraImageUri if (resultData == null || resultData.getData() == null) { targetUri = cameraImageUri; } else { targetUri = resultData.getData(); } if (targetUri == null) { resetTempVar(); return; } // 格式校验 if (!checkFileValid(targetUri)) { Toast.makeText(this, "仅支持pdf、word、png、jpeg格式", Toast.LENGTH_SHORT).show(); resetTempVar(); return; } // 获取文件名并赋值给对应TextView String fileName = queryFileName(targetUri); TextView targetTv = findViewById(currentTargetTvId); if (targetTv != null) { targetTv.setText(fileName); } resetTempVar(); } ); // 相机权限申请处理 private final ActivityResultLauncher<String> cameraPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { // 权限通过重新打开选择弹窗 int triggerBtnId = currentTargetTvId == R.id.fileidname ? R.id.btn_pick1 : R.id.btn_pick2; View triggerBtn = findViewById(triggerBtnId); if (triggerBtn != null) openFileDialog(triggerBtn); } else { Toast.makeText(this, "需要相机权限才能拍摄图片", Toast.LENGTH_SHORT).show(); resetTempVar(); } } ); // 重置临时变量 private void resetTempVar() { cameraImageUri = null; currentTargetTvId = -1; }
3. 添加工具方法
实现格式校验、文件名查询的通用方法:
/** * 校验文件格式是否符合要求:pdf、doc/docx、png、jpg/jpeg */ private boolean checkFileValid(Uri uri) { ContentResolver cr = getContentResolver(); String mimeType = cr.getType(uri); if (mimeType == null) return false; // 匹配支持的MIME类型 List<String> allowedMimeList = Arrays.asList( "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "image/png", "image/jpeg" ); if (allowedMimeList.contains(mimeType)) return true; // 兜底校验图片后缀,部分机型返回image/*类型 if (mimeType.startsWith("image/")) { String fileName = queryFileName(uri).toLowerCase(Locale.getDefault()); return fileName.endsWith(".png") || fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"); } return false; } /** * 从Uri查询文件名 */ @SuppressLint("Range") private String queryFileName(Uri uri) { String fileName = null; if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { try (Cursor cursor = getContentResolver().query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } } } if (fileName == null && uri.getPath() != null) { fileName = new File(uri.getPath()).getName(); } return fileName; }
4. 改造文件选择弹窗方法
替换原来的OpenfileDialog、OpenfileDialog1两个重复方法,把相机选项加入系统选择器:
public void openFileDialog(View view) { // 绑定要赋值的目标TextView currentTargetTvId = view.getId() == R.id.btn_pick1 ? R.id.fileidname : R.id.fileidname1; // 组装文件选择Intent Intent fileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); fileIntent.addCategory(Intent.CATEGORY_OPENABLE); String[] supportMimes = { "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "image/png", "image/jpeg" }; fileIntent.setType("*/*"); fileIntent.putExtra(Intent.EXTRA_MIME_TYPES, supportMimes); // 组装相机拍照Intent Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File photoTempFile = null; try { String timeTag = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); File picDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); photoTempFile = File.createTempFile("IMG_"+timeTag, ".jpg", picDir); } catch (IOException e) { e.printStackTrace(); } // 生成拍照文件的Provider Uri if (photoTempFile != null) { cameraImageUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoTempFile); cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri); cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } // 把相机选项加入选择器 Intent chooser = Intent.createChooser(fileIntent, "选择文件/拍照"); chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{cameraIntent}); // 检查相机权限与可用性 if (cameraIntent.resolveActivity(getPackageManager()) != null) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { filePickLauncher.launch(chooser); } else { cameraPermissionLauncher.launch(Manifest.permission.CAMERA); } } else { // 无可用相机时直接打开文件选择 filePickLauncher.launch(fileIntent); } }
最后把布局里两个选择按钮的点击事件都绑定到openFileDialog方法即可,不需要再写两个重复的打开方法。
注意:如果后续需要上传文件,不需要强行查询文件本地真实路径,直接通过
ContentResolver.openInputStream(uri)拿到文件流即可,适配所有Android版本的Uri类型。
内容的提问来源于stack exchange,提问作者Suraj D
相关产品推荐
相关产品推荐

