前言:

这里有两个方案,第一个使用Andorid客户端和JavaScript互相调用方法来实现,这种方法极力不推荐,它会增加服务端和客户端的开发成本。

第二种就是继承WebViewChromeClient了,WebChromeClient是Html/Js和Android客户端进行交互的一个中间件,其将webview中js所产生的事件封装,然后传递到Android客户端。Google这样做的其中一个很重要的原因就是安全问题。

一,使用Android本地和JS方法互相调用完成文件上传与选择(会增客户端与服务端开发成本,不推荐)

这里我仅仅演示了Andorid客户端和Javascript如何互相调用

1.Html代码

<!doctype html>
<html>
<head><meta charset="UTF-8"><title>Untitled Document</title></head>
<script type="text/javascript"> function javaNoParam(){
Android.showToast();
} function javaWithParam(message){
Android.showToast(message);
} function jsNoParam(){
alert("来自Java调用,无参")
} function jsWithParam(message){
alert("来自Java调用,有参数:"+message)
} </script>
<body>
<p> <input type="button" name="button" id="button" value="调用Java无参函数" onClick="javaNoParam()"></p>
<p> <input type="button" name="button2" id="button2" value="调用Java有参函数" onClick="javaWithParam('有参数')"></p>
<p>&nbsp;</p>
</body>
</html>

2.启动WebView对JavaScript的支持 ,默认不支持。

WebSettings setting = webview.getSettings();setting.setJavaScriptEnable(true);

3.写一个客户端接口供JS端调用

    public class WebAppInterface {

        private Context context;

        public WebAppInterface(Context context) {
this.context = context;
} public void showToast() {
Toast.makeText(context, "js端调用,无参数", Toast.LENGTH_SHORT).show();
} public void showToast(String message) {
Toast.makeText(context, "js端调用,有参数:" + message, Toast.LENGTH_SHORT).show();
} }

4.将WebAppInterface接口设置到WebView中

webview.addJavascriptInterface(new WebAppInterface(this), "Android");

第二个参数是个代号,供JS端调用,有点像JS和客户端碰头的接头暗号:

 function javaWithParam(message){
Android.showToast(message); //需要在addJavascriptInterface(new WebAppInterface(this), "Android")中设定的保持一致 }

设置完以上的部分,就可到达js调用客户端代码的目的

4.Android客户端远程调用JavaScript方法

 webview.loadUrl("javascript:jsNoParam()");
webview.loadUrl("javascript:jsWithParam('" + "Hello!" + "')");

其中jsNoParam()和jsWithParam(param)都是javacript中的方法

上面的全部步骤即可实现Andorid客户端和JavaScript的简单调,但是这样如果应用到实际开发中会增加服务端和客户端的开发成本,每个接口都需要服务端和客户端一起协商开发,这样的在开发模式中耦合性很差,有没有一种东西技能满足web端与客户端交互又能达到开发模式上解耦合?

当然是有,要不然google那帮高帅富们岂不是废了。

二,继承WebChromeCilent,重写WebChromeClient的onFileChooser方法:

1.现提供一个简单的版本,仅仅实现选择文件上传功能

1.Html

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>WebView Test</title>
</head> <script type="text/javascript"> function alertSomething(){
alert("你好")
} function delete_confirm() <!--调用方法-->
{
event.returnValue = confirm("确定 or 取消");
} </script>
<body>
<input type="button" name="button" id="button" value="js提示对话框" onClick="alertSomething()"></p>
<input type="button" name="button2" id="button2" value="js确定or取消对话框" onClick="delete_confirm()"></p> <input type="file" value="" class='zj-up-btn pa' name="uploadfile" id="uploadfile" onchange="form.submit()" /></p> </body>
</html>

Java:继承WebChromeClient重写onFileChooser方法

public class FileSelectionWebActivity extends FragmentActivity {

    private static final int FILE_SELECT_CODE = 0;

    private WebView webView;
private ValueCallback<Uri> mUploadMessage; @Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.selection_file_web_activity);
initWebView(); } @SuppressLint("SetJavaScriptEnabled")
private void initWebView() {
webView = (WebView) findViewById(R.id.fileSelectionWebview);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setBuiltInZoomControls(true); webView.loadUrl("file:///android_asset/selectFileHtml/index.html");
webView.setWebViewClient(new MyWebViewClient(this));
webView.setWebChromeClient(new MyWebChromeClient()); } private class MyWebChromeClient extends WebChromeClient { // 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/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE); } // 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("*/*");
startActivityForResult(Intent.createChooser(i, "File Browser"), FILE_SELECT_CODE);
} // 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/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE); } } private class MyWebViewClient extends WebViewClient {
private Context context; public DuomiWebViewClient(Context context) {
super();
this.context = context;
} @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
} @Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
} @Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
} } // flipscreen not loading again
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
} @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) {
return;
} switch (requestCode) {
case FILE_SELECT_CODE : {
Uri uri = data.getData();
Log.e("Tag", "Path:" + uri.toString());
mUploadMessage.onReceiveValue(uri);
mUploadMessage = null;
}
break;
}
} }

上面的代码只是提供了上传文件的功能,有时候当你想上传图时可能需要拍照上传,或者你想上传各种多媒体类型的文件,怎么办?

其实我们手机的浏览器已经有这些功能了,为何不Reading the fucking source code!

下面提供一个复杂的功能,代码是从浏览器中移植过来的:

拥有的功能:

1.客户端弹出服务端JS对话框

2.能够拍照上传

3.支持主流媒体文件选择

废话不多说,贴代码:

public class WebViewActivity extends FragmentActivity {

    private WebView webview;
private UploadHandler mUploadHandler; @Override
protected void onCreate(Bundle arg0) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(arg0);
setContentView(R.layout.activity_webview); webview = (WebView) findViewById(R.id.webview);
webview.setWebChromeClient(new MyChromeViewClient());
webview.setWebViewClient(new MyWebViewClinet());
// webview.setDownloadListener(new MyDownloadListener()); initWebViewSettings();
initData(); } @SuppressLint({ "SetJavaScriptEnabled", "NewApi" })
private void initWebViewSettings() { WebSettings settings = webview.getSettings();
settings.setDefaultFontSize(50);
settings.setDefaultFixedFontSize(30); settings.setJavaScriptEnabled(true);
settings.setAllowFileAccess(true);
settings.setDomStorageEnabled(true);
settings.setLoadWithOverviewMode(true);
settings.setUseWideViewPort(true);
settings.setSupportZoom(true); // WebView inside Browser doesn't want initial focus to be set.
settings.setNeedInitialFocus(false);
// Browser supports multiple windows
settings.setSupportMultipleWindows(true);
// enable smooth transition for better performance during panning or } private void initData() {
Intent intent = getIntent();
String url = intent.getStringExtra("url");
webview.loadUrl(url); } @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);
} @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK) && webview.canGoBack()) {
webview.goBack();
return true;
} return super.onKeyDown(keyCode, event);
} class MyDownloadListener implements DownloadListener{ @Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,
long contentLength) {
// TODO Auto-generated method stub } } class MyChromeViewClient extends WebChromeClient { @Override
public void onCloseWindow(WebView window) {
WebViewActivity.this.finish();
super.onCloseWindow(window);
} public void onProgressChanged(WebView view, final int progress) { } @Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { new AlertDialog.Builder(WebViewActivity.this).setTitle("提示信息").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;
} @Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { new AlertDialog.Builder(WebViewActivity.this).setTitle("提示信息").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; } // 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);
} } class MyWebViewClinet extends WebViewClient { @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { return true;
} } // copied from android-4.4.3_r1/src/com/android/browser/UploadHandler.java 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;
} public String getFilePath() {
return mCameraFilePath;
} boolean handled() {
return mHandled;
} public 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;
} public 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(), "File uploads are 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, "Choose file for 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);
}
} class Controller { final static int FILE_SELECTED = 4; Activity getActivity() {
return WebViewActivity.this;
}
} }

有几个类要说明下:

MyChromeViewClient 继承WebChromeClient重写了几个关键方法。其中有三个重载方法openFileChooser,用来兼容不同的Andorid版本,以防出现NoSuchMethodError异常。

另外一个类UploadHandler,起到一个解耦合作用,它相当于WebChromeClient和Web网页端的一个搬运工兼职翻译,解析网页端传递给WebChromeClient的动作,然后将onActivityResult接收用户选择的文件传递给司机ValueCallback。WebChromeClient提供了一个Web网页端和客户端交互的通道,而UploadHandler就是用来搬砖的~。

UploadHandler有个很重要的成员变量:ValueCallback<Uri> mUploadMessage。ValueCallback是WebView留下来的一个回调,就像是WebView的司机一样,当WebChromeClient和UploadHandler合作将文件选择后,ValueCallback开始将文件给WebView,告诉WebView开始干活了,砖头已经运回来了,你可以盖房子了。

     

WebView加载html实现网页上传本地文件(图片,拍照,语音等)的更多相关文章

  1. 用java 代码下载Samba服务器上的文件到本地目录以及上传本地文件到Samba服务器

    引入: 在我们昨天架设好了Samba服务器上并且创建了一个 Samba 账户后,我们就迫不及待的想用JAVA去操作Samba服务器了,我们找到了一个框架叫 jcifs,可以高效的完成我们工作. 实践: ...

  2. paramiko模块的安装和使用(含上传本地文件或文件夹到服务器,以及下载服务器文件到本地)

    安装和使用分两步介绍: 介绍一下,本文的运行环境是win7 64位 和python 2.7  . 安装: WIN7_64位 安装python-ssh访问模块(paramiko)的安装教程,本人亲测下面 ...

  3. 上传本地文件到SVN

    前言:今天按照自己的记忆上传本地文件夹到SVN,出现了点问题,重温了简单操作. https://blog.csdn.net/qq_35150366/article/details/81129847 参 ...

  4. git 上传本地文件到github

    git 上传本地文件到github 1 git config --global user.name "Your Real Name" 2 git config --global u ...

  5. 如何用一张图片代替 'input:file' 上传本地文件??

    今天去面试,碰到了一道题,也许是因为紧张或者喝水喝多了,一时竟然没有转过弯来,回来之后一细想原来这么简单,哭笑不得,特此记录一下! 原题是这样的:  如何用一张图片代替 'input:file' 上传 ...

  6. 两种方法上传本地文件到github

    https://www.jianshu.com/p/c70ca3a02087 自从使用github以来,一直都是在github网站在线上传文件到仓库中,但是有时因为网络或者电脑的原因上传失败.最重要的 ...

  7. Linux 将本地文件上传Linux服务器, 即ssh 命令上传本地文件

    利用ssh传输文件   在linux下一般用scp这个命令来通过ssh传输文件. 1.从服务器上下载文件 scp username@servername:/path/filename /var/www ...

  8. Linux 将本地文件上传Linux服务器, 即ssh 命令上传本地文件

    http://blog.csdn.net/rodulf/article/details/71169996 利用ssh传输文件 在linux下一般用scp这个命令来通过ssh传输文件. 1.从服务器上下 ...

  9. 两种方法上传本地文件到github(转)

    自从使用github以来,一直都是在github网站在线上传文件到仓库中,但是有时因为网络或者电脑的原因上传失败.最重要的原因是我习惯本地编辑,完成以后再一起上传github.看过了几个教程,总结出最 ...

随机推荐

  1. 用Leangoo看板进行可视化的缺陷跟踪管理

    转自:https://www.leangoo.com/10464.html 缺陷管理通常关注如下几个方面: 1. 缺陷的处理速度 2. 缺陷处理的状态 3. 缺陷的分布 4. 缺陷产生的原因 使用Le ...

  2. ADF简单介绍

    1.ADF也是用的MVC的分层模式,如下图所示 2.Model层代理数据服务将数据关联在View层,用户则是在View层的UI界面上的操作来更改Model层代理的数据,Controller控制层执行用 ...

  3. 使用openSSL构造一个支持https的nodejs服务器

    首先通过下面的链接下载openSSL https://slproweb.com/products/Win32OpenSSL.html 下载完毕后,执行openssl进入交互式界面: 使用命令生成pri ...

  4. iPhone 11来了

  5. HTML插入地图

    方法/步骤 1.打开“百度地图生成器”的网址:http://api.map.baidu.com/lbsapi/creatmap/index.html 如下图: 2.在“1.定位中心点”中,切换城市,并 ...

  6. Nexus Repository Manager OSS 2 配置阿里云私服做代理的坑

    安装 搭建 Nexus 私服很简单,官网下载,解压: 使用管理员权限打开cmd: > cd nexus---bundle\nexus--\bin > nexus.bat install # ...

  7. TensorFlow指定GPU/CPU进行训练和输出devices信息

    TensorFlow指定GPU/CPU进行训练和输出devices信息 1.在tensorflow代码中指定GPU/CPU进行训练 with tf.device('/gpu:0'): .... wit ...

  8. Ubuntu在命令行安装显卡驱动

    Ubuntu在命令行安装显卡驱动 1.进入电脑的BIOS,把Security Boot设置为Disabled. 2.进入终端,输入一以下命令(这里以安装NVIDIA-390进行演示) sudo add ...

  9. Educational Codeforces Round 37 (Rated for Div. 2)C. Swap Adjacent Elements (思维,前缀和)

    Educational Codeforces Round 37 (Rated for Div. 2)C. Swap Adjacent Elements time limit per test 1 se ...

  10. linux下top命令的使用

    top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器 视图参数含义 top视图分为两部分:操作系统资源概况信息和进程信息.首先分析资源 ...