Android WebView相机拍照上传无响应,无法加载图片至输入框
Android WebView相机拍照上传无响应问题修复
问题描述
开发Android WebView网页应用时,相机拍照后点击发送无响应,无法将图片加载到输入框。排查发现相机拍照时response.getData().getDataString()返回空值,但文件管理器上传功能正常。
问题原因
- 使用
MediaStore.ACTION_IMAGE_CAPTURE并指定MediaStore.EXTRA_OUTPUT时,相机不会通过response.getData()返回Uri,而是直接将图片写入你指定的文件路径,因此response.getData()为空是正常行为。 - Android 7.0+禁止直接使用
file://Uri暴露文件,必须通过FileProvider生成content://格式的Uri,否则会触发安全异常,同时WebView也无法正常访问file://Uri。
解决方案
1. 修复相机Intent的Uri生成逻辑
在onShowFileChooser方法中,替换Uri.fromFile()为FileProvider生成content://Uri,并添加临时权限:
//For Android 5.0+ public boolean onShowFileChooser( WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams){ if(mUMA != null){ mUMA.onReceiveValue(null); } mUMA = filePathCallback; Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if(takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null){ File photoFile = null; try{ photoFile = createImageFile(); }catch(IOException ex){ Log.e(TAG, "Image file creation failed", ex); } if(photoFile != null){ // 使用FileProvider生成content格式的Uri mCM = FileProvider.getUriForFile(MainActivity.this, getPackageName(), photoFile).toString(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse(mCM)); // 授予临时读取权限 takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); }else{ takePictureIntent = null; } } // 其余代码保持不变 }
2. 优化图片存储路径(Android 10+适配)
修改createImageFile方法,使用应用私有存储目录,避免存储权限问题:
// Create an image file private File createImageFile() throws IOException{ @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "img_"+timeStamp+"_"; // 使用应用私有图片目录,Android 10+无需WRITE_EXTERNAL_STORAGE权限 File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File tempFile = File.createTempFile(imageFileName,".jpg",storageDir); return tempFile; }
3. 配置FileProvider路径
在res/xml目录下创建provider_paths.xml,配置私有存储路径的访问权限:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 对应getExternalFilesDir(Environment.DIRECTORY_PICTURES)的路径 --> <external-files-path name="pictures" path="Pictures"/> </paths>
4. 调整Manifest权限
针对Android版本优化权限声明,避免不必要的权限请求:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <!-- Android 10+无需读写外部存储权限,限制到SDK 28 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <uses-permission android:name="android.permission.INTERNET"/> <!-- Android 13+图片权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
5. 确认ActivityResult逻辑
原代码中onActivityResult的空值判断逻辑正确,无需修改,确保当response.getData()为空时,直接使用预先生成的mCM Uri:
if (response.getData() == null) { if (mCM != null) { results = new Uri[]{Uri.parse(mCM)}; } }
修改后的完整MainActivity.java关键代码
// 省略import部分... public class MainActivity extends AppCompatActivity { private WebView webView; PermissionManager permissionManager; private static final String TAG = MainActivity.class.getSimpleName(); private String mCM; private ValueCallback<Uri> mUM; private ValueCallback<Uri[]> mUMA; private ActivityResultLauncher<Intent> imgFinished = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult response) { if (Build.VERSION.SDK_INT >= 21) { Uri[] results = null; if (response.getResultCode() == Activity.RESULT_OK) { if (null == mUMA) { return; } if (response.getData() == null) { if (mCM != null) { results = new Uri[]{Uri.parse(mCM)}; } } else { String dataString = response.getData().getDataString(); if (dataString != null) { results = new Uri[]{Uri.parse(dataString)}; } } } mUMA.onReceiveValue(results); mUMA = null; } else { if (null == mUM) return; Uri result = response == null || response.getResultCode() != RESULT_OK ? null : response.getData().getData(); mUM.onReceiveValue(result); mUM = null; } } } ); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = findViewById(R.id.webView); WebSettings MyWebviewSettings = webView.getSettings(); MyWebviewSettings.setAllowFileAccessFromFileURLs(true); MyWebviewSettings.setAllowUniversalAccessFromFileURLs(true); MyWebviewSettings.setJavaScriptCanOpenWindowsAutomatically(true); MyWebviewSettings.setJavaScriptEnabled(true); MyWebviewSettings.setDomStorageEnabled(true); MyWebviewSettings.setBuiltInZoomControls(true); MyWebviewSettings.setAllowFileAccess(true); MyWebviewSettings.setSupportZoom(true); MyWebviewSettings.setAllowContentAccess(true); MyWebviewSettings.setMediaPlaybackRequiresUserGesture(false); webView.loadUrl("https://hbwfc-acc.9knots.nl/app"); webView.setWebChromeClient(new WebChromeClient() { @Override public void onPermissionRequest(final PermissionRequest request) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { request.grant(request.getResources()); } } //For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg){ mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); imgFinished.launch(Intent.createChooser(i,"File Chooser")); } // For Android 3.0+ public void openFileChooser(ValueCallback uploadMsg, String acceptType){ mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); imgFinished.launch(Intent.createChooser(i, "File Browser")); } //For Android 4.1+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){ mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); imgFinished.launch(Intent.createChooser(i, "File Chooser")); } //For Android 5.0+ public boolean onShowFileChooser( WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams){ if(mUMA != null){ mUMA.onReceiveValue(null); } mUMA = filePathCallback; Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if(takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null){ File photoFile = null; try{ photoFile = createImageFile(); }catch(IOException ex){ Log.e(TAG, "Image file creation failed", ex); } if(photoFile != null){ mCM = FileProvider.getUriForFile(MainActivity.this, getPackageName(), photoFile).toString(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse(mCM)); takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); }else{ takePictureIntent = null; } } Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType("*/*"); Intent[] intentArray; if(takePictureIntent != null){ intentArray = new Intent[]{takePictureIntent}; }else{ intentArray = new Intent[0]; } Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); imgFinished.launch(chooserIntent); return true; } }); webView.setWebViewClient(new WebViewClient()); webView.setWebContentsDebuggingEnabled(true); permissionManager = new PermissionManager() {}; permissionManager.checkAndRequestPermissions(this); } // Create an image file private File createImageFile() throws IOException{ @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "img_"+timeStamp+"_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File tempFile = File.createTempFile(imageFileName,".jpg",storageDir); return tempFile; } }
内容的提问来源于stack exchange,提问作者Gerben Mol
相关产品推荐
相关产品推荐

