webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if("http://www.jikedaohang.com/".equals(url)) { view.loadUrl("https://www.baidu.com/"); } return true; } });
①public WebResourceResponse shouldInterceptRequest (WebView view, String url) 【已过时】
②public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
两种方法主要是第二个参数的不同,WebResourceRequest 将能够获取更多的信息,提供了getUrl(),getMethod,getRequestHeaders等方法第一种回调:
mWebView.setWebViewClient(new WebViewClient(){
@Overridepublic WebResourceResponse shouldInterceptRequest(WebView view, String url) {WebResourceResponse response = null ;if (url.contains( "logo" )) {try {InputStream logo = getAssets().open( "logo.png" );response = new WebResourceResponse( "image/png" , "UTF-8" , logo);} catch (IOException e) {e.printStackTrace();} }return response;
}
});
5)处理https请求,为WebView处理ssl证书设置WebView默认是不处理https请求的,需要在WebViewClient子类中重写父类的onReceivedSslError函数
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); // 接受信任所有网站的证书
// handler.cancel(); // 默认操作 不处理
// handler.handleMessage(null); // 可做其他处理
}
});
setWebChromeClient
用来处理Javascript的对话框、网站图标、网站标题以及网页加载进度等
1)显示页面加载进度在WebChromeClient子类中重写父类的onProgressChanged
函数,progress表示当前页面加载的进度,为1至100的整数
webView.setWebChromeClient(new WebChromeClient() { public void onProgressChanged(WebView view, int progress) { setTitle("页面加载中,请稍候..." + progress + "%"); setProgress(progress * 100); if (progress == 100) { //... } } });
2)
加快HTML网页加载完成速度(默认情况html代码下载到WebView后,webkit开始解析网页各个节点,发现有外部样式文件或者外部脚本文件时,会异步发起网络
请求下载文件,但如果在这之前也有解析到image节点,那势必也会发起网络请求下
载相应的图片。在网络情况较差的情况下,过多的网络请求就会造成带宽紧张,影响
到css或js文件加载完成的时间,造成页面空白loading过久。解决的方法就是告诉
WebView先不要自动加载图片,等页面finish后再发起图片加载。)
//1.首先在WebView初始化时添加如下代码 if(Build.VERSION.SDK_INT >= 19) { /*对系统API在19以上的版本作了兼容。因为4.4以上系统在onPageFinished时再恢复图片加载时,如果存在多张图片引用的是相同的src时,会只有一个image标签得到加载,因而对于这样的系统我们就先直接加载。*/ webView.getSettings().setLoadsImagesAutomatically(true); } else { webView.getSettings().setLoadsImagesAutomatically(false); } //2.在WebView的WebViewClient子类中重写onPageFinished()方法添加如下代码: @Override public void onPageFinished(WebView view, String url) { if(!webView.getSettings().getLoadsImagesAutomatically()) { webView.getSettings().setLoadsImagesAutomatically(true); } }
setDownloadListener
通常webview渲染的界面中含有可以下载文件的链接,点击该链接后,应该开始执行下载的操作并保存文件到本地中。
1 ) 创建DownloadListener
class MyDownloadListenter implements DownloadListener{ @Override public void onDownloadStart(String url, String userAgent,String contentDisposition, String mimetype, long contentLength) { //下载任务...,主要有两种方式 //(1)自定义下载任务 //(2)调用系统的download的模块 Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } }
2)给webview加入监听webview.setDownloadListener(new MyDownloadListenter());
goBack()
返回上一浏览页面,通过重写onKeyDown方法实现点击返回键返回上一浏览页面而非退出程序
public boolean onKeyDown(int keyCode, KeyEvent event) { //其中webView.canGoBack()在webView含有一个可后退的浏览记录时返回true if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) { webView.goBack(); return true; } return super.onKeyDown(keyCode, event); } }
WebSettings配置
settings.setJavaScriptEnabled(true);
(2)设置缓存方式,主要有以下几种:
LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据。
LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式。
LOAD_NO_CACHE: 不使用缓存,只从网络获取数据。
LOAD_CACHE_ELSE_NETWORK:只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
(3)开启DOM storage API功能(HTML5 提供的一种标准的接口,主要将键值对存储在本地,在页面加载完毕后可以通过 JavaScript 来操作这些数据。)
settings.setDomStorageEnabled(true);
(4)设置数据库缓存路径
settings.setDatabasePath(cacheDirPath);
(5)设置Application Caches缓存目录
settings.setAppCachePath(cacheDirPath);
(6)设置默认编码
settings.setDefaultTextEncodingName(“utf-8”);
(7)将图片调整到适合webview的大小
settings.setUseWideViewPort(false);
(8)支持缩放
settings.setSupportZoom(true);
(9)支持内容重新布局
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
(10)多窗口
settings.supportMultipleWindows();
(11)设置可以访问文件
settings.setAllowFileAccess(true);
(12)当webview调用requestFocus时为webview设置节点
settings.setNeedInitialFocus(true);
(13)设置支持缩放
settings.setBuiltInZoomControls(true);
(14)支持通过JS打开新窗口
settings.setJavaScriptCanOpenWindowsAutomatically(true);
(15)缩放至屏幕的大小
settings.setLoadWithOverviewMode(true);
(16)支持自动加载图片
settings.setLoadsImagesAutomatically(true);
WebChoromeClient的回调方法列表
辅助WebView处理Javascript的对话框、网站图标、网站标题以及网页加载进度等
(1)监听网页加载进度
onProgressChanged(WebView view, int newProgress)
(2)监听网页标题 : 比如百度页面的标题是“百度一下,你就知道”
onReceivedTitle(WebView view, String title)
(3)监听网页图标
onReceivedIcon(WebView view, Bitmap icon)
Java和JavaScript互调
addJavascriptInterface方式实现与本地js交互(存在漏洞)
- 首先是JS的一段代码:
function javaCallJs(arg){ document.getElementById("content").innerHTML = ("欢迎:"+arg ); }
- 然后是在java中调用JS中的方法
webView.loadUrl("javascript:javaCallJs("+"'"+name+"'"+")");
JS调java
- 配置Javascript接口
webView.addJavascriptInterface(new JSInterface (),"Android");
- 实现Javascript接口类
class JSInterface { @JavascriptInterface public void showToast(String arg){ Toast.makeText(MainActivity.this,arg,Toast.LENGTH_SHORT).show(); } }
- JS中调用java代码
window.Android.showToast(‘JS中传来的参数’)”中的”Android”即
<input type="button" value="点击Android被调用" onclick="window.Android.showToast('JS中传来的参数')"/>
addJavascriptInterface()中指定的,并且JS向java传递了参数,类型
为String。而showToast(String arg)会以Toast的形式弹出此参数。
安全
- 任意代码执行漏洞
- 密码明文存储漏洞
- 域控制不严格漏洞
漏洞产生原因
JS调用Android的其中一个方式是通过addJavascriptInterface接口进行对象映射:
java反射拿到sd卡里的东西解决方案
Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击
在Android 4.2版本之前采用拦截prompt()进行漏洞修复。
让JS调用一Javascript方法:该方法是通过调用prompt()把JS中的信息(含特定标识,方法名称等)传递到Android端;
- 继承 WebView ,重写 addJavascriptInterface 方法,然后在内部自己维护一个对象映射关系的 Map;将需要添加的 JS 接口放入该Map中
- 每次当 WebView 加载页面前加载一段本地的 JS 代码,原理是:
在Android的onJsPrompt()中 ,解析传递过来的信息,再通过反射机制调用Java对象的方法,这样实现安全的JS调用Android代码。
关于Android返回给JS的值:可通过prompt()把Java中方法的处理结果返回到Js中 代码如下javascript:(function JsAddJavascriptInterface_(){ // window.jsInterface 表示在window上声明了一个Js对象 // jsInterface = 注册的对象名 // 它注册了两个方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2) // 如果有返回值,就添加上return if (typeof(window.jsInterface)!='undefined') { console.log('window.jsInterface_js_interface_name is exist!!');} else { window.jsInterface = { // 声明方法形式:方法名: function(参数) onButtonClick:function(arg0) { // prompt()返回约定的字符串 // 该字符串可自己定义 // 包含特定的标识符MyApp和 JSON 字符串(方法名,参数,对象名等) return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]})); }, onImageClick:function(arg0,arg1,arg2) { return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]})); }, }; } } )() // 当JS调用 onButtonClick() 或 onImageClick() 时,就会回调到Android中的 onJsPrompt () // 我们解析出方法名,参数,对象名 // 再通过反射机制调用Java对象的方法searchBoxJavaBridge_接口引起远程代码执行漏洞
漏洞产生原因
- 在Android 3.0以下,Android系统会默认通过
searchBoxJavaBridge_
的Js接口给 WebView 添加一个JS映射对象:searchBoxJavaBridge_
对象- 该接口可能被利用,实现远程任意代码。
解决方案
删除
searchBoxJavaBridge_
接口// 通过调用该方法删除接口 removeJavascriptInterface();
密码明文存储漏洞
问题分析
WebView默认开启密码保存功能 :
mWebView.setSavePassword(true)
- 开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码;
- 如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险
解决方案
关闭密码保存提醒
WebSettings.setSavePassword(false)
域控制不严格漏洞
setAllowFileAccess问题:
// 设置是否允许 WebView 使用 File 协议 webView.getSettings().setAllowFileAccess();
// 默认设置为true,即允许在 File 域下执行任意 JavaScript 代码使用 file 域加载的 js代码能够使用进行同源策略跨域访问,从而导致隐私信息泄露
如果不允许使用 file 协议,则不会存在上述的威胁;
webView.getSettings().setAllowFileAccess(true);
但同时也限制了 WebView 的功能,使其不能加载本地的 html 文件,移动版的 Chrome 默认禁止加载 file 协议的文件
解决方案:
对于不需要使用 file 协议的应用,禁用 file 协议;对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript。setAllowFileAccess(true);setAllowFileAccess(false);
// 禁止 file 协议加载 JavaScript if (url.startsWith("file://") { setJavaScriptEnabled(false); } else { setJavaScriptEnabled(true); }
问题:setAllowFileAccessFromFileURLs()// 设置是否允许通过 file url 加载的 Js代码读取其他的本地文件 webView.getSettings().setAllowFileAccessFromFileURLs(true); // 在Android 4.1前默认允许 // 在Android 4.1后默认禁止
当
AllowFileAccessFromFileURLs()
设置为 true 时,攻击者的JS代码为:<script> function loadXMLDoc() { var arm = "file:///etc/hosts"; var xmlhttp; if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } xmlhttp.onreadystatechange=function() { //alert("status is"+xmlhttp.status); if (xmlhttp.readyState==4) { console.log(xmlhttp.responseText); } } xmlhttp.open("GET",arm); xmlhttp.send(null); } loadXMLDoc(); </script> // 通过该代码可成功读取 /etc/hosts 的内容数据
解决方案:设置
setAllowFileAccessFromFileURLs(false);
当设置成为 false 时,上述JS的攻击代码执行会导致错误,表示浏览器禁止从 file url 中的 javascript 读取其它本地文件。
问题:setJavaScriptEnabled()// 设置是否允许 WebView 使用 JavaScript(默认是不允许) webView.getSettings().setJavaScriptEnabled(true); // 但很多应用(包括移动浏览器)为了让 WebView 执行 http 协议中的 JavaScript,都会主动设置为true,不区别对待是非常危险的。
即使把
setAllowFileAccessFromFileURLs()
和setAllowUniversalAccessFromFileURLs()
都设置为 false,通过 file URL 加载的 javascript 仍然有方法访问其他的本地文件:符号链接跨源攻击优化加载速度// 设置是否允许 WebView 使用 JavaScript(默认是不允许) webView.getSettings().setJavaScriptEnabled(true); // 但很多应用(包括移动浏览器)为了让 WebView 执行 http 协议中的 JavaScript,都会主动设置为true,不区别对待是非常危险的。
即使把
setAllowFileAccessFromFileURLs()
和setAllowUniversalAccessFromFileURLs()
都设置为 false,通过 file URL 加载的 javascript 仍然有方法访问其他的本地文件:符号链接跨源攻击解决方案
对于不需要使用 file 协议的应用,禁用 file 协议;
// 禁用 file 协议;
setAllowFileAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript。
// 需要使用 file 协议
setAllowFileAccess(true);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
// 禁止 file 协议加载 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}腾讯出优化加载速度h5 https://github.com/Tencent/VasSonic
1.提高渲染的优先级// webSettings.setRenderPriority(RenderPriority.HIGH);
2、使把图片加载放在最后来加载渲染
webSettings.setBlockNetworkImage(true);
3,使用硬件加速,该功能在Android 3.0 (API level 11)才加入。
参考:http://developer.android.com/guide/topics/graphics/hardware-accel.html
4、开启缓存
// 开启H5(APPCache)缓存功能
webSettings.setAppCacheEnabled(true);
// webView数据缓存分为两种:AppCache和DOM Storage(Web Storage)。
// 开启 DOM storage 功能
webSettings.setDomStorageEnabled(true);
// 应用可以有数据库
webSettings.setDatabaseEnabled(true);
// 根据网络连接情况,设置缓存模式,
if (UtilTools.isConnected()) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);// 根据cache-control决定是否从网络上取数据
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);// 先查找缓存,没有的情况下从网络获取。
}
// 可以读取文件缓存(manifest生效)
webSettings.setAllowFileAccess(true);
2.换内核1).腾讯浏览服务基于腾讯X5内核解决方案(包括内核和云服务),能够有效解决传统移动web技术面临的普遍问题,同时能极大扩展应用(Hybrid App)内浏览场景的服务能力。SDK提供的JAR包约250K 速度快、省流量等 方便快捷的替换原生的。app启动后需要初始化,有时候X5内核没有初始化完成就需要打开H5页面就会造成页面打不开。某些机型又出现H5页面无法打开
WebVie具体可以参考:http://x5.tencent.com/tbs/guide/sdkInit.html。2).SuperWebviewSuperWebview是APICloud官方推出的另一项重量级API生态产品SuperWebview查看官方文档,更倾向于web开发,不能直接替换WebView3).Crosswalk一款开源的Web引擎,其基于 Chromium/Blink
的应用运行环境,对于混合开发的轻量级应用尤为受欢迎。 它允许Web开发者将他们的应用打包成系统的应用安装包,获得与本地应用一致的体验。
同时也支持多个应用同时使用一个Crosswalk库的共享模式,仅当应用第一次启动并且发现系统还没有相应的Crosswalk库时才提示用户下载安装
目前是大多数情况下开发者将Crosswalk直接嵌入到应用本身。在这种嵌入模式下Web应用开发者可以完全控制Crosswalk的更新。
缺点:apk激增20~50M体积
官网:https://crosswalk-project.org/。
接入遇见的坑:
*1.最低版本要求
根据http://blog.youkuaiyun.com/sslinp/article/details/51607237文章介绍,最低版本是14,我的项目中就是14,于是就按照文章介绍集成,结果一build,报如下错误:
Error:Execution failed for task:app:processArmv7DebugManifest.> Manifest merger failed with multiple errors, see logs
查看log发现是我集成的版本要求最低版本是16。
点击查看最新Crosswalk版本。如下图我集成的是目前最新的版本
*2.适配不同手机处理器版本
不同用户手机处理器下载不同版本。即在build.gradle中的android闭包中声明如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
productFlavors {
armv7 {
ndk {
abiFilters "armeabi-v7a"
}
}
x86 {
ndk {
abiFilters "x86"
}
}
}
|