android 原生使用WebView嵌入H5页面 Hybrid开发

一、性能问题
  • android webview 里H5加载速度慢
  • 网络流量大
1、H5页面加载速度慢
  1. 渲染速度慢

    js解析效率

    js本身的解析过程复杂、解析速度不快,前端页面设计较多的js代码文件

    手机硬件设备的性能

    机型多,硬件性能不一

  2. 资源加载慢

    H5页面的资源多

    网络请求数量多

    ​ H5页面所有资源都需要从网络请求

二、解决方案
  • webView组件本身的缓存机制
  • 资源预加载
  • 资源拦截

webView组件本身的缓存机制

  • WebView自带的缓存机制有5种:浏览器 缓存机制

  • Application Cache 缓存机制

  • Dom Storage 缓存机制

  • Web SQL Database 缓存机制

  • Indexed Database 缓存机制

  • File System 缓存机制(H5页面新加入的缓存机制,虽然Android WebView暂时不支持,但会进行简单介绍)

    (1) Cache-Control: 用于控制文件在本地缓存的有效时长

    ​ eg:Cache-Control:max-age=600,则表示文件在本地应该缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如果有请求这个资源,浏览器不会发出 HTTP 请求,而是直接使用本地缓存的文件。

    (2)Expires: 与Cache-Control 功能相同,即控制缓存的有效时间

    ​ cache-Control 优先级高

    (3) Last-Modified: 标识文件在服务器上的最新更新时间

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

    (4) Etag:功能同Last-Modified, 即标识文件在服务器上的最新更新时间

优点:支持Http协议层

缺点:缓存文件需要首次加载后才会产生;浏览器缓存的存储空间有限,缓存有被清楚的可能,缓存的文件没有校验

可以缓存讲台文件资源,如JS,CSS、文本、图片等,webView 会将缓存的文件记录及文件内容会存在当前appde data目录中,

Application Cache 缓存机制

以文件为单位进行缓存,且文件有一定更新机制(类似于浏览器缓存机制)

AppCache : manifest 属性和manifest文件

<!DOCTYPE html>
<html manifest="demo_html.appcache">
// HTML 在头中通过 manifest 属性引用 manifest 文件
// manifest 文件:就是上面以 appcache 结尾的文件,是一个普通文件文件,列出了需要缓存的文件
// 浏览器在首次加载 HTML 文件时,会解析 manifest 属性,并读取 manifest 文件,获取 Section:CACHE MANIFEST 下要缓存的文件列表,再对文件缓存
<body>
...
</body>
</html>

// 原理说明如下:
AppCache 在首次加载生成后,也有更新机制。被缓存的文件如果要更新,需要更新 manifest 文件
因为浏览器在下次加载时,除了会默认使用缓存外,还会在后台检查 manifest 文件有没有修改(byte by byte)
发现有修改,就会重新获取 manifest 文件,对 Section:CACHE MANIFEST 下文件列表检查更新
manifest 文件与缓存文件的检查更新也遵守浏览器缓存机制
如用户手动清了 AppCache 缓存,下次加载时,浏览器会重新生成缓存,也可算是一种缓存的更新
AppCache 的缓存文件,与浏览器的缓存文件分开存储的,因为 AppCache 在本地有 5MB(分 HOST)的空间限制

    // 通过设置WebView的settings来实现
    WebSettings settings = getSettings();

    String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
    settings.setAppCachePath(cacheDirPath);
    // 1. 设置缓存路径
     settings.setAppCacheMaxSize(20*1024*1024);
    // 2. 设置缓存大小
    settings.setAppCacheEnabled(true);
    // 3. 开启Application Cache存储机制

Application 只调用一次 WebSettings.setAppCachePath() 和
WebSettings.setAppCacheMaxSize()

Dom Storage 缓存机制

通过存储字符串的Key-Value 对来提供

sessionStorage:具备临时性,即存储与页面相关的数据,它在页面关闭后无法使用
localStorage:具备持久性,即保存的数据在页面关闭后也可以使用。

特点:

  • 存储空间大(5MB): 存储空间对于不同浏览器不同,如Cookies才4KB
  • 存储安全、便捷:Dom Storage 存储的数据在本地,不需要经常和服务器进行交互。

应用:

存储临时、简单的数据

​ 类似于sharedPreference机制

    // 通过设置 `WebView`的`Settings`类实现
    WebSettings settings = getSettings();

    settings.setDomStorageEnabled(true);
    // 开启DOM storage

Web SQL Database 缓存机制

​ 基于SQL的数据库存储机制

​ 充分利用数据库的优势,可方便对数据进行增加、删除、修改、查询。

应用:

存储适合数据库的结构化数据

    // 通过设置WebView的settings实现
    WebSettings settings = getSettings();

    String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
    settings.setDatabasePath(cacheDirPath);
    // 设置缓存路径

    settings.setDatabaseEnabled(true);
    // 开启 数据库存储机制

官方说明,Web SQL Database 存储机制不在推荐使用(不在维护),取而代之的是IndexedDB缓存机制。

IndexedDB缓存机制

属于NoSQL数据库,通过存储字符串的Key-Value对来提供,类似于Dom Storage 存储机制的key-value存储方式

优点:

  • 功能强大、使用简单

    通过数据库的事务(tranction)机制进行数据操作

    可对对象任何属性生成索引,方便查询

  • 存储空间大

    较大的存储空间,默认推荐250MB(分HOST)

  • 使用灵活

    以key-value的方式存存取对象,可以是任何类型值或对象,包括二级制

    异步的API调用,避免造成等待而影响体验

存储 复杂、数据量大的结构化数据

// 通过设置WebView的settings实现
WebSettings settings = getSettings();

    settings.setJavaScriptEnabled(true);
    // 只需设置支持JS就自动打开IndexedDB存储机制
    // Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。
资源预加载

​ 提前加载将使用的H5页面,即提前构建缓存

​ 预加载webView 、预加载H5资源

预加载WebView对象

​ 提前初始化一个webView对象,后续复用这个webView对象

预加载H5资源

  1. 在应用启动、初始化第一个WebView对象时,直接开始网络请求加载H5页面
  2. 后续需打开这些H5页面时就直接从该本地对象中获取
自身构建缓存

为了有效解决 Android WebView 的性能问题,除了使用 Android WebView 自身的缓存机制,还可以自己针对某一需求场景构建缓存机制。

实现方法:

  1. 事先将更新频率较低、常用 & 固定的H5静态资源 文件(如JSCSS文件、图片等) 放到本地
  2. 拦截H5页面的资源网络请求 并进行检测
  3. 如果检测到本地具有相同的静态资源 就 直接从本地读取进行替换 而 不发送该资源的网络请求 到 服务器获取

实现方法:

假设现在需要拦截一个图片的资源并用本地资源进行替代

    mWebview.setWebViewClient(new WebViewClient() {
        // 重写 WebViewClient  的  shouldInterceptRequest ()
        // API 21 以下用shouldInterceptRequest(WebView view, String url)
        // API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)
        // 下面会详细说明

         // API 21 以下用shouldInterceptRequest(WebView view, String url)
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

            // 步骤1:判断拦截资源的条件,即判断url里的图片资源的文件名
            if (url.contains("logo.gif")) {
            // 假设网页里该图片资源的地址为:http://abc.com/imgage/logo.gif
            // 图片的资源文件名为:logo.gif

                InputStream is = null;
                // 步骤2:创建一个输入流

                try {
                    is =getApplicationContext().getAssets().open("images/abc.png");
                    // 步骤3:获得需要替换的资源(存放在assets文件夹里)
                    // a. 先在app/src/main下创建一个assets文件夹
                    // b. 在assets文件夹里再创建一个images文件夹
                    // c. 在images文件夹放上需要替换的资源(此处替换的是abc.png图片)

                } catch (IOException e) {
                    e.printStackTrace();
                }

                // 步骤4:替换资源
                WebResourceResponse response = new WebResourceResponse("image/png",
                        "utf-8", is);
                // 参数1:http请求里该图片的Content-Type,此处图片为image/png
                // 参数2:编码类型
                // 参数3:存放着替换资源的输入流(上面创建的那个)
                return response;
            }

            return super.shouldInterceptRequest(view, url);
        }

       // API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {

           // 步骤1:判断拦截资源的条件,即判断url里的图片资源的文件名
            if (request.getUrl().toString().contains("logo.gif")) {
            // 假设网页里该图片资源的地址为:http://abc.com/imgage/logo.gif
            // 图片的资源文件名为:logo.gif

                InputStream is = null;
                // 步骤2:创建一个输入流

                try {
                    is = getApplicationContext().getAssets().open("images/abc.png");
                     // 步骤3:获得需要替换的资源(存放在assets文件夹里)
                    // a. 先在app/src/main下创建一个assets文件夹
                    // b. 在assets文件夹里再创建一个images文件夹
                    // c. 在images文件夹放上需要替换的资源(此处替换的是abc.png图片

                } catch (IOException e) {
                    e.printStackTrace();
                }

                // 步骤4:替换资源
                WebResourceResponse response = new WebResourceResponse("image/png",
                        "utf-8", is);
                // 参数1:http请求里该图片的Content-Type,此处图片为image/png
                // 参数2:编码类型
                // 参数3:存放着替换资源的输入流(上面创建的那个)
                return response;
            }
            return super.shouldInterceptRequest(view, request);
        }
});
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐