在WebView中上传文件

自从最近几天以来,我一直在努力从WebView上传文件,并且没有任何进展。 我GOOGLE和实施所有build议的解决scheme,但没有作品,如:在这里提出的解决scheme – Android的webview,文件input字段filechoos不显示 … …, http: //blog.tourizo.com/2009/02/how-to -display-local-file-in-android ….等等。

问题:我有一个带有以下代码的HTML页面来上传文件。 它在Firefox等桌面浏览器以及模拟器/ AVD的内置浏览器中正常工作,也就是说,当单击由元素呈现的“浏览…”button时,浏览器会打开一个对话框,在其中可以select要上传的文件。

但是,在Android 3.0模拟器/ AVD,当我点击“select文件”,没有任何反应,没有文件对话框打开!

<form method="POST" enctype="multipart/form-data"> File to upload: <input type="file" name="uploadfile">&nbsp;&nbsp; <input type="submit" value="Press to Upload..."> to upload the file! </form> 

任何人都可以请最早build议一个可能的解决scheme。

这是所有Android版本的完整解决scheme,我也很难。

 public class MyWb extends Activity { /** Called when the activity is first created. */ WebView web; ProgressBar progressBar; private ValueCallback<Uri> mUploadMessage; private final static int FILECHOOSER_RESULTCODE=1; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if(requestCode==FILECHOOSER_RESULTCODE) { if (null == mUploadMessage) return; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); web = (WebView) findViewById(R.id.webview01); progressBar = (ProgressBar) findViewById(R.id.progressBar1); web = new WebView(this); web.getSettings().setJavaScriptEnabled(true); web.loadUrl("http://www.script-tutorials.com/demos/199/index.html"); web.setWebViewClient(new myWebClient()); web.setWebChromeClient(new WebChromeClient() { //The undocumented magic method override //Eclipse will swear at you if you try to put @Override here // For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); MyWb.this.startActivityForResult(Intent.createChooser(i,"File Chooser"), FILECHOOSER_RESULTCODE); } // For Android 3.0+ public void openFileChooser( ValueCallback uploadMsg, String acceptType ) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); MyWb.this.startActivityForResult( Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE); } //For Android 4.1 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){ mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); MyWb.this.startActivityForResult( Intent.createChooser( i, "File Chooser" ), MyWb.FILECHOOSER_RESULTCODE ); } }); setContentView(web); } public class myWebClient extends WebViewClient { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // TODO Auto-generated method stub super.onPageStarted(view, url, favicon); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // TODO Auto-generated method stub view.loadUrl(url); return true; } @Override public void onPageFinished(WebView view, String url) { // TODO Auto-generated method stub super.onPageFinished(view, url); progressBar.setVisibility(View.GONE); } } //flipscreen not loading again @Override public void onConfigurationChanged(Configuration newConfig){ super.onConfigurationChanged(newConfig); } // To handle "Back" key press event for WebView to go back to previous screen. /*@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK) && web.canGoBack()) { web.goBack(); return true; } return super.onKeyDown(keyCode, event); }*/ } 

另外我想补充一点,像这个例子中的“上传页面”,不会在<4版本上工作,因为它具有图像预览function,如果你想使它工作使用一个简单的PHP上传,而无需预览。

更新

请在这里find棒棒糖设备的解决scheme,感谢gauntface

更新2

find所有的Android设备,包括棉花糖完整的解决scheme在这里看到它的github项目更多。

从HONEYCOMB(API 11)到棉花糖(API 23)的工作方法

 static WebView mWebView; private ValueCallback<Uri> mUploadMessage; public ValueCallback<Uri[]> uploadMessage; public static final int REQUEST_SELECT_FILE = 100; private final static int FILECHOOSER_RESULTCODE = 1; 

修改onActivityResult()

 @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (requestCode == REQUEST_SELECT_FILE) { if (uploadMessage == null) return; uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent)); uploadMessage = null; } } else if (requestCode == FILECHOOSER_RESULTCODE) { if (null == mUploadMessage) return; // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment // Use RESULT_OK only if you're implementing WebView inside an Activity Uri result = intent == null || resultCode != MainActivity.RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else Toast.makeText(getActivity().getApplicationContext(), "Failed to Upload Image", Toast.LENGTH_LONG).show(); } 

现在在onCreate()onCreateView()粘贴下面的代码

  WebSettings mWebSettings = mWebView.getSettings(); mWebSettings.setJavaScriptEnabled(true); mWebSettings.setSupportZoom(false); mWebSettings.setAllowFileAccess(true); mWebSettings.setAllowFileAccess(true); mWebSettings.setAllowContentAccess(true); mWebView.setWebChromeClient(new WebChromeClient() { // For 3.0+ Devices (Start) // onActivityResult attached before constructor protected void openFileChooser(ValueCallback uploadMsg, String acceptType) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE); } // For Lollipop 5.0+ Devices public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { if (uploadMessage != null) { uploadMessage.onReceiveValue(null); uploadMessage = null; } uploadMessage = filePathCallback; Intent intent = fileChooserParams.createIntent(); try { startActivityForResult(intent, REQUEST_SELECT_FILE); } catch (ActivityNotFoundException e) { uploadMessage = null; Toast.makeText(getActivity().getApplicationContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show(); return false; } return true; } //For Android 4.1 only protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { mUploadMessage = uploadMsg; Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE); } protected void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); } }); 

这是我发现的唯一解决scheme!

 WebView webview; private ValueCallback<Uri> mUploadMessage; private final static int FILECHOOSER_RESULTCODE = 1; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (requestCode == FILECHOOSER_RESULTCODE) { if (null == mUploadMessage) return; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } } // Next part class MyWebChromeClient extends WebChromeClient { // The undocumented magic method override // Eclipse will swear at you if you try to put @Override here public void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); Cv5appActivity.this.startActivityForResult( Intent.createChooser(i, "Image Browser"), FILECHOOSER_RESULTCODE); } } 

在5.0 Lollipop中,Google添加了一个官方的方法WebChromeClient.onShowFileChooser 。 他们甚至提供了一种自动生成文件select器意图的方法,以便使用input接受MIMEtypes。

 public class MyWebChromeClient extends WebChromeClient { // reference to activity instance. May be unnecessary if your web chrome client is member class. private MyActivity activity; public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { // make sure there is no existing message if (myActivity.uploadMessage != null) { myActivity.uploadMessage.onReceiveValue(null); myActivity.uploadMessage = null; } myActivity.uploadMessage = filePathCallback; Intent intent = fileChooserParams.createIntent(); try { myActivity.startActivityForResult(intent, MyActivity.REQUEST_SELECT_FILE); } catch (ActivityNotFoundException e) { myActivity.uploadMessage = null; Toast.makeText(myActivity, "Cannot open file chooser", Toast.LENGTH_LONG).show(); return false; } return true; } } public class MyActivity extends ... { public static final int REQUEST_SELECT_FILE = 100; public ValueCallback<Uri[]> uploadMessage; protected void onActivityResult(int requestCode, int resultCode, Intent data){ if (requestCode == REQUEST_SELECT_FILE) { if (uploadMessage == null) return; uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data)); uploadMessage = null; } } } } 

对于KitKat之前的Android版本,其他答案中提到的私有方法工作。 我还没有findKitKat(4.4)的一个很好的解决方法。

我发现我需要3个接口定义来处理各种版本的android。

 public void openFileChooser(ValueCallback < Uri > uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); FreeHealthTrack.this.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILECHOOSER_RESULTCODE); } public void openFileChooser(ValueCallback < Uri > uploadMsg, String acceptType) { openFileChooser(uploadMsg); } public void openFileChooser(ValueCallback < Uri > uploadMsg, String acceptType, String capture) { openFileChooser(uploadMsg); } 

此解决scheme也适用于蜂窝和冰淇淋三明治。 看起来像谷歌推出了一个很酷的新function(接受属性),忘了实现向后兼容的过载。

 protected class CustomWebChromeClient extends WebChromeClient { // For Android 3.0+ public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) { context.mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); context.startActivityForResult( Intent.createChooser( i, "File Chooser" ), MainActivity.FILECHOOSER_RESULTCODE ); } // For Android < 3.0 public void openFileChooser( ValueCallback<Uri> uploadMsg ) { openFileChooser( uploadMsg, "" ); } } 

hifarrer的完整解决scheme对我非常有帮助。

但是,我遇到了很多其他问题 – 支持其他MIMEtypes,列表捕获设备(摄像头,video,audiorecoder),立即打开捕获设备(例如:<input accept =“image / *; capture”>)…

所以,我做了一个解决scheme,完全相同的默认网页浏览器应用程序。

我使用了android-4.4.3_r1 / src / com / android / browser / UploadHandler.java。 (感谢Rupert Rawnsley)

 package org.mospi.agatenativewebview; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.view.View; import android.webkit.JsResult; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebSettings.PluginState; import android.widget.Toast; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView webView = (WebView) findViewById(R.id.webView1); initWebView(webView); webView.loadUrl("http://google.com"); // TODO input your url } private final static Object methodInvoke(Object obj, String method, Class<?>[] parameterTypes, Object[] args) { try { Method m = obj.getClass().getMethod(method, new Class[] { boolean.class }); m.invoke(obj, args); } catch (Exception e) { e.printStackTrace(); } return null; } private void initWebView(WebView webView) { WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setAllowFileAccess(true); settings.setDomStorageEnabled(true); settings.setCacheMode(WebSettings.LOAD_NO_CACHE); settings.setLoadWithOverviewMode(true); settings.setUseWideViewPort(true); settings.setSupportZoom(true); // settings.setPluginsEnabled(true); methodInvoke(settings, "setPluginsEnabled", new Class[] { boolean.class }, new Object[] { true }); // settings.setPluginState(PluginState.ON); methodInvoke(settings, "setPluginState", new Class[] { PluginState.class }, new Object[] { PluginState.ON }); // settings.setPluginsEnabled(true); methodInvoke(settings, "setPluginsEnabled", new Class[] { boolean.class }, new Object[] { true }); // settings.setAllowUniversalAccessFromFileURLs(true); methodInvoke(settings, "setAllowUniversalAccessFromFileURLs", new Class[] { boolean.class }, new Object[] { true }); // settings.setAllowFileAccessFromFileURLs(true); methodInvoke(settings, "setAllowFileAccessFromFileURLs", new Class[] { boolean.class }, new Object[] { true }); webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); webView.clearHistory(); webView.clearFormData(); webView.clearCache(true); webView.setWebChromeClient(new MyWebChromeClient()); // webView.setDownloadListener(downloadListener); } UploadHandler mUploadHandler; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (requestCode == Controller.FILE_SELECTED) { // Chose a file from the file picker. if (mUploadHandler != null) { mUploadHandler.onResult(resultCode, intent); } } super.onActivityResult(requestCode, resultCode, intent); } class MyWebChromeClient extends WebChromeClient { public MyWebChromeClient() { } private String getTitleFromUrl(String url) { String title = url; try { URL urlObj = new URL(url); String host = urlObj.getHost(); if (host != null && !host.isEmpty()) { return urlObj.getProtocol() + "://" + host; } if (url.startsWith("file:")) { String fileName = urlObj.getFile(); if (fileName != null && !fileName.isEmpty()) { return fileName; } } } catch (Exception e) { // ignore } return title; } @Override public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { String newTitle = getTitleFromUrl(url); new AlertDialog.Builder(MainActivity.this).setTitle(newTitle).setMessage(message).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }).setCancelable(false).create().show(); return true; // return super.onJsAlert(view, url, message, result); } @Override public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { String newTitle = getTitleFromUrl(url); new AlertDialog.Builder(MainActivity.this).setTitle(newTitle).setMessage(message).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { result.cancel(); } }).setCancelable(false).create().show(); return true; // return super.onJsConfirm(view, url, message, result); } // Android 2.x public void openFileChooser(ValueCallback<Uri> uploadMsg) { openFileChooser(uploadMsg, ""); } // Android 3.0 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { openFileChooser(uploadMsg, "", "filesystem"); } // Android 4.1 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { mUploadHandler = new UploadHandler(new Controller()); mUploadHandler.openFileChooser(uploadMsg, acceptType, capture); } // Android 4.4, 4.4.1, 4.4.2 // openFileChooser function is not called on Android 4.4, 4.4.1, 4.4.2, // you may use your own java script interface or other hybrid framework. // Android 5.0.1 public boolean onShowFileChooser( WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { String acceptTypes[] = fileChooserParams.getAcceptTypes(); String acceptType = ""; for (int i = 0; i < acceptTypes.length; ++ i) { if (acceptTypes[i] != null && acceptTypes[i].length() != 0) acceptType += acceptTypes[i] + ";"; } if (acceptType.length() == 0) acceptType = "*/*"; final ValueCallback<Uri[]> finalFilePathCallback = filePathCallback; ValueCallback<Uri> vc = new ValueCallback<Uri>() { @Override public void onReceiveValue(Uri value) { Uri[] result; if (value != null) result = new Uri[]{value}; else result = null; finalFilePathCallback.onReceiveValue(result); } }; openFileChooser(vc, acceptType, "filesystem"); return true; } }; class Controller { final static int FILE_SELECTED = 4; Activity getActivity() { return MainActivity.this; } } // copied from android-4.4.3_r1/src/com/android/browser/UploadHandler.java ////////////////////////////////////////////////////////////////////// /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // package com.android.browser; // // import android.app.Activity; // import android.content.ActivityNotFoundException; // import android.content.Intent; // import android.net.Uri; // import android.os.Environment; // import android.provider.MediaStore; // import android.webkit.ValueCallback; // import android.widget.Toast; // // import java.io.File; // import java.util.Vector; // // /** // * Handle the file upload callbacks from WebView here // */ // public class UploadHandler { class UploadHandler { /* * The Object used to inform the WebView of the file to upload. */ private ValueCallback<Uri> mUploadMessage; private String mCameraFilePath; private boolean mHandled; private boolean mCaughtActivityNotFoundException; private Controller mController; public UploadHandler(Controller controller) { mController = controller; } String getFilePath() { return mCameraFilePath; } boolean handled() { return mHandled; } void onResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) { // Couldn't resolve an activity, we are going to try again so skip // this result. mCaughtActivityNotFoundException = false; return; } Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData(); // As we ask the camera to save the result of the user taking // a picture, the camera application does not return anything other // than RESULT_OK. So we need to check whether the file we expected // was written to disk in the in the case that we // did not get an intent returned but did get a RESULT_OK. If it was, // we assume that this result has came back from the camera. if (result == null && intent == null && resultCode == Activity.RESULT_OK) { File cameraFile = new File(mCameraFilePath); if (cameraFile.exists()) { result = Uri.fromFile(cameraFile); // Broadcast to the media scanner that we have a new photo // so it will be added into the gallery for the user. mController.getActivity().sendBroadcast( new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); } } mUploadMessage.onReceiveValue(result); mHandled = true; mCaughtActivityNotFoundException = false; } void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { final String imageMimeType = "image/*"; final String videoMimeType = "video/*"; final String audioMimeType = "audio/*"; final String mediaSourceKey = "capture"; final String mediaSourceValueCamera = "camera"; final String mediaSourceValueFileSystem = "filesystem"; final String mediaSourceValueCamcorder = "camcorder"; final String mediaSourceValueMicrophone = "microphone"; // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder' // or 'microphone' and the default value should be 'filesystem'. String mediaSource = mediaSourceValueFileSystem; if (mUploadMessage != null) { // Already a file picker operation in progress. return; } mUploadMessage = uploadMsg; // Parse the accept type. String params[] = acceptType.split(";"); String mimeType = params[0]; if (capture.length() > 0) { mediaSource = capture; } if (capture.equals(mediaSourceValueFileSystem)) { // To maintain backwards compatibility with the previous implementation // of the media capture API, if the value of the 'capture' attribute is // "filesystem", we should examine the accept-type for a MIME type that // may specify a different capture value. for (String p : params) { String[] keyValue = p.split("="); if (keyValue.length == 2) { // Process key=value parameters. if (mediaSourceKey.equals(keyValue[0])) { mediaSource = keyValue[1]; } } } } //Ensure it is not still set from a previous upload. mCameraFilePath = null; if (mimeType.equals(imageMimeType)) { if (mediaSource.equals(mediaSourceValueCamera)) { // Specified 'image/*' and requested the camera, so go ahead and launch the // camera directly. startActivity(createCameraIntent()); return; } else { // Specified just 'image/*', capture=filesystem, or an invalid capture parameter. // In all these cases we show a traditional picker filetered on accept type // so launch an intent for both the Camera and image/* OPENABLE. Intent chooser = createChooserIntent(createCameraIntent()); chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType)); startActivity(chooser); return; } } else if (mimeType.equals(videoMimeType)) { if (mediaSource.equals(mediaSourceValueCamcorder)) { // Specified 'video/*' and requested the camcorder, so go ahead and launch the // camcorder directly. startActivity(createCamcorderIntent()); return; } else { // Specified just 'video/*', capture=filesystem or an invalid capture parameter. // In all these cases we show an intent for the traditional file picker, filtered // on accept type so launch an intent for both camcorder and video/* OPENABLE. Intent chooser = createChooserIntent(createCamcorderIntent()); chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType)); startActivity(chooser); return; } } else if (mimeType.equals(audioMimeType)) { if (mediaSource.equals(mediaSourceValueMicrophone)) { // Specified 'audio/*' and requested microphone, so go ahead and launch the sound // recorder. startActivity(createSoundRecorderIntent()); return; } else { // Specified just 'audio/*', capture=filesystem of an invalid capture parameter. // In all these cases so go ahead and launch an intent for both the sound // recorder and audio/* OPENABLE. Intent chooser = createChooserIntent(createSoundRecorderIntent()); chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType)); startActivity(chooser); return; } } // No special handling based on the accept type was necessary, so trigger the default // file upload chooser. startActivity(createDefaultOpenableIntent()); } private void startActivity(Intent intent) { try { mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED); } catch (ActivityNotFoundException e) { // No installed app was able to handle the intent that // we sent, so fallback to the default file upload control. try { mCaughtActivityNotFoundException = true; mController.getActivity().startActivityForResult(createDefaultOpenableIntent(), Controller.FILE_SELECTED); } catch (ActivityNotFoundException e2) { // Nothing can return us a file, so file upload is effectively disabled. Toast.makeText(mController.getActivity(), R.string.uploads_disabled, Toast.LENGTH_LONG).show(); } } } private Intent createDefaultOpenableIntent() { // Create and return a chooser with the default OPENABLE // actions including the camera, camcorder and sound // recorder where available. Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(), createSoundRecorderIntent()); chooser.putExtra(Intent.EXTRA_INTENT, i); return chooser; } private Intent createChooserIntent(Intent... intents) { Intent chooser = new Intent(Intent.ACTION_CHOOSER); chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents); chooser.putExtra(Intent.EXTRA_TITLE, mController.getActivity().getResources() .getString(R.string.choose_upload)); return chooser; } private Intent createOpenableIntent(String type) { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType(type); return i; } private Intent createCameraIntent() { Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File externalDataDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DCIM); File cameraDataDir = new File(externalDataDir.getAbsolutePath() + File.separator + "browser-photos"); cameraDataDir.mkdirs(); mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg"; cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); return cameraIntent; } private Intent createCamcorderIntent() { return new Intent(MediaStore.ACTION_VIDEO_CAPTURE); } private Intent createSoundRecorderIntent() { return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); } } } 

res / values / string.xml的附加stringresoruce:

 <string name="uploads_disabled">File uploads are disabled.</string> <string name="choose_upload">Choose file for upload</string> 

如果你使用的是proguard,你可能需要在proguard-project.txt中的下面选项:

 -keepclassmembers class * extends android.webkit.WebChromeClient { public void openFileChooser(...); } 

UPDATE#1(2015.09.09)

增加了Android 5.0.1兼容性的代码。

我发现有必要在Android 4.1中定义public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) 。 然后我跟着米歇尔·奥利维尔的解决scheme。

我实际上设法让文件select器出现在Kitkat,select一个图像,并得到活动结果中的文件path,但唯一不能“修复”(导致此解决方法)的东西是使input字段填充与文件数据。

有没有人知道如何从一个活动访问input字段? 我使用这个例子评论 。 只是这最后一块,我只需要把正确的地方放在墙上的最后一块砖(我可以直接从代码触发图像文件的上传。

更新#1

我没有硬核Android开发,所以我会在新手级别上显示代码。 我在现有的Activity中创build一个新的Activity

清单部分

 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application android:label="TestApp"> <activity android:name=".BrowseActivity"></activity> </application> 

从这个例子的答案创build我的BrowseActivity类。 WebChromeClient()实例基本上看起来是一样的,除了最后一块,触发select器UI部分…

 private final static int FILECHOOSER_RESULTCODE=1; private final static int KITKAT_RESULTCODE = 2; ... // The new WebChromeClient() looks pretty much the same, except one piece... WebChromeClient chromeClient = new WebChromeClient(){ // For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg) { /* Default code */ } // For Android 3.0+ public void openFileChooser( ValueCallback uploadMsg, String acceptType ) { /* Default code */ } //For Android 4.1, also default but it'll be as example public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){ mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); BrowseActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), BrowseActivity.FILECHOOSER_RESULTCODE); } // The new code public void showPicker( ValueCallback<Uri> uploadMsg ){ // Here is part of the issue, the uploadMsg is null since it is not triggered from Android mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); BrowseActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), BrowseActivity.KITKAT_RESULTCODE); }} 

And some more stuff

 web = new WebView(this); // Notice this part, setting chromeClient as js interface is just lazy web.getSettings().setJavaScriptEnabled(true); web.addJavascriptInterface(chromeClient, "jsi" ); web.getSettings().setAllowFileAccess(true); web.getSettings().setAllowContentAccess(true); web.clearCache(true); web.loadUrl( "http://as3breeze.com/upload.html" ); web.setWebViewClient(new myWebClient()); web.setWebChromeClient(chromeClient); @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { Log.d("Result", "("+requestCode+ ") - (" +resultCode + ") - (" + intent + ") - " + mUploadMessage); if (null == intent) return; Uri result = null; if(requestCode==FILECHOOSER_RESULTCODE) { Log.d("Result","Old android"); if (null == mUploadMessage) return; result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else if (requestCode == KITKAT_RESULTCODE) { Log.d("Result","Kitkat android"); result = intent.getData(); final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); String path = getPath( this, result); File selectedFile = new File(path); //I used you example with a bit of editing so thought i would share, here i added a method to upload the file to the webserver File selectedFile = new File(path); UploadFile(selectedFile); //mUploadMessage.onReceiveValue( Uri.parse(selectedFile.toString()) ); // Now we have the file but since mUploadMessage was null, it gets errors } } public void UploadFile(File selectedFile) { Random rnd = new Random(); String sName = "File" + rnd.nextInt(999999) + selectedFile.getAbsolutePath().substring(selectedFile.getAbsolutePath().lastIndexOf(".")); UploadedFileName = sName; uploadFile = selectedFile; if (progressBar != null && progressBar.isShowing()) { progressBar.dismiss(); } // prepare for a progress bar dialog progressBar = new ProgressDialog(mContext); progressBar.setCancelable(true); progressBar.setMessage("Uploading File"); progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressBar.show(); new Thread() { public void run() { int serverResponseCode; String serverResponseMessage; HttpURLConnection connection = null; DataOutputStream outputStream = null; DataInputStream inputStream = null; String pathToOurFile = uploadFile.getAbsolutePath(); String urlServer = "http://serveraddress/Scripts/UploadHandler.php?name" + UploadedFileName; String lineEnd = "\r\n"; String twoHyphens = "--"; String boundary = "*****"; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1*1024*1024; try { FileInputStream fileInputStream = new FileInputStream(uploadFile); URL url = new URL(urlServer); connection = (HttpURLConnection) url.openConnection(); Log.i("File", urlServer); // Allow Inputs &amp; Outputs. connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(false); // Set HTTP method to POST. connection.setRequestMethod("POST"); connection.setRequestProperty("Connection", "Keep-Alive"); connection.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary); Log.i("File", "Open conn"); outputStream = new DataOutputStream( connection.getOutputStream() ); outputStream.writeBytes(twoHyphens + boundary + lineEnd); outputStream.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + pathToOurFile +"\"" + lineEnd); outputStream.writeBytes(lineEnd); Log.i("File", "write bytes"); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; Log.i("File", "available: " + fileInputStream.available()); // Read file bytesRead = fileInputStream.read(buffer, 0, bufferSize); Log.i("file", "Bytes Read: " + bytesRead); while (bytesRead > 0) { outputStream.write(buffer, 0, bufferSize); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); } outputStream.writeBytes(lineEnd); outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); // Responses from the server (code and message) serverResponseCode = connection.getResponseCode(); serverResponseMessage = connection.getResponseMessage(); Log.i("file repsonse", serverResponseMessage); //once the file is uploaded call a javascript function to verify the user wants to save the image progressBar.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Log.i("start", "File name: " + UploadedFileName); WebView myWebView = (WebView) findViewById(R.id.webview); myWebView.loadUrl("javascript:CheckImage('" + UploadedFileName + "')"); } }); fileInputStream.close(); outputStream.flush(); outputStream.close(); } catch (Exception ex) { Log.i("exception", "Error: " + ex.toString()); } } }.start(); 

}

Lastly, some more code to get the actual file path, code found on SO, ive added post url in comments as well so the author gets credits for his work.

 /** * Get a file path from a Uri. This will get the the path for Storage Access * Framework Documents, as well as the _data field for the MediaStore and * other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @author paulburke * @source https://stackoverflow.com/a/20559175 */ @TargetApi(Build.VERSION_CODES.KITKAT) public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } // TODO handle non-primary volumes } // DownloadsProvider else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } // MediaProvider else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[] { split[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { return getDataColumn(context, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @param selection (Optional) Filter used in the query. * @param selectionArgs (Optional) Selection arguments used in the query. * @return The value of the _data column, which is typically a file path. * @source https://stackoverflow.com/a/20559175 */ public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } finally { if (cursor != null) cursor.close(); } return null; } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. * @source https://stackoverflow.com/a/20559175 */ public static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. * @source https://stackoverflow.com/a/20559175 */ public static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. * @source https://stackoverflow.com/a/20559175 */ public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } 

Lastly, the HTML page needs to trigger that new method of showPicker (specificaly when on A4.4)

 <form id="form-upload" method="post" enctype="multipart/form-data"> <input id="fileupload" name="fileupload" type="file" onclick="javascript:prepareForPicker();"/> </form> <script type="text/javascript"> function getAndroidVersion() { var ua = navigator.userAgent; var match = ua.match(/Android\s([0-9\.]*)/); return match ? match[1] : false; }; function prepareForPicker(){ if(getAndroidVersion().indexOf("4.4") != -1){ window.jsi.showPicker(); return false; } } function CheckImage(name) { //Check to see if user wants to save I used some ajax to save the file if necesarry } </script> 

Google's own browser offers such a comprehensive solution to this problem that it warrants it's own class:

openFileChooser implementation in Android 4.0.4

UploadHandler class in Android 4.0.4

Found a SOLUTION which works for me! Add one more rule in the file proguard-android.txt :

 -keepclassmembers class * extends android.webkit.WebChromeClient { public void openFileChooser(...); } 

This is work for me. Also work for Nugget and Marshmallow [ 2] [ 3]

 import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class MainActivity extends AppCompatActivity{ WebView webView; private static final String TAG = MainActivity.class.getSimpleName(); private String mCM; private ValueCallback<Uri> mUM; private ValueCallback<Uri[]> mUMA; private final static int FCR=1; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent){ super.onActivityResult(requestCode, resultCode, intent); if(Build.VERSION.SDK_INT >= 21){ Uri[] results = null; //Check if response is positive if(resultCode== Activity.RESULT_OK){ if(requestCode == FCR){ if(null == mUMA){ return; } if(intent == null){ //Capture Photo if no image available if(mCM != null){ results = new Uri[]{Uri.parse(mCM)}; } }else{ String dataString = intent.getDataString(); if(dataString != null){ results = new Uri[]{Uri.parse(dataString)}; } } } } mUMA.onReceiveValue(results); mUMA = null; }else{ if(requestCode == FCR){ if(null == mUM) return; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUM.onReceiveValue(result); mUM = null; } } } @SuppressLint({"SetJavaScriptEnabled", "WrongViewCast"}) @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(Build.VERSION.SDK_INT >=23 && (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1); } webView = (WebView) findViewById(R.id.ifView); assert webView != null; WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setAllowFileAccess(true); if(Build.VERSION.SDK_INT >= 21){ webSettings.setMixedContentMode(0); webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); }else if(Build.VERSION.SDK_INT >= 19){ webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); }else if(Build.VERSION.SDK_INT < 19){ webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } webView.setWebViewClient(new Callback()); webView.loadUrl("https://infeeds.com/"); webView.setWebChromeClient(new WebChromeClient(){ //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("*/*"); MainActivity.this.startActivityForResult(Intent.createChooser(i,"File Chooser"), FCR); } // For Android 3.0+, above method not supported in some android 3+ versions, in such case we use this public void openFileChooser(ValueCallback uploadMsg, String acceptType){ mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); MainActivity.this.startActivityForResult( Intent.createChooser(i, "File Browser"), FCR); } //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("*/*"); MainActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), MainActivity.FCR); } //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(); takePictureIntent.putExtra("PhotoPath", mCM); }catch(IOException ex){ Log.e(TAG, "Image file creation failed", ex); } if(photoFile != null){ mCM = "file:" + photoFile.getAbsolutePath(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); }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); startActivityForResult(chooserIntent, FCR); return true; } }); } public class Callback extends WebViewClient{ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){ Toast.makeText(getApplicationContext(), "Failed loading app!", Toast.LENGTH_SHORT).show(); } } // 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 = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); return File.createTempFile(imageFileName,".jpg",storageDir); } @Override public boolean onKeyDown(int keyCode, @NonNull KeyEvent event){ if(event.getAction() == KeyEvent.ACTION_DOWN){ switch(keyCode){ case KeyEvent.KEYCODE_BACK: if(webView.canGoBack()){ webView.goBack(); }else{ finish(); } return true; } } return super.onKeyDown(keyCode, event); } @Override public void onConfigurationChanged(Configuration newConfig){ super.onConfigurationChanged(newConfig); } 

}

In KitKat you can use the Storage Access Framework.

Storage Access Framework / Writing a Client App