You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 12:06:18