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
相关产品推荐
相关产品推荐

