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 报告错误信息
onScaleChanged WebView发生改变
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进程间通信


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进来。

你可能感兴趣的:(Android学习笔记)