Android 内嵌WebView之选择文件上传及填坑记录
前言
今天老大给我提了个醒,年前的一个问题还没解决…然而我早已忘记… 既然说了,那么咱就研究研究这问题。
问题是关于内嵌webView时网页内无法调用系统的拍照功能/文件选择功能。于是我写起一个demo试试,发现确实无法调起来,而浏览器上可以正常使用,恩…那就是我们支持的不够咯~
这不,发现带有input type = file标签的Html页面通过webVie显示就无法正常功能做,那么我们要做的是在webView中增加对这类js的支持…
实现
1.我们先写一个简单的demo, 通过webView打开一个有调用打开文件功能的网页(文末附源码)
WebView mWeb = (WebView) findViewById(R.id.webView);
WebSettings settings = mWeb.getSettings();
mWeb.requestFocusFromTouch();
settings.setJavaScriptEnabled(true); //支持js
settings.setAllowFileAccess(true); //设置可以访问文件
mWeb.loadUrl("file:///android_asset/getFile.html");
开发中遇到的问题
点击打开webView时跳转至系统自带的浏览器/第三方浏览器
解决办法:覆盖WebView的WebViewClient对象。
mWeb.setWebViewClient(new WebViewClient(){
public boolean shouldOverrideUrlLoading(WebView view, String url){
view.loadUrl(url);
return true;
}
});
同时扩展一下:在不做任何处理下按系统返回键,整个Browser回调用finis()销毁,而一般我们是希望按历史记录逐步回退,那么这个时候我们需要在当前Activity中消费掉Back事件。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mWeb.canGoBack()){
mWeb.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
注:这里我们将Html文件放到assets目录下
功能的具体实现
通过Google大法终于找出常用的解决办法。
【重写WebVie的webChromeClien覆盖OpenFileChooser方法】
Android在不同版本中调用相机的方法不同:
Android 3.0 以下
public void openFileChooser(ValueCallback<Uri> uploadMsg)
Android 3.0以上
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType)
Androi 4.4以下
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
Android 5.0以上
public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams)
代码如下:
mWeb.setWebChromeClient(new MyWebChromeClient());
private class MyWebChromeClient extends WebChromeClient {
// Android 3.0 以下
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooser(uploadMsg, null);
}
// Android 3.0以上
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
openFileChooser(uploadMsg, acceptType, null);
}
// Androi 4.4以下
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
openFileInput(uploadMsg, null, false);
}
// Android 5.0以上
@SuppressWarnings("all")
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
if (Build.VERSION.SDK_INT >= 21) {
final boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多选
openFileInput(null, filePathCallback, allowMultiple);
return true;
} else {
return false;
}
}
}
@SuppressLint("NewApi")
protected void openFileInput(final ValueCallback<Uri> fileUploadCallbackFirst, final ValueCallback<Uri[]> fileUploadCallbackSecond, final boolean allowMultiple) {
//Android 5.0以下版本
if (mFileUploadCallbackLowVersion != null) {
mFileUploadCallbackLowVersion.onReceiveValue(null);
}
mFileUploadCallbackLowVersion = fileUploadCallbackFirst;
//Android 5.0及以上版本
if (mFileUploadCallbackHighVersion != null) {
mFileUploadCallbackHighVersion.onReceiveValue(null);
}
mFileUploadCallbackHighVersion = fileUploadCallbackSecond;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
if (allowMultiple) {
if (Build.VERSION.SDK_INT >= 18) {
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
}
i.setType(mFileTypes);
startActivityForResult(Intent.createChooser(i, "选择文件"), REQUEST_CODE_FILE_PICKER);
}
最后我们在Acitivity的onActivityResult()方法中处理,不管请求是否成功,我们都要在onReceiveValue()传nul,否则html中的j执行一次就不会再执行即我们点击不会在有响应了。
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
if (requestCode == REQUEST_CODE_FILE_PICKER) {
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
//Android 5.0以下版本
if (mFileUploadCallbackLowVersion != null) {
mFileUploadCallbackLowVersion.onReceiveValue(intent.getData());
mFileUploadCallbackLowVersion = null;
} else if (mFileUploadCallbackHighVersion != null) {//Android 5.0及以上版本
Uri[] dataUris = null;
try {
if (intent.getDataString() != null) {
dataUris = new Uri[]{Uri.parse(intent.getDataString())};
} else {
if (Build.VERSION.SDK_INT >= 16) {
if (intent.getClipData() != null) {
final int numSelectedFiles = intent.getClipData().getItemCount();
dataUris = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
dataUris[i] = intent.getClipData().getItemAt(i).getUri();
}
}
}
}
} catch (Exception ignored) {
}
mFileUploadCallbackHighVersion.onReceiveValue(dataUris);
mFileUploadCallbackHighVersion = null;
}
}
} else {
//这里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系统版本下分别持有了
//WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值
//否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效
if (mFileUploadCallbackLowVersion != null) {
mFileUploadCallbackLowVersion.onReceiveValue(null);
mFileUploadCallbackLowVersion = null;
} else if (mFileUploadCallbackHighVersion != null) {
mFileUploadCallbackHighVersion.onReceiveValue(null);
mFileUploadCallbackHighVersion = null;
}
}
}
}
总结
Android及iOS的webview的引擎都是webkit,对H5提供支持。
webview有两个方法:setWebChromeClient 和setWebClientsetWebClient
WebChromeClient:辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等
WebViewClient就是帮助WebView处理各种通知、请求事件的。
WebSetting常用方法总结:
1.setJavaScriptEnabled(true); //支持js
2.setPluginsEnabled(true); //支持插件
3.setUseWideViewPort(false); //将图片调整到适合webview的大小
4.setSupportZoom(true); //支持缩放
5.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); //支持内容重新布
6.supportMultipleWindows(); //多窗口
7.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
8.setAllowFileAccess(true); //设置可以访问文件
9.setNeedInitialFocus(true); //当webview调用requestFocus时为webview设置节点 webview
10. webSettings.setBuiltInZoomControls(true); //设置支持缩放
11.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
12.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
13.setLoadsImagesAutomatically(true); //支持自动加载图片
扩展
现在对于许多APP的开放方案已经从原生开发趋向于混合(Hybrid)开发的方式,我们应该去学习Java与JavaScript的交互;
React Native 结合了 Web 应用和 Native 应用的优势,可以使用 JavaScript 来开发 iOS 和 Android 原生应用。在 JavaScript 中用 React 抽象操作系统原生的 UI 组件,代替 DOM 元素来渲染等。
React Native 使你能够使用基于 JavaScript 和 React 一致的开发体验在本地平台上构建世界一流的应用程序体验。
我们都应该学习学习!