Android面试系列文章2018之Android部分WebView篇
Android面试系列2018知识总结:
http://blog.csdn.net/ClAndEllen/article/details/79257663
1.什么是WebView?
Android WebView在Android平台上是一个特殊的View, 基于webkit引擎、展现web页面的控件,这个类可以被用来在你的app中仅仅显示一张在线的网页,还可以用来开发浏览器。WebView内部实现是采用渲染引擎来展示view的内容,提供网页前进后退,网页放大,缩小,搜索。Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。
现在很多APP都内置了Web网页,比如说很多电商平台,淘宝、京东、聚划算等等。WebView比较灵活,不需要升级客户端,只需要修改网页代码即可。一些经常变化的页面可以用WebView这种方式去加载网页。例如中秋节跟国庆节打开的页面不一样,如果是用WebView显示的话,只修改修改html页面就行,而不需要升级客户端。
Webview功能强大,可以直接使用html文件(本地sdcard/assets目录),还可以直接加载url,使用JavaScript可以html跟原生APP互调。
2.基本用法
webView.loadUrl("http://139.196.35.30:8080/OkHttpTest/apppackage/test.html");//加载url
webView.loadUrl("file:///android_asset/test.html");//加载asset文件夹下html
//方式3:加载手机sdcard上的html页面
webView.loadUrl("content://com.ansen.webview/sdcard/test.html");
//方式4 使用webview显示html代码
webView.loadDataWithBaseURL(null,"<html><head><title> 欢迎您 </title></head>" +
"<body><h2>使用webview显示 html代码</h2></body></html>", "text/html" , "utf-8", null);
一些常用的方法说明:
- void loadUrl(String url):加载网络链接 url
- boolean canGoBack():判断 WebView 当前是否可以返回上一页
- goBack():回退到上一页
- boolean canGoForward():判断 WebView 当前是否可以向前一页
- goForward():回退到前一页
- onPause():类似 Activity 生命周期,页面进入后台不可见状态
- pauseTimers():该方法面向全局整个应用程序的webview,它会暂停所有webview的layout,parsing,- - JavaScript Timer。当程序进入后台时,该方法的调用可以降低CPU功耗。
- onResume():在调用 onPause()后,可以调用该方法来恢复 WebView 的运行。
- resumeTimers():恢复pauseTimers时的所有操作。(注:pauseTimers和resumeTimers 方法必须一起使用,否则再使用其它场景下的 WebView 会有问题)
- destroy():销毁 WebView
- clearHistory():清除当前 WebView 访问的历史记录。
- clearCache(boolean includeDiskFiles):清空网页访问留下的缓存数据。需要注意的时,由于缓存是全局的,所以只要是WebView用到的缓存都会被清空,即便其他地方也会使用到。该方法接受一个参数,从命名即可看出作用。若设为false,则只清空内存里的资源缓存,而不清空磁盘里的。
- reload():重新加载当前请求
- setLayerType(int layerType, Paint paint):设置硬件加速、软件加速
- removeAllViews():清除子view。
- clearSslPreferences():清除ssl信息。
- clearMatches():清除网页查找的高亮匹配字符。
- removeJavascriptInterface(String interfaceName):删除interfaceName 对应的注入对象
- addJavascriptInterface(Object object,String interfaceName):注入 java 对象。
- setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled):设置垂直方向滚动条。
- setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled):设置横向滚动条。
- loadUrl(String url, Map<String, String> additionalHttpHeaders):加载制定url并携带http header数据。
- evaluateJavascript(String script, ValueCallback resultCallback):Api 19 之后可以采用此方法之行 Js。
- stopLoading():停止 WebView 当前加载。
- clearView():在Android 4.3及其以上系统这个api被丢弃了, 并且这个api大多数情况下会有bug,经常不能清除掉之前的渲染数据。官方建议通过loadUrl(“about:blank”)来实现这个功能,阴雨需要重新加载一个页面自然时间会收到影响。
- freeMemory():释放内存,不过貌似不好用。
- clearFormData():清除自动完成填充的表单数据。需要注意的是,该方法仅仅清除当前表单域自动完成填充的表单数据,并不会清除WebView存储到本地的数据。
注意:
- onPause() 尽力尝试暂停可以暂停的任何处理,如动画和地理位置。 不会暂停JavaScript。 要全局暂停JavaScript,可使用pauseTimers。
- onResume() 恢复onPause() 停掉的操作;
- pauseTimers() 暂停所有WebView的布局,解析和JavaScript定时器。 这个是一个全局请求,不仅限于这个WebView。
- resumeTimers() 恢复所有WebView的所有布局,解析和JavaScript计时器,将恢复调度所有计时器.
3.三个必须要说明的类
3.1 WebSettings
WebSettings有什么用处呢?就是对对WebView进行各种配置和管理,我们来看看这个类提供的一些方法吧:
- setJavaScriptEnabled(boolean flag):是否支持 Js 使用。
- setCacheMode(int mode):设置 WebView 的缓存模式。
- setAppCacheEnabled(boolean flag):是否启用缓存模式。
- setAppCachePath(String appCachePath):Android 私有缓存存储,如果你不调用setAppCachePath方法,WebView将不会产生这个目录。
- setSupportZoom(boolean support):是否支持缩放。
- setTextZoom(int textZoom):Sets the text zoom of the page in percent. The default is 100。
- setAllowFileAccess(boolean allow):是否允许加载本地 html 文件/false。
- setDatabaseEnabled(boolean flag):是否开启数据库缓存
- setDomStorageEnabled(boolean flag):是否开启DOM缓存。
- setUserAgentString(String ua):设置 UserAgent 属性。
- setLoadsImagesAutomatically(boolean flag):支持自动加载图片
- setAllowFileAccessFromFileURLs(boolean flag::允许通过 file url 加载的 Javascript 读取其他的本地文件,Android 4.1 之前默认是true,在 Android 4.1 及以后默认是false,也就是禁止。
- setAllowUniversalAccessFromFileURLs(boolean flag):允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源,Android 4.1 之前默认是true,在 Android 4.1 及以后默认是false,也就是禁止如果此设置是允许,则 setAllowFileAccessFromFileURLs 不起做用。
- boolean getLoadsImagesAutomatically():是否支持自动加载图片。
代码示例如下:
//声明WebSettings子类
WebSettings webSettings = webView.getSettings();
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
// 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量)
// 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可
//支持插件
webSettings.setPluginsEnabled(true);
//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
3.2 WebViewClient
WebViewClient有什么用处呢?就是对对WebView进行处理各种通知和请求事件,我们来看看这个类提供的一些方法吧:
- onPageStarted(WebView view, String url, Bitmap favicon):WebView 开始加载页面时回调,一次Frame加载对应一次回调。
- onLoadResource(WebView view, String url):WebView 加载页面资源时会回调,每一个资源产生的一次网络加载,除非本地有当前 url 对应有缓存,否则就会加载。
- shouldInterceptRequest(WebView view, String url):WebView 可以拦截某一次的 request 来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
- shouldInterceptRequest(WebView view, android.webkit.WebResourceRequest request):WebView 可以拦截某一次的 request 来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
- shouldOverrideUrlLoading(WebView view, String url):是否在 WebView 内加载页面。
- onReceivedSslError(WebView view, SslErrorHandler handler, SslError error):WebView ssl 访问证书出错,handler.cancel()取消加载,handler.proceed()对然错误也继续加载。
- onPageFinished(WebView view, String url):WebView 完成加载页面时回调,一次Frame加载对应一次回调。
- onReceivedError(WebView view, int errorCode, String description, String failingUrl):WebView 访问 url 出错。
代码示例如下:
public class SafeWebViewClient extends WebViewClient {
/**
* 当WebView得页面Scale值发生改变时回调
*/
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
}
/**
* 是否在 WebView 内加载页面
*
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
/**
* WebView 开始加载页面时回调,一次Frame加载对应一次回调
*
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
/**
* WebView 完成加载页面时回调,一次Frame加载对应一次回调
*
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
/**
* WebView 加载页面资源时会回调,每一个资源产生的一次网络加载,除非本地有当前 url 对应有缓存,否则就会加载。
*
* @param view WebView
* @param url url
*/
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
/**
* WebView 可以拦截某一次的 request 来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
*
* @param view WebView
* @param request 当前产生 request 请求
* @return WebResourceResponse
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
/**
* WebView 访问 url 出错
*
* @param view
* @param request
* @param error
*/
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
}
/**
* WebView ssl 访问证书出错,handler.cancel()取消加载,handler.proceed()对然错误也继续加载
*
* @param view
* @param handler
* @param error
*/
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
}
}
3.3 WebChromeClient
WebChromeClient有什么用处呢?辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等,我们来看看这个类提供的一些方法吧:
- onConsoleMessage(String message, int lineNumber,String sourceID):输出 Web 端日志。
- onProgressChanged(WebView view, int newProgress):当前 WebView 加载网页进度。
- onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result):处理 JS 中的 Prompt对话框
- onJsAlert(WebView view, String url, String message, JsResult result): Js 中调用 alert() 函数,产生的对话框。
- onReceivedTitle(WebView view, String title):接收web页面的 Title。
- onReceivedIcon(WebView view, Bitmap icon):接收web页面的icon。
代码示例如下:
public class SafeWebChromeClient extends WebChromeClient {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
return super.onConsoleMessage(consoleMessage);
}
/**
* 当前 WebView 加载网页进度
*
* @param view
* @param newProgress
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
/**
* Js 中调用 alert() 函数,产生的对话框
*
* @param view
* @param url
* @param message
* @param result
* @return
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
/**
* 处理 Js 中的 Confirm 对话框
*
* @param view
* @param url
* @param message
* @param result
* @return
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
/**
* 处理 JS 中的 Prompt对话框
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
* @return
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
/**
* 接收web页面的icon
*
* @param view
* @param icon
*/
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
/**
* 接收web页面的 Title
*
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
}
4.与JavaScript交互
4.1 JSBridge
- 1、使用系统方法 addJavascriptInterface 注入 java 对象来实现。
- 2、利用 WebViewClient 中 shouldOverrideUrlLoading (WebView view, String url) 接口,拦截操作。这个就是很多公司在用的 scheme 方式,通过制定url协议,双方各自解析,使用iframe来调用native代码,实现互通。
- 3、利用 WebChromeClient 中的 onJsAlert、onJsConfirm、onJsPrompt 提示接口,同样也是拦截操作。
示例如下:
//开启Js可用
mWebView.getSettings().setJavaScriptEnabled(true);
// 创建要注入的 Java 类
public class NativeInterface {
private Context mContext;
public NativeInterface(Context context) {
mContext = context;
}
@JavascriptInterface
public void hello() {
Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public void hello(String params) {
Toast.makeText(mContext, params, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public String getAndroid() {
Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
return "Android data";
}
}
// WebView 注入即可
mWebView.addJavascriptInterface(new NativeInterface(this), "AndroidNative");
//Js编写
<script>
function callHello(){
AndroidNative.hello();
}
function callHello1(){
AndroidNative.hello('hello Android');
}
function callAndroid(){
var temp = AndroidNative.getAndroid();
console.log(temp);
alert(temp);
}
</script>
Native 调用 Js:mWebView.loadUrl(js);
Js 调用 Native :AndroidNative.getAndroid();
注意的是4.2版本以下会存在漏洞,4.2以上需要添加 @JavascriptInterface 注解才能被调用到,Js 调用方式不变。
4.2 JS注入漏洞
虽然可以通过注入方式来实现 WebView 和 JS 交互,但是实现功能的同时也带了安全问题,通过注入的 Java 类作为桥梁,JS 就可以利用这个漏洞。
常见漏洞
目前已知的 WebView 漏洞有 4 个,分别是:
- 1、CVE-2012-6636,揭露了 WebView 中 addJavascriptInterface 接口会引起远程代码执行漏洞;
- 2、CVE-2013-4710,针对某些特定机型会存在 addJavascriptInterface API 引起的远程代码执行漏洞;
- 3、CVE-2014-1939 爆出 WebView 中内置导出的 “searchBoxJavaBridge_” Java Object 可能被利用,实现远程任意代码;
- 4、CVE-2014-7224,类似于 CVE-2014-1939 ,WebView 内置导出 “accessibility” 和 “accessibilityTraversal” 两个 Java Object 接口,可被利用实现远程任意代码执行。
如何解决漏洞?
1、Android 4.2 以下不要在使用 JavascriptInterface方式,4.2 以上需要添加注解 @JavascriptInterface 才能调用。(这部分和JsBrige 有关,更详细的内容后面会介绍)
2、同1解决;
3、在创建 WebView 时,使用 removeJavascriptInterface 方法将系统注入的 searchBoxJavaBridge_ 对象删除。
4、当系统辅助功能服务被开启时,在 Android 4.4 以下的系统中,由系统提供的 WebView 组件都默认导出 ”accessibility” 和 ”accessibilityTraversal” 这两个接口,这两个接口同样存在远程任意代码执行的威胁,同样的需要通过 removeJavascriptInterface 方法将这两个对象删除。
super.removeJavascriptInterface("searchBoxJavaBridge_");
super.removeJavascriptInterface("accessibility");
super.removeJavascriptInterface("accessibilityTraversal");
以上都是系统机制层面上的漏洞,还有一些是使用 WebView 不挡产生的漏洞。
5、通过 WebSettings.setSavePassword(false) 关闭密码保存提醒功能,防止明文密码存在本地被盗用。
6、WebView 默认是可以使用 File 协议的,也就是 setAllowFileAccess(true),我们应该是主动设置为
setAllowFileAccess(false),防止加载本地文件,移动版的 Chrome 默认禁止加载 file 协议的文件。
setAllowFileAccess(true);//设置为 false 将不能加载本地 html 文件
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
最后:推荐 SafeWebView 这个库中解决了 Android WebView 中 Js 注入漏洞问题,另外还包含了一些异常处理。可以自行下载阅读源码。
5.使用WebView带来的坑
- a.API 16之前版本存在远程代码执行漏洞,该漏洞源自于程序没有正确限制使用WebView.addJavascriptInterface方法,攻击者可以使用Java Reflection API利用该漏洞执行任意Java对象和方法。
- b.WebView的销毁和内存泄漏问题。WebView的完全销毁是件麻烦事,一旦销毁流程不正确,极易容易导致内存泄漏。http://blog.csdn.net/xuguobiao/article/details/51473016
- c.jsbridge:通过javascript构建一个桥,桥的两端一个端是客户端,一端是服务端,它可以让本地端调用远端的web代码,也可以让远端调用本地的代码。
- d.WebViewClient.onPageFinished(不靠谱) --> WebChromeClient.onProgressChanged(靠谱)
- e.后台耗电(性能优化):WebView开启网页时会自己开启线程,如果没有合理的销毁,那么残余线程就会一直运行,so这会非常耗电的,解决方案:有一种暴力方式就是Activity的onDestroy中调用System.exit()方法把虚拟机关闭,也可以结合自己应用的WebView的情况设计出一个温柔的方案。
- f.WebView硬件加速导致的页面渲染问题:WebView硬件加速偶尔导致页面白块,页面闪烁,但是加载速度比较快,解决方案:关闭硬件加速。
- g.WebView的内存泄漏问题:原因->WebView会关联一个Activity,WebView执行的操作是在新线程当中回收,时间Activity没有办法确认,Activity的生命周期和WebView线程生命周期不一致导致WebView一直执行,因为WebView内部持有Activity的引用,导致Activity一直不能被回收,原理类似于匿名内部类持有外部类的引用一样。那么如何解决呢?解决方案如下: (1).独立进程,简单暴力,涉及到进程间通信。(开发过程中常用) (2).动态添加WebView,对传入WebView中使用Context弱引用,动态添加WebView意思在布局中创建一个ViewGroup用来放置WebView,Activity创建add进来,Activity停止时remove掉。
6.WebView缓存原理分析与应用&性能优化
https://www.jianshu.com/p/5e7075f4875f
https://tech.meituan.com/WebViewPerf.html