前言:

这里有两个方案,第一个使用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. SQL学习——LIKE运算符

    原文链接 LIKE 作用 在WHERE子句中使用LIKE运算符来搜索列中的指定模式. 有两个通配符与LIKE运算符一起使用: % - 百分号表示零个,一个或多个字符 _ - 下划线表示单个字符 注意: ...

  2. JAVA 1.6锁状态转换

    JVM 学不好 并发就学不好 面试问题 Object 有哪些方法 syn实现过程 wait notify 为什么要设计到Object上而不是接口?虽然可以 但是面向对象的思想 子类 object.wa ...

  3. 【Git】五、远程仓库

    前面4节将的都是本地的git操作,这节开始讲合并到本地分支后,如何与远程仓库做交互 -------------------------------- 提要 //生成本地ssh密钥 $ ssh-keyg ...

  4. Go语言根据数据表自动生成model以及controller代码

    手写model的用法请参考: https://www.jianshu.com/p/f5784b8c00d0 这里仅说明自动生成model文件的过程 bee generate appcode -tabl ...

  5. python 只导出项目依赖包

    平时导出依赖一般都是 pip freeze >  requirements.txt   这种方式导出的是当前python环境中所有的包,只会多不会少,有些库不是必需的也跟着导出来,冗余过重. 这 ...

  6. Linux的bg和fg命令

    我们都知道,在 Windows 上面,我们要么让一个程序作为服务在后台一直运行,要么停止这个服务.而不能让程序在前台后台之间切换.而 Linux 提供了 fg 和 bg 命令,让我们轻松调度正在运行的 ...

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

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

  8. Map的使用及遍历方式

    Java中Map遍历的四种方式 如果同时需要key和value推荐使用entrySet(第一种): 如果只是获取key,或者value,推荐使用keySet或者values方式(第二种): 如果需要在 ...

  9. Linux下批量修改文件编码

    假设需要将所有afish目录下的php文件,编码从gb2312转到utf8 cd afish find ./ -type f -name “*.php”|while read line;do echo ...

  10. EasyUi Datagrid中footer renderFooter

    默认的'rowStyler' 选项不支持footer,想让footer支持rowStyler的话,dategird就得重写.代码如下. var myview = $.extend({}, $.fn.d ...