自从过去几天以来,我一直在努力从WebView上传文件,但没有任何进展.我用谷歌搜索并实现了所有建议的解决方案,但都没有,例如:此处建议的解决方案,等等.
问题:我有一个带有以下代码的HTML页面来上传文件.它在Firefox等桌面浏览器和模拟器/ AVD的内置浏览器中工作正常,当我点击元素呈现的"浏览..."按钮时,浏览器会打开一个对话框,我可以在其中选择要上传的文件.
但是,在android 3.0模拟器/ AVD中,当我点击"选择文件"时,没有任何反应,没有打开文件对话框!
有人可以最早建议一个可能的解决方案.
这是所有Android版本的完整解决方案,我也很难.
public class MyWb extends Activity { /** Called when the activity is first created. */ WebView web; ProgressBar progressBar; private ValueCallbackmUploadMessage; 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 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 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个版本,因为它有图像预览功能,如果你想让它工作使用简单的php上传而不预览.
更新:
请在这里找到棒棒糖设备的解决方案,并感谢gauntface
更新2:
所有Android设备的完整解决方案,直到这里的oreo,这是更高级的版本,你应该调查它,也许它可以帮助.
从HONEYCOMB(API 11)到Marshmallow(API 23)的工作方法
static WebView mWebView; private ValueCallbackmUploadMessage; public ValueCallback 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, ValueCallbackfilePathCallback, 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 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 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); } });
这是我发现有效的唯一解决方案!
WebView webview; private ValueCallbackmUploadMessage; 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 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.它们甚至提供了一种自动生成文件选择器意图的方法,以便它使用输入接受mime类型.
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, ValueCallbackfilePathCallback, 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 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版本,其他答案中提到的私有方法可行.我还没有为KitKat找到一个好的解决方法(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); }
该解决方案也适用于Honeycomb和Ice Cream Sandwich.似乎谷歌推出了一个很酷的新功能(接受属性),忘了实现过载以实现向后兼容.
protected class CustomWebChromeClient extends WebChromeClient { // For Android 3.0+ public void openFileChooser( ValueCallbackuploadMsg, 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 uploadMsg ) { openFileChooser( uploadMsg, "" ); } }
hifarrer的完整解决方案对我很有帮助.
但是,我遇到了许多其他问题 - 支持其他mime类型,列出捕获设备(相机,视频,音频重新编码器),立即打开捕获设备(例如:)...
因此,我制作了一个与默认Web浏览器应用完全相同的解决方案.
我使用了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(ValueCallbackuploadMsg) { openFileChooser(uploadMsg, ""); } // Android 3.0 public void openFileChooser(ValueCallback uploadMsg, String acceptType) { openFileChooser(uploadMsg, "", "filesystem"); } // Android 4.1 public void openFileChooser(ValueCallback 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 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 finalFilePathCallback = filePathCallback; ValueCallback vc = new ValueCallback () { @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 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 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的附加字符串resoruce:
File uploads are disabled. Choose file for upload
如果您使用的是proguard,则可能需要在proguard-project.txt中选择以下选项:
-keepclassmembers class * extends android.webkit.WebChromeClient { public void openFileChooser(...); }
更新#1(2015.09.09)
添加Android 5.0.1兼容性的代码.
我发现有必要public void openFileChooser(ValueCallback
在Android 4.1中定义.然后我跟随Michel Olivier的解决方案.
我实际上设法让文件选择器出现在Kitkat,选择一个图像并获取活动结果中的文件路径,但唯一不能"修复"(导致此解决方法)的是使输入文件填充输出文件数据.
有没有人知道如何从活动中访问输入字段?我正在使用这个例子评论.只是最后一块,墙上的最后一块砖,我只需要放到正确的位置(因为我可以直接从代码触发上传图像文件).
我不是硬核Android开发者所以我会在新手级别上显示代码.我在现有的Activity中创建了一个新的Activity
清单部分
我从这个示例答案创建了我的BrowseActivity类.WebChromeClient()实例看起来基本相同,除了最后一块,触发了选择器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(ValueCallbackuploadMsg) { /* 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 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 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); }}
还有一些东西
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 & 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();
}
最后,一些更多的代码来获取实际的文件路径,在SO上找到代码,我在评论中添加了帖子url,以便作者获得他的工作的学分.
/** * 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 /sf/ask/17360801/ */ @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 /sf/ask/17360801/ */ 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 /sf/ask/17360801/ */ 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 /sf/ask/17360801/ */ 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 /sf/ask/17360801/ */ public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); }
最后,HTML页面需要触发showPicker的新方法(特别是在A4.4上)
这对我有用.也适用于牛轧糖和棉花糖[[
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 { private static final String TAG = MainActivity.class.getSimpleName(); private final static int FCR = 1; WebView webView; private String mCM; private ValueCallbackmUM; private ValueCallback mUMA; @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 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 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 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; } }); } // 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); } 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(); } } }
谷歌自己的浏览器为这个问题提供了这样一个全面的解决方案,它保证了它自己的类:
Android 4.0.4中的openFileChooser实现
Android 4.0.4中的UploadHandler类
找到适合我的解决方案!在文件中再添加一条规则proguard-android.txt
:
-keepclassmembers class * extends android.webkit.WebChromeClient { public void openFileChooser(...); }