Android WebView详解

WebView详解

WebView使用

WebSettings常用方法:

方法说明
setAllowFileAccess启用或禁用WebView访问文件数据
setBlockNetworkImage是否显示网络图像
setBuiltInZoomControls设置是否支持缩放
setCacheMode设置缓冲的模式
setDefaultFontSize设置默认的字体大小
setDefaultTextEncodingName设置在解码时时候用的默认编码
setFixedFontFamily设置固定使用的字体
setJavaScriptEnabled设置是否支持JavaScript
setLayoutAlgorithm设置布局方式
setLightTouchEnabled设置用鼠标激活被选项
setSupportZoom设置是否支持变焦

设置将接收各种通知和请求的WebViewClient

方法说明
doUpdateVisitedHistory更新历史记录
onFormResubmission应用程序重新请求网页数据
onLoadResource加载指定地址提供的资源
onPageFinished网页加载完毕
onPageStarted网页开始加载
onReceivedError报告错误信息
onScaleChangedWebView发生改变
shouldOverrideUrlLoading控制新的连接在当前WebView中打开

WebChromeClient常用方法:

方法说明
onCloseWindow关闭WebView
onCreateWindow创建WebView
onJsAlert处理Javascript中的Alert对话框
onJsConfirm处理Javascript中的Confirm对话框
onJsPrompt处理Javascript中的Prompt对话框
onProgressChanged加载进度条改变
onReceivedlcon网页图标更改
onReceivedTitle网页Title更改
onRequestFocus WebView显示焦点

自定义WebView

  • 创建和设置WebChromeClient子类。当一些可能影响浏览器的用户界面发生了,例如,进度更新和JavaScript警报送到这里(见调试任务)调用这个类。
  • 创建和设置WebViewClient子类。当影响内容呈现的事情发生是调用这个类,例如,错误或表单提交。您也可以拦截的URL加载到这里(通过shouldOverrideUrlLoading())。
  • 修改WebSettings,如以setJavaScriptEnabled(boolean arg)方式启用JavaScript。
  • 将Java对象通过addJavascriptInterface(Object,String)方法注射到WebView。 这方法允许您将Java对象注入到一个页面的JavaScript上下文,这样他们可以通过JavaScript访问的页面。

Cookie和窗口管理

可以有自己的缓存和cookie存储;

addJavascriptInterface(Object,String)

该方法可以让web页面调用Android,所以这是危险的。也就是传说中的webview的注入bug

页面导航

当用户单击在WebView上的链接时,默认行为是启动一个处理URL的Android应用。通常默认网页浏览器打开和装在目的URL。但是你可以为WebView覆盖这个行为,以便在你的WebView上打开链接。然后,您可以允许用户前后浏览通过的由您的WebView保留的网页历史记录。
要打开用户点击链接,只是提供一个WebViewClient为您的WebView,使用setWebViewClient()。

mWebView.setWebViewClient(new WebViewClient());  

WebView 漏洞

UXSS漏洞

可以越过同源策略,获得任意网页的Cookie等信息,Android4.4以下都有此问题。

addJavaScriptInterface() 方法漏洞

通过反射机制,js可以直接获取Runtime,从而执行命令。Android4.2以上,可以通过声明 @JavascriptInterface保证安全性,4.2以下不能调用addJavascriptInterface()方法,需要另谋他法。

  • 如果使用https,应进行证书校验防止访问的页面被篡改挂马;
  • 如果使用http,应进行白名单过滤、完整性校验等防止访问的页面被篡改;
  • 如果加载本地Html,应将html文件内置在APK中,以及进行对html页面完整性的校验;

WebView 优化

页面加载速度优化

除了 web 页面自身的 URL 请求,还会有 web 页面外部引用的JS、CSS、字体、图片等等都是个独立的 http 请求。这些请求都是串行的,这些请求加上浏览器的解析、渲染时间就会导致 WebView 整体加载时间变长,消耗的流量也对应的真多。

选择合适的WebView缓存
缓存机制优势适用场景
浏览器缓存机制HTTP协议层支持静态文件的缓存
Dom Storage较大存储空间临时、简单数据的缓存,Cookies的扩展
Web SQL Database存储、管理复杂结构数据用IndexedDB替代,不推荐使用
APPCache方便构建离线APP离线APP、静态文件缓存,不推荐使用
IndexedDB存储任何类型数据、简单、支持索引结构关系复杂的数据存储 Web SQL DataBase的替代
File System API支持文件系统的操作数据适合以文件进行管理的场景,Android系统还不支持

* 浏览器缓存机制

主要前端负责,Android 端不需要进行特别的配置。

Cache-Control用户控制文件在本地缓存有效时长。Cache-Control:max-age=600表示文件在本地缓存600秒,从发出请求算起,如果有请求该资源,浏览器不会发送HTTP请求,直接使用本地缓存文件。

Last=Modified是标识文件在服务器上的最新更改时间。下次请求时,如果文件缓存过期,浏览器通过 If-Modified-Since 字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。如果没有则返回 304 告诉浏览器使用缓存;如果有修改,则返回200,同时返回最新的文件。

Cache-ControlLast=Modified配合使用。

Cache-Control 还有一个同功能的字段:Expires。Expires 的值一个绝对的时间点,如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在这个时间点之前,缓存都是有效的。Expires 是 HTTP1.0 标准中的字段,Cache-Control 是 HTTP1.1 标准中新加的字段,功能一样,都是控制缓存的有效时间。当这两个字段同时出现时,Cache-Control 是高优化级的

Etag也是和 Last-Modified 一样,对文件进行标识的字段。不同的是,Etag 的取值是一个对文件进行标识的特征字串。在向服务器查询文件是否有更新时,浏览器通过 If-None-Match 字段把特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否有更新。没有更新回包304,有更新回包200。Etag 和 Last-Modified 可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。

Last-Modified是服务端文件最新修改的时间,If-Modified-Since是客户端缓存存储的文件最新修改的时间,客户端发起请求的时候,带上If-Modified-Since给服务端去验证,如果If-Modified-Since和Last-Modified相同,则表示版本相同,返回304,客户端缓存可以继续使用;不相同则返回200,刷新客户端缓存

  • Dom Storage(Web Storage)存储机制

    配合前端使用,使用时需要打开 DomStorage 开关。

    DOM 存储被设计为用来提供一个更大存储量、更安全、更便捷的存储方法,从而可以代替掉将一些不需要让服务器知道的信息存储到 cookies 里的这种传统方法。Dom Storage 机制类似 Cookies,但有一些优势。

    Dom Storage 是通过存储字符串的 Key/Value 对来提供的,并提供 5MB (不同浏览器可能不同,分 HOST)的存储空间(Cookies 才 4KB)。另外 Dom Storage 存储的数据在本地,不像 Cookies,每次请求一次页面,Cookies 都会发送给服务器。

    DOM Storage 分为 sessionStoragelocalStorage。localStorage 对象和 sessionStorage 对象使用方法基本相同,它们的区别在于作用的范围不同。sessionStorage 用来存储与页面相关的数据,它在页面关闭后无法使用。而 localStorage 则持久存在,在页面关闭后也可以使用。

    比如:页面的操作需要跳到其他页面操作然后再跳回来,但又想保留之前用户输入的信息,就可以这样临时存储数据。

    webSettings.setDomStorageEnabled(true);

    在使用时,Android端只是设置一下,其他的需要Web开发去实现。

  • Web SQL Database 存储机制(不再推荐使用,官方停止维护)

    为了兼容性,在 Android 内嵌 Webview 中,需要通过 Webview 设置接口启用 SQL Database,同时还要设置数据库文件的存储路径。

    webSettings.setDatabaseEnabled(true);
    final String dbPath = getApplicationContext().getDir("db",Context.MODE_PRIVATE).getPath();
    webSettings.setDatabasePath(dbPath)
  • Application Cache 存储机制

    Application Cache(简称 AppCache)似乎是为支持 Web App 离线使用而开发的缓存机制。它的缓存机制类似于浏览器的缓存(Cache-Control 和 Last-Modified)机制,都是以文件为单位进行缓存,且文件有一定更新机制。但 AppCache 是对浏览器缓存机制的补充,不是替代。

    AppCache 的原理有两个关键点:manifest 属性和 manifest 文件。会在HTML头部定义一个manifest文件,里面声明了哪些需要缓存的文件。也有5MB的空间限制。

    不过根据官方文档,AppCache 已经不推荐使用了,标准也不会再支持。现在主流的浏览器都是还支持 AppCache的,以后就不太确定了。同样给出 Android 端启用 AppCache 的代码。

    webSettings.setAppCacheEnabled(true);
    final String cachePath = getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath();
    webSettings.setAppCachePath(cachePath);
    webSettings.setAppCacheMaxSize(5*1024*1024);
  • Indexed Database 存储机制

    IndexedDB 也是一种数据库的存储机制,但不同于已经不再支持的 Web SQL Database。IndexedDB 不是传统的关系数据库,可归为 NoSQL 数据库。IndexedDB 又类似于 Dom Storage 的 key-value 的存储方式,但功能更强大,且存储空间更大。

    Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。

    webSettings.setJavaScriptEnabled(true);

  • File System API(Android 暂不支持)

    File System API 是 H5 新加入的存储机制。它为 Web App 提供了一个虚拟的文件系统,就像 Native App 访问本地文件系统一样。由于安全性的考虑,这个虚拟文件系统有一定的限制。Web App 在虚拟的文件系统中,可以进行文件(夹)的创建、读、写、删除、遍历等操作。很可惜到目前,Android 系统的 WebView 还不支持 File System API。

常用资源预加载

上面介绍的都是二次启动,首次加载H5页面可以预加载资源文件,如(JS、CSS、图片等资源)

    mWebView.setWebViewClient(new WebViewClient() {
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {
            WebResourceResponse response = null;
            // 检查该资源是否已经提前下载完成。我采用的策略是在应用启动时,用户在 wifi 的网络环境下                // 提前下载 H5 页面需要的资源。
            boolean resDown = JSHelper.isURLDownValid(url);
            if (resDown) {
                jsStr = JsjjJSHelper.getResInputStream(url);
                if (url.endsWith(".png")) {
                    response = getWebResourceResponse(url, "image/png", ".png");
                } else if (url.endsWith(".gif")) {
                    response = getWebResourceResponse(url, "image/gif", ".gif");
                } else if (url.endsWith(".jpg")) {
                    response = getWebResourceResponse(url, "image/jepg", ".jpg");
                } else if (url.endsWith(".jepg")) {
                    response = getWebResourceResponse(url, "image/jepg", ".jepg");
                } else if (url.endsWith(".js") && jsStr != null) {
                    response = getWebResourceResponse("text/javascript", "UTF-8", ".js");
                } else if (url.endsWith(".css") && jsStr != null) {
                    response = getWebResourceResponse("text/css", "UTF-8", ".css");
                } else if (url.endsWith(".html") && jsStr != null) {
                    response = getWebResourceResponse("text/html", "UTF-8", ".html");
                }
            }
            // 若 response 返回为 null , WebView 会自行请求网络加载资源。 
            return response;
        }
    });

    private WebResourceResponse getWebResourceResponse(String url, String mime, String style) {
        WebResourceResponse response = null;
        try {
            response = new WebResourceResponse(mime, "UTF-8", new FileInputStream(new File(getJSPath() + TPMD5.md5String(url) + style)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return response;
    }

    public String getJsjjJSPath() {
        String splashTargetPath = JarEnv.sApplicationContext.getFilesDir().getPath() + "/JS";
        if (!TPFileSysUtil.isDirFileExist(splashTargetPath)) {
            TPFileSysUtil.createDir(splashTargetPath);
        }
        return splashTargetPath + "/";
    }
常用JS本地化及延迟加载

直接将常用JS脚本放在assert文件夹中,在WebView调用了onPageFinished()方法进行加载。注意:需要在JS文件中写入一个JS文件载入完毕的事件。

Android 的 OnPageFinished 事件会在 Javascript 脚本执行完成之后才会触发。如果在页面中使 用JQuery,会在处理完 DOM 对象,执行完 $(document).ready(function() {}); 事件自会后才会渲染并显示页面。而同样的页面在 iPhone 上却是载入相当的快,因为 iPhone 是显示完页面才会触发脚本的执行。所以我们这边的解决方案延迟 JS 脚本的载入,这个方面的问题是需要Web前端工程师帮忙优化的。

使用第三方WebView内核

WebView 的兼容性是个大问题。Android4.4版本Google使用了Chromium替代Webkit作为WebView内核,第三方ROM对原生WebView作出修改。

可以使用第三方内核,比如腾讯浏览服务,SDK只有212KB。

WebView与Native交互

Java WebView 与 JavaScript交互
loadUrl(“javascript:method('参数')”)

myWebView.addJavascriptInterface(new JsInteration(), "control”);
public class JsInteration {

  @JavascriptInterface
  public void toastMessage(String message) {
      Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
  }

  @JavascriptInterface
  public void onSumResult(int result) {
      Log.i(LOGTAG, "onSumResult result=" + result);
  }
}

调用时:window.control.toastMessage("message");

WebView导致的内存泄露

Android 原生的WebView存在内存泄漏,根治该问题的办法是:为WebView开启一个进程,通过AIDL与主进程通信,用完WebView直接销毁整个进程。缺点是:涉及到Android进程间通信


<activity android:name=".webview.WebViewActivity"
android:launchMode="singleTop"
android:process=":remote"
android:screenOrientation="unspecified" />

在关闭时:


@Override
protected void onDestroy() {
super.onDestroy();
System.exit(0);
}

还有一个办法:使用自定义的WebView,而且是在Java代码中直接new出来,传入 applicationContext 来防止activity引用被滥用。

Safe Java-JS WebView Bridge

抛弃使用高风险的WebView addJavascriptInterface方法,通过对js层调用函数及回调函数的包装,支持异步回调,方法参数支持js所有已知的类型,包括number、string、boolean、object、function。

原理:在页面加载30%时,把js文件load进来。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值