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

Android WebView 文件上传功能失效问题咨询

问题分析与解决方案

你的WebView点击上传按钮无响应,核心原因是没有配置WebChromeClient来处理文件上传的请求回调,另外Android 6.0+需要动态申请权限,还有部分系统版本的存储权限适配问题也需要处理。下面是具体的修复步骤:

1. 核心修复:添加WebChromeClient处理文件上传

WebView的文件上传逻辑需要通过WebChromeClient的onShowFileChooser方法来触发系统的文件选择器/相机,你之前只设置了WebViewClient,缺少这部分关键逻辑。

2. 动态申请权限

Android 6.0(API 23)及以上,相机、存储这类危险权限需要在运行时动态申请,只在Manifest里声明是不够的。

3. 处理文件选择结果

需要通过Activity结果回调来获取用户选择的文件,再传递回WebView。

修改后的完整代码

MainActivity.java

package com.pr.social;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;

import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebChromeClient;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient.FileChooserParams;
import android.graphics.Bitmap;
import android.view.View;
import android.widget.ProgressBar;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.MediaStore;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {
    String ShowOrHideWebViewInitialUse = "show";
    private WebView webview;
    private ProgressBar spinner;
    private ValueCallback<Uri[]> filePathCallback;
    private final int REQUEST_PERMISSIONS_CODE = 100;

    // 权限列表
    private final String[] REQUIRED_PERMISSIONS = {
            android.Manifest.permission.CAMERA,
            android.Manifest.permission.READ_EXTERNAL_STORAGE,
            android.Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // Activity结果回调
    private final ActivityResultLauncher<Intent> fileChooserLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if (result.getResultCode() == RESULT_OK && result.getData() != null) {
                    // 处理选择的文件
                    Uri[] results;
                    Uri data = result.getData().getData();
                    if (data != null) {
                        results = new Uri[]{data};
                    } else {
                        results = null;
                    }
                    if (filePathCallback != null) {
                        filePathCallback.onReceiveValue(results);
                        filePathCallback = null;
                    }
                } else {
                    // 用户取消选择,回调null
                    if (filePathCallback != null) {
                        filePathCallback.onReceiveValue(null);
                        filePathCallback = null;
                    }
                }
            }
    );

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        webview = findViewById(R.id.webView);
        spinner = findViewById(R.id.progressBar1);

        // 检查并申请权限
        checkAndRequestPermissions();

        // 配置WebView基础设置
        webview.setWebViewClient(new CustomWebViewClient());
        webview.setWebChromeClient(new CustomWebChromeClient()); // 关键:添加WebChromeClient
        webview.getSettings().setBuiltInZoomControls(true);
        webview.getSettings().setDisplayZoomControls(false);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.getSettings().setDomStorageEnabled(true);
        webview.getSettings().setAllowFileAccess(true); // 允许文件访问
        webview.getSettings().setAllowContentAccess(true);
        webview.setOverScrollMode(WebView.OVER_SCROLL_NEVER);
        webview.loadUrl("https://social.domain.com");
    }

    // 检查权限并申请
    private void checkAndRequestPermissions() {
        boolean allPermissionsGranted = true;
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                allPermissionsGranted = false;
                break;
            }
        }
        if (!allPermissionsGranted) {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_PERMISSIONS_CODE);
        }
    }

    // 权限申请结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSIONS_CODE) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }
            if (!allGranted) {
                // 权限未全部授予,可提示用户
                checkAndRequestPermissions(); // 再次申请或者处理
            }
        }
    }

    // 自定义WebViewClient处理页面加载
    private class CustomWebViewClient extends WebViewClient {
        @Override
        public void onPageStarted(WebView webview, String url, Bitmap favicon) {
            if (ShowOrHideWebViewInitialUse.equals("show")) {
                webview.setVisibility(webview.INVISIBLE);
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            ShowOrHideWebViewInitialUse = "hide";
            spinner.setVisibility(View.GONE);
            view.setVisibility(webview.VISIBLE);
            super.onPageFinished(view, url);
        }
    }

    // 自定义WebChromeClient处理文件上传
    private class CustomWebChromeClient extends WebChromeClient {
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            MainActivity.this.filePathCallback = filePathCallback;

            // 创建文件选择Intent
            Intent intent = fileChooserParams.createIntent();
            // 添加相机选项(如果需要)
            Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if (captureIntent.resolveActivity(getPackageManager()) != null) {
                intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{captureIntent});
            }

            // 启动选择器
            fileChooserLauncher.launch(Intent.createChooser(intent, "选择文件"));
            return true;
        }
    }

    @Override
    public void onBackPressed() {
        if (webview.canGoBack()) {
            webview.goBack();
        } else {
            super.onBackPressed();
        }
    }
}

AndroidManifest.xml 修改

针对Android 10+的存储权限,添加android:requestLegacyExternalStorage="true"(如果你的targetSdkVersion >= 30,也可以改用Scoped Storage,但这个属性可以快速兼容旧逻辑),同时确保Activity的配置正确:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pr.social">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <!-- 针对Android 10+的存储兼容 -->
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Social"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar"
        android:requestLegacyExternalStorage="true"> <!-- 添加这一行 -->

        <activity android:name="com.pr.social.MainActivity"
            android:configChanges="orientation|screenSize|keyboardHidden|keyboard"/> <!-- 补充配置避免页面重启 -->
        <activity android:name="com.pr.social.SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

关键说明

  • WebChromeClient:onShowFileChooser是处理WebView文件上传的核心方法,它会在网页点击上传按钮时被触发,我们在这里创建系统文件选择器并添加相机选项。
  • 动态权限:确保在运行时申请相机和存储权限,否则文件选择器和相机无法正常使用。
  • ActivityResultLauncher:替代旧的onActivityResult方法,更安全地处理Activity结果回调。
  • 存储兼容:android:requestLegacyExternalStorage="true"让App在Android 10+上可以继续使用旧的存储访问逻辑,避免Scoped Storage的适配问题。

现在你重新编译运行App,点击上传按钮应该就能正常唤起相机、图库和文件管理器的选项了。

内容的提问来源于stack exchange,提问作者Khir Hamzi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 14:02:41