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

Android 13中WebView无法打开文件管理器上传图片视频问题

Android 13 WebView 文件上传无法打开文件管理器的修复方案

核心问题分析

  1. 权限请求后无重试逻辑:当前file_permission()在请求权限后直接返回false,导致权限授予后文件选择逻辑不会重新触发。
  2. Intent 兼容性问题:Android 13 对Intent.ACTION_GET_CONTENT的处理更严格,硬编码file_type = "*/*"可能导致文件管理器无法正确响应。
  3. Uri 构建方式过时:使用Uri.fromFile()在部分Android 13设备上会触发安全限制,需适配更合规的Uri生成方式。

具体修复步骤

1. 补充权限请求回调处理

重写onRequestPermissionsResult方法,在权限授予后重新触发文件选择逻辑:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 1) {
        boolean allGranted = true;
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                allGranted = false;
                break;
            }
        }
        if (allGranted && file_path != null) {
            // 重新触发文件选择逻辑
            webView.setWebChromeClient(new WebChromeClient(){
                @Override
                public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
                    return MainActivity.this.onShowFileChooser(webView, filePathCallback, fileChooserParams);
                }
            });
            file_path = null;
        }
    }
}

2. 修复Intent与文件类型处理

将硬编码的file_type替换为fileChooserParams提供的类型,同时改用ACTION_OPEN_DOCUMENT适配Android 13:

// 从fileChooserParams获取文件类型,替代硬编码的*/*
String fileType = "*/*";
if (fileChooserParams.getAcceptTypes().length > 0) {
    fileType = fileChooserParams.getAcceptTypes()[0];
}

// 改用ACTION_OPEN_DOCUMENT替代ACTION_GET_CONTENT
Intent contentSelectionIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType(fileType);
// 支持多文件选择(按需开启)
contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);

3. 替换过时的Uri构建方式

使用FileProvider生成安全Uri(需配置Manifest和路径文件):

// 以拍照为例,替换Uri.fromFile逻辑
if (photoFile != null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        cam_file_data = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile).toString();
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile));
    } else {
        cam_file_data = "file:" + photoFile.getAbsolutePath();
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
    }
}

在AndroidManifest.xml添加FileProvider配置:

<application>
    <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 xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
    <external-files-path name="my_videos" path="Movies" />
</paths>

4. 优化权限检查方法

简化file_permission()逻辑,避免冗余判断:

public boolean file_permission(){
    if(Build.VERSION.SDK_INT >=33){
        boolean hasCamera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
        boolean hasImages = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED;
        boolean hasVideos = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED;
        boolean hasAudio = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_GRANTED;
        if (!hasCamera || !hasImages || !hasVideos || !hasAudio) {
            ActivityCompat.requestPermissions(this, 
                new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_AUDIO}, 1);
            return false;
        }
    } else if (Build.VERSION.SDK_INT >=23){
        boolean hasWrite = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        boolean hasCamera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
        if (!hasWrite || !hasCamera) {
            ActivityCompat.requestPermissions(this, 
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1);
            return false;
        }
    }
    return true;
}

5. 抽离文件选择逻辑为独立方法

将onShowFileChooser中的逻辑抽成独立方法,方便权限授予后复用:

private boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
    if(file_permission()) {
        file_path = filePathCallback;
        Intent takePictureIntent = null;
        Intent takeVideoIntent = null;

        boolean includeVideo = false;
        boolean includePhoto = false;

        paramCheck:
        for (String acceptTypes : fileChooserParams.getAcceptTypes()) {
            String[] splitTypes = acceptTypes.split(", ?+");
            for (String acceptType : splitTypes) {
                switch (acceptType) {
                    case "*/*":
                        includePhoto = true;
                        includeVideo = true;
                        break paramCheck;
                    case "image/*":
                        includePhoto = true;
                        break;
                    case "video/*":
                        includeVideo = true;
                        break;
                }
            }
        }

        if (fileChooserParams.getAcceptTypes().length == 0) {
            includePhoto = true;
            includeVideo = true;
        }

        if (includePhoto) {
            takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if (takePictureIntent.resolveActivity(this.getPackageManager()) != null) {
                File photoFile = null;
                try {
                    photoFile = create_image();
                } catch (IOException ex) {
                    Log.e(TAG, "Image file creation failed", ex);
                }
                if (photoFile != null) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        cam_file_data = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile).toString();
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile));
                    } else {
                        cam_file_data = "file:" + photoFile.getAbsolutePath();
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                    }
                } else {
                    cam_file_data = null;
                    takePictureIntent = null;
                }
            }
        }

        if (includeVideo) {
            takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
            if (takeVideoIntent.resolveActivity(this.getPackageManager()) != null) {
                File videoFile = null;
                try {
                    videoFile = create_video();
                } catch (IOException ex) {
                    Log.e(TAG, "Video file creation failed", ex);
                }
                if (videoFile != null) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        cam_file_data = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", videoFile).toString();
                        takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", videoFile));
                    } else {
                        cam_file_data = "file:" + videoFile.getAbsolutePath();
                        takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(videoFile));
                    }
                } else {
                    cam_file_data = null;
                    takeVideoIntent = null;
                }
            }
        }

        String fileType = "*/*";
        if (fileChooserParams.getAcceptTypes().length > 0) {
            fileType = fileChooserParams.getAcceptTypes()[0];
        }

        Intent contentSelectionIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
        contentSelectionIntent.setType(fileType);
        contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE);

        Intent[] intentArray;
        if (takePictureIntent != null && takeVideoIntent != null) {
            intentArray = new Intent[]{takePictureIntent, takeVideoIntent};
        } else if (takePictureIntent != null) {
            intentArray = new Intent[]{takePictureIntent};
        } else if (takeVideoIntent != null) {
            intentArray = new Intent[]{takeVideoIntent};
        } else {
            intentArray = new Intent[0];
        }

        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
        chooserIntent.putExtra(Intent.EXTRA_TITLE, "File chooser");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
        startActivityForResult(chooserIntent, file_req_code);
        return true;
    } else {
        return false;
    }
}

在WebChromeClient中调用该方法:

webView.setWebChromeClient(new WebChromeClient(){
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
        progress.setProgress(newProgress);
        progress.setVisibility(newProgress == 100 ? View.GONE : View.VISIBLE);
    }

    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        return MainActivity.this.onShowFileChooser(webView, filePathCallback, fileChooserParams);
    }
});

总结

通过补充权限回调逻辑、适配Android 13的Intent行为、替换安全的Uri构建方式,即可解决WebView文件上传在Android 13中无法打开文件管理器的问题。

内容的提问来源于stack exchange,提问作者Abdullah Chowdhury

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 17:45:55