基本设置
WebView可以实现将网页嵌入到为应用程序专门设计的环境中。大多数情况还是建议使用标准的网络浏览器将内容交互给用户。WebView缺少一些完全开发的浏览器功能。
private Context mContext;
private WebView mWebView;
/**
* WebView父亲布局
*/
private LinearLayout llContainerWeb;
//可以使用addView的方式,也可以使用xml方式,只要记得必要的时候移除view、清除缓存和置空即可
mWebView = new WebView(mContext);
ViewGroup.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
//把webView加入llContainerWeb
llContainerWeb.addView(mWebView, layoutParams);
//不显示横向滑动条
mWebView.setHorizontalScrollBarEnabled(false);
//不显示纵向滑动条
mWebView.setVerticalScrollBarEnabled(false);
WebSettings webSettings = mWebView.getSettings();
//设置是否启用javascript
webSettings.setJavaScriptEnabled(true);
//是否支持缩放(这个是前提)
webSettings.setSupportZoom(false);
//是否显示缩放工具(当上门那个成立,再设置这个,必要条件)
webSettings.setBuiltInZoomControls(false);
//设置此属性,可任意比例缩放
webSettings.setUseWideViewPort(false);
//设置充满全屏
webSettings.setLoadWithOverviewMode(true);
//把所有内容放到WebView组件等宽的一列中
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
//设置字体默认大小
/*webSettings.setDefaultFontSize(15);*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//设置不允许安全来源从不安全的位置加载内容起源,非常建议使用这个。
//webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
//不建议使用这个,从任何来源加载内容。5.1以上默认禁止了https和http混用 这是开启
//webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
//如下设置是解决Cross origin requests are only supported for protocol schemes: http, data, chrome, https.这种问题的。
//webSettings.setAllowFileAccess(true);
//webSettings.setAllowFileAccessFromFileURLs(true);
//能否访问来自于任何源的文件标识的URL
//webSettings.setAllowUniversalAccessFromFileURLs(true);
//设置WebViewClient,可以拦截url,做一些处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
//拦截url:根据协议的参数,判断是否是所需要的url
//比如约定好的字段为"https://www.haizhuo.com/user?name=hz&id=15"
Uri uri = request.getUrl();
if (uri.getScheme().equals("https")) {
if (uri.getHost().equals("www.haizhuo.com")) {
//todo
}
}
return super.shouldOverrideUrlLoading(view, request);
}
});
} else {
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("https")) {
if (uri.getHost().equals("www.haizhuo.com")) {
//todo
}
}
return super.shouldOverrideUrlLoading(view, url);
}
});
}
//设置WebChromeClient,可以获得js回调、进度、标题、图标等的回调。
mWebView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
});
mWebView.loadUrl("https://www.baidu.com/");
//直接加载assets目录下的html网页,并且与网页有关的css,js,图片等文件会加载。
//mWebView.loadUrl("file:///android_asset/test/test.html");
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode()==KeyEvent.KEYCODE_BACK&&mWebView.canGoBack()){
//获取webView的浏览记录
WebBackForwardList webBackForwardList=mWebView.copyBackForwardList();
//有上一个界面,就跳转上一个界面
if (webBackForwardList.getCurrentIndex()>0){
mWebView.goBack();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
super.onDestroy();
//使用完毕,清除webview缓存和历史,避免内存泄露
mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mWebView.clearHistory();
// mWebView.clearCache(true);
// mWebView.clearSslPreferences();
//不要在android.graphics.Canvas#draw、dispatchDraw、onDraw方法里调用此方法
// webView.removeAllViews();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.destroy();
mWebView = null;
}
Android调用JS代码
注意事项:JS代码调用要在 onPageFinished() 回调之后。
通过WebView的loadUrl()
一般样式
String url = "javascript:methodName(params……)";
webView.loadUrl(url);
例1:调用网页端写好的方法
<script type="text/javascript">
function testPop(){
alert("17张牌你能秒我?");
}
</script>
mWebView.loadUrl("javascript:testPop()");
例2:调用在客户端写的方法(网页端没有)
//设置网页中所有标签名字为‘img’的对象高宽
mWebView.loadUrl("javascript:(function(){" +
"var objs = document.getElementsByTagName('img'); " +
"for(var i=0;i<objs.length;i++) " +
"{"
+ "var img = objs[i]; " +
" img.style.maxWidth = '100%'; img.style.height = 'auto'; " +
"}" +
"})()");
通过WebView的evaluateJavascript()
mWebView.evaluateJavascript("javascript:test()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为返回的结果
}
});
2个方法对比
JS通过WebView调用 Android 代码
不管通过什么方式,双方都需要做一个约定。比如通过WebChromeClient拦截JS对话框消息,当消息是某约定的字段,Android客户端做某个操作;再如通过WebViewClient拦截url,当网页跳转到某个约定的地址时,Android客户端做某个操作;还有WebView方法addJavascriptInterface,也要约定对象映射的名字,这样,js端才能通过这个“名字”的对象调用方法。
1. 通过对象映射
WebView的addJavascriptInterface()。addJavascriptInterface是WebKit的原生API,属于WebView对象的公共方法,用于暴露一个java对象给js,使得js可以直接调用方法。当然,java与js的相互调用也离不开loadUrl()方法的配合使用。
例子
先写一个映射关系的类
class MyJsInterfaceTest{
//API 17以后一定要添加注解,否则addJavascriptInterface方法会失效。
@SuppressLint("JavascriptInterface")
@JavascriptInterface
public String getAppNameFromAndroid(){
return MyApplication.getInstance().getAppName();
}
}
然后设置WebView
webView.getSettings().setJavaScriptEnabled(true);
//...省略部分代码
webView.addJavascriptInterface(new MyJsInterfaceTest(), "myJsTest");
在html里通过映射关系类调用方法
<script type="text/javascript">
//调用Android端代码获取名字,并弹框显示
function getName(){
alert(myJsTest.getAppNameFromAndroid());
}
</script>
//...省略部分代码
<button class="bt_todo_java" id="button_one" onclick="getName()">js调用Android代码</button>
2. 拦截url
WebViewClient 的shouldOverrideUrlLoading ()方法
当即将在当前WebView中加载URL时,使主机应用程序有机会控制。 如果未提供WebViewClient,则默认情况下,WebView将要求“活动管理器”为URL选择适当的处理程序。 如果提供了WebViewClient,则返回true会导致当前WebView放弃加载URL,而返回false会导致WebView照常继续加载URL。
WebView在加载一个页面开始的时候会回调onPageStarted方法,在该页面加载完成之后会回调onPageFinished方法。而如果该链接发生了重定向,回调shouldOverrideUrlLoading会在回调onPageFinished之前。
示例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mWebview.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
//拦截url:根据协议的参数,判断是否是所需要的url
//比如约定好的字段为"https://www.haizhuo.com/user?name=hz&id=15"
Uri uri = request.getUrl();
if (uri.getScheme().equals("https")) {
if (uri.getHost().equals("www.haizhuo.com")) {
//todo
}
}
return super.shouldOverrideUrlLoading(view, request);
}
});
} else {
mWebview.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("https")) {
if (uri.getHost().equals("www.haizhuo.com")) {
//todo
}
}
return super.shouldOverrideUrlLoading(view, url);
}
});
}
shouldOverrideUrlLoading不执行的情况
通常情况,直接与用户交互,跳转的地址,shouldOverrideUrlLoading是能拦截到的。
而延时跳转的话(比如在某个回调里跳转),之后的跳转shouldOverrideUrlLoading都拦截不到了,但会将请求的页面加载到webview中。
以example.html为例
//...省略其他代码
function goToResultPage(){
//本页跳转
window.location.href='http://www.myresult.com.cn';
}
//延时跳转
setTimeout(goToResultPage, 3000);
分析:正常操作到example.html,url都有拦截到,但是跳转到http://www.myresult.com.cn及他以后的地址shouldOverrideUrlLoading都不会有回调了。
3. 拦截JS对话框
WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
除此之外,还有onProgressChanged()、onReceivedTitle()、onReceivedTouchIconUrl()等一些列方法。
上述3方法对比
补充知识点
WebViewClient
WebViewClient这个组件主要用来辅助webview处理一些简单的html页面内容,比如各种通知,请求事件等等,也就是说在用这个组件你可以监听网页加载是否完成,是否开始等等,然后还有就是可以设置是否在外部浏览器中浏览。
onLoadResource、onPageStart、onPageFinish、onReceiveError、onReceivedHttpAuthRequest、shouldOverrideUrlLoading
WebChromeClient
WebChromeClient可以设置标题啥的。
关于在js中打印日志,java中获取打印信息以及提示、打印回调
- 在 Javascript 代码中调用console.log(‘xxx’)方法,中Native代码会回调WebChromeClient.consoleMessage()
- JS中调用window.prompt(message, value),WebChromeClient.onJsPrompt()就会收到回调信息
- JS中调用 alert(“信息”),WebChromeClient.onJsAlert()就会收到回调信息
除上面几个外,再记录一些(不是全部):onReceivedTitle、onReceivedIcon、onReceivedTouchIconUrl、onHideCustomView、onRequestFocus、onCloseWindow、onJsBeforeUnload、onGeolocationPermissionsShowPrompt、onGeolocationPermissionsHidePrompt、onPermissionRequest、onPermissionRequestCanceled、onConsoleMessage、getDefaultVideoPoster、getVisitedHistory等
跨域
什么是跨域?什么是Cross-origin_resource_sharing?
跨域请求存在的原因:由于浏览器的同源策略,即属于不同域的页面之间不能相互访问各自的页面内容。
跨域使用的场景?
- 域名不同
www.jiuyescm.com和www.jiuye.com即为不同的域名 - 二级域名相同,子域名不同
a.jiuyescm.com和b.jiuyescm.com为子域不同 - 端口不同,协议不同
http://www.jiuyescm.com 和 https://www.jiuyescm.com
www.jiuyescm.com:8888和www.jiuyescm.com:8080
解决跨域的方式?
- jsonp
安全性差,已经不推荐 - CORS(W3C标准,跨域资源共享 - Cross-origin resource sharing)
服务端设置,安全性高,推荐使用 - websocke
特殊场景时使用,不属于常规跨域操作 - 代理服务(nginx)
可作为服务端cors配置的一种方式,推荐使用
问题记录
WebView中如何判断网络状态?
- 比如安卓原生开发语言java的SDK里的WebView,对于浏览器的判断网络的方法,比如navigator.onLine,就无法正常返回。而如果你使用跨平台框架,比如Cordova框架构建APP的话,WebView就变得支持那些浏览器方法了。
- 还有一种办法,是通过js调用Java方法,返回网络状态给网页端。
- 直接改系统源码(系统源码browser下下来找到WebView部分,自己封装一个用)可参考apache/cordova-android项目的SystemWebView.java
WebView中如何支持deeplink?
阉割版浏览器WebView,与普通浏览器不同,WebView中重定向到deeplink地址,基本都是报错:网页无法打开 net::ERR_UNKNOWN_URL_SCHEME,解决办法如下:
- 拦截URI自己处理。
- 直接改系统源码(系统源码browser下下来找到WebView部分,自己封装一个用)可参考apache/cordova-android项目的SystemWebView.java