Android面试系列文章2018之Android部分WebView篇

Android面试系列文章2018之Android部分WebView篇

Android面试系列2018知识总结:
http://blog.csdn.net/ClAndEllen/article/details/79257663

WebView知识体系图

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

7.Hybrid框架

  什么是Hybrid?

  VasSonic介绍

  VasSonic用法

  rexxar-android

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值