Android之WebView 缓存处理 性能优化【四】

老早之前就想总结下Webview相关的知识点了,因为互联网大潮中,很多APP都会使用到Webview,像那些不计其数的电商APP,无一例外的使用Webview;或者一些非电商APP中的像广告页面,注册协议页面都会用到;最后因为一些事情拖到现在才做,感觉事情真不能拖,越往后推越做不了,罪过罪过。

怎么总结Webview呢

1.简单介绍

2.WebView/WebViewClient/WebChromeClient api介绍

3.简单使用

4.JS调用Android本地

5.Android调用JS方法

6.缓存处理及性能优化

7.webview使用注意点


webview系列文章

Android之WebView/WebViewClient/WebChromeClient简介 API详述 【一】

Android之WebView/WebViewClient/WebChromeClient 使用样例 【二】

Android之WebView Android调用JS方法 JS调用Android方法 【三】

Android之WebView 缓存处理 性能优化【四】

Android之WebView 使用注意点 JS注入漏洞问题 内存优化【五】


6.缓存处理及性能优化

*.性能优化

现在不管什么样的APP,在里面嵌入H5页面已经是非常普通的了,感觉你的APP没有H5就OUT了这种,大家都这么热衷于H5的使用,肯定是有它的合理之处,有句话怎么说来着,存在即合理;能这么广泛的使用,很重要的一点得益于现在通信技术的发展,你很难想象在4G技术出来以前在手机APP里使用H5,那加载速度让你生不如死;当然现在手机硬件的提升也很重要。Webview的开发能让开发商用最低的成本实现Android,Ios,Web之间的复用,并且能很好的对功能及时作出更新,从长远眼光来看,这必然是发展的趋势。

对我们开发者来说,面对的问题肯定也是越来越多,很重要的一点就是在使用WebView的时候,它的性能问题。

1.当我们在使用WebView去加载页面的时候,很明显的能感受到它的加载速度比Native慢,也就是加载速度问题

2.加载网页的时候,里面引用的图片,js,css等全是通过网络下载的,也就是流量的消耗问题

影响Webview加载性能的原因有很多

1.webview的初始化

Android在4.4以前使用Webkit作为webview内核,之后做了优化以chromium替代,但是我们在使用的时候第一次打开Webview的时候,还是会感觉很慢,而且这种慢还是没有开始请求数据,仅仅是内核的启动初始化,这一块我们开发者要去修改这块,提高内核启动效率不太现实,尽管国内厂家也做了自己的优化,像腾讯的X5内核,相比于原生的有很大的提升,而且提供的功能更多,已经开放了。

但是我们如果就用原生内核怎么避免在使用webview加载页面出现过久初始化的情况呢。

第一种:建立一个全局的Webview,当App启动的时候,就在Application里初始化一个Webview,对,就是直接new;当需要用的时候就直接取这个单例形式的webview去加载网页,这样就把webview初始化的等待时间变得让用户感知不了;不过每次使用的时候需要清空上次使用时的页面内容。

使用这种方法会带来一些内存问题,这个放在后面讲述

第二种:上面这种方法是初始化webview-》加载网页-》请求数据,是串行的,阻塞的;如果我们不初始化全局webview,而是在使用的时候初始化webview,然后同时去请求网络数据到本地,并行处理,这样总体的加载时间就被缩短了。

2.页面加载

影响页面加载的原因也有很多,每次页面加载,都包含了很多的网络请求,有很多的资源需要下载,包括web页面本身的url请求,还有页面引用的图片,js,css,而且这些请求都是串行的;请求结束后还要进行渲染,这其中又涉及到JS的解析;如果每次都这么干,加载速度慢就不说了,光流量就是一个大的消耗,虽然现在运营商都推出了很多不限量套餐,我自己用的就是无限流量套餐大笑,但是大部分用户还不是的。

不想每次都请求重复数据,那就涉及到缓存了。

*.Webview自带的缓存机制主要有以下几种:

1.浏览器缓存机制

2.APPlication Cache

3.Dom Storage

4.Web Sql Database

5.Indexed Database

6.File System

第一种:浏览器缓存机制

这个协议主要是前端同学设置,android客户端开发无须关心,原理是通过Http协议头部的Cache-Control/Expires,

Last-Modifed/Etag这几个字段去控制文件缓存,例如

Cache-Control:max-age=65265,这就表示要缓存65265s,如果在这个时间内再去请求这个文件,webview就不会发出请求,直接使用本地缓存的文件

Expires:Tue,12 Jun 2018 16:39:54 GMT,这表示文件到期时间是2018年6月12号16点39分54秒,在这个时间前都是使用本地缓存,不再去请求了

Last-Modified:Tue,12 Jun 2018 16:39:54 GMT,这表示文件最后的修改时间,在下次请求的时候,如果这个时间已经超过了上面两个字段的时间,就会作为If-Modified-Since字段的值,发送到服务器,服务器会跟Last-Modified比对,然后判断是否需要重新返回给客户端

ETag:"10df8c5f-110",这个属于文件的一个标志,   当请求的时候会赋值到If-None-Match字段,服务器会与最新的标志比较;如果与 Last-Modified同时出现,只要一个生效,就认为没有更新

这种缓存机制只要是正规的浏览器基本都支持,不过手机缓存的存储空间有限(在data/data/包名 目录下),随时可能被清除。

第二种:APPlication Cache

这种缓存机制可以说是对浏览器缓存机制的补充,原理相似,都是以文件为单位,有文件更新机制;这个机制需要前端设置,客户端也需要进行一些设置才能起作用。是一个专门为Web App离线使用的缓存机制,不过对于我们客户端开发官方已经不推荐使用了,

在APP上设置如下

//Android 私有缓存存储,如果你不调用setAppCachePath方法,WebView将不会产生这个目录。
mSetting.setAppCachePath(MyApplication.getInstance().getCacheDir().getAbsolutePath());
//设置是否启用缓存,不过需要设置好缓存路径,默认false
mSetting.setAppCacheEnabled(true);

在编写Html代码的时候需要指定manifest属性    ,这样页面就能使用app cache

 
 
<html manifest="demo.appcache">
</html>

一个完整的appcache文件包含3个section

 
 
CACHE MANIFEST
# 2018-06-12
/demo.js
NETWORK:
*
FALLBACK:
/ fail.html

cache manifest 下面的文件就是要缓存的文件,

network 下面的文件就是要加载的文件

fallback 下面的文件就是页面加载失败的时候显示的页面

第三种 Dom Storage

在APP上进行设置

//设置是否启用DOM存储
mSetting.setDomStorageEnabled(true);

这种机制分为两种,sessionStorage和localStorage

前者是临时性的,存储页面相关的数据,页面关闭后不能使用

后者是持久性的,就是在页面关闭后数据也能使用

这种机制就是替代cookies存储一些无须与服务器交流的数据,有点类似于SharedPreference,以key-value方法存储数据

第四种 Web Sql Database

在APP上进行设置

mSetting.setDatabaseEnabled(true);
String dbPath = MyApplication.getInstance().getDir("db", Context.MODE_PRIVATE).getPath();
mSetting.setDatabasePath(dbPath);

这种方式官方已经不推荐使用了,后续版本不再维护

原理是基于SQL的数据库存储一些结构性的数据,可以方便对数据进行增删改查

第五种 Indexed Database

这种就是取代上面那种机制,是一种NoSql数据库,使用key-value的存储方式,相比于dom功能更强大,可以通过数据库的事务机制进行数据操作;存储空间更大,默认推荐250M,比dom的5M大的多了。比较适合复杂,大量的结构化数据存储

在app上只用设置支持js就自动打开了这种缓存机制。

mSetting.setJavaScriptEnabled(true);

第六种 File System

这种机制是H5新加入的存储机制,目前Android webview暂时不支持

综上所述

    当我们存储静态资源文件,比如js,使用浏览器缓存、    APP Cache缓存

    存储临时简单数据 使用dom storage

    存储复杂大量数据 使用indexedDB


说完了缓存机制的使用,webview还有缓存模式的设置

//设置缓存模式
mSetting.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);

/**
 * Normal cache usage mode. Use with {@link #setCacheMode}.
 *
 * @deprecated This value is obsolete, as from API level
 * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and onwards it has the
 * same effect as {@link #LOAD_DEFAULT}.
 */
@Deprecated
public static final int LOAD_NORMAL = 0;

/**
 * Use cached resources when they are available, even if they have expired.
 * Otherwise load resources from the network.
 * Use with {@link #setCacheMode}.
 */
public static final int LOAD_CACHE_ELSE_NETWORK = 1;

/**
 * Don't use the cache, load from the network.
 * Use with {@link #setCacheMode}.
 */
public static final int LOAD_NO_CACHE = 2;

/**
 * Don't use the network, load from the cache.
 * Use with {@link #setCacheMode}.
 */
public static final int LOAD_CACHE_ONLY = 3;

这四种模式在前面文章有介绍。


*.预加载

上面这几种都是自带的缓存,但是都有一个前提是总要下载一次才能缓存,其实我们加载的页面中通常有很多东西是静态的,也就是长时间不会改变的,这些分两部分

1.整个H5页面在一段时间内都不会更改,比如一些广告页,公告页等

2.对于H5页面的一些js,css,图片等一部分文件一段时间内不会变化

对于第一种情况,我们可以在用户空闲时间直接把页面下载到本地保存,加载的时候取本地资源,这样速度也会更快

对于第二种情况,将一些静态文件下载下来,然后当页面进行加载的时候,拦截网络请求,判断本地有没有相同的资源,如果有,就加载本地的,就无需发请求到服务器,实现方式如下

/**
     * WebView 可以拦截某一次的 request 来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
     * API21加入
     * @param view    WebView
     * @param request 当前产生 request 请求
     * @return WebResourceResponse
     */
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {

        WebResourceResponse response;
        String url = request.getUrl().toString();
        // 判断拦截资源的条件
        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") ) {
            response = getWebResourceResponse("text/javascript", "UTF-8", "js");
        } else if (url.endsWith(".css") ) {
            response = getWebResourceResponse("text/css", "UTF-8", "css");
        } else if (url.endsWith(".html") ) {
            response = getWebResourceResponse("text/html", "UTF-8", "html");
        }else{
            return super.shouldInterceptRequest(view, request);
        }
        if(response == null) return super.shouldInterceptRequest(view, request);
        
        return response;
    }

    private WebResourceResponse getWebResourceResponse(String url, String mime, String type) {
        WebResourceResponse response = null;

        /**
         * 图片资源的地址为:http://www.mango.com/imgage/logo.gif
         * 比如,有一个请求是要像服务器下载一张图片logo.gif,这个图片正好本地已经提前下载过了,
         * 那我们就读取本地资源,
         */
        if (!TextUtils.isEmpty(url)) {
            FileInputStream fis = null;
            try {
                String path = Environment.getExternalStorageDirectory() + File.separator+resource
                        +File.separator+type+File.separator;
                String name = url.substring(url.lastIndexOf("/")+1);
                File file = new File(path+name);
                if(!file.exists()){
                    return response;
                }
                fis = new FileInputStream(path+name);
                // 这里资源可以在assets目录下取
//                is = MyApplication.getInstance().getAssets().open("images/abc.png");
                /**
                 *  参数1:http请求里该图片的Content-Type,此处图片为image/gif
                 *  参数2:编码类型
                 *  参数3:存放着替换资源的输入流(上面创建的那个)
                 */
                response = new WebResourceResponse(mime, "utf-8", fis);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    fis = null;
                }
            }
        }
        return response;
    }

这个方法是webviewclient里的方法,我们自己写一个类去继承它,然后重写这个方法就行了,然后使用webview的set方法

mWebViewClient = new MyWebViewClient();
mWebview.setWebViewClient(mWebViewClient);

这样我们对页面加载的过程进行拦截,就能将一些文件替换成我们本地文件了。


我们提前下载的这些文件也是可以更新的,当手机处于wifi条件且空闲时,然后服务器主动把资源推送到本地进行增量更新,很多知名app都是这样做的。


上面这个重写的方法还可以做其它的事,比如修改请求连接,往链接里加一些标志位,服务端也可以通过标志位进行一些其它处理

public  String addParams(String url) {
    if (!TextUtils.isEmpty(url) && !url.contains("change=") ){
        if (url.contains("?")) {
            return url + "&change=1";
        } else {
            return url + "?change=1";
        }
    } else {
        return url;
    }
}

/**
 * WebView 可以拦截某一次的 request 来返回我们自己加载的数据,这个方法在后面缓存会有很大作用。
 * API21加入
 * @param view    WebView
 * @param request 当前产生 request 请求
 * @return WebResourceResponse
 */
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) {

    String scheme = request.getUrl().getScheme().trim();
    if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) {
        return super.shouldInterceptRequest(view, new WebResourceRequest() {
            @Override
            public Uri getUrl() {
                return Uri.parse(addParams(request.getUrl().toString()));
            }

            @SuppressLint("NewApi")
            @Override
            public boolean isForMainFrame() {
                return request.isForMainFrame();
            }

            @SuppressLint("NewApi")
            @Override
            public boolean hasGesture() {
                return request.hasGesture();
            }

            @SuppressLint("NewApi")
            @Override
            public String getMethod() {
                return request.getMethod();
            }

            @SuppressLint("NewApi")
            @Override
            public Map<String, String> getRequestHeaders() {
                return request.getRequestHeaders();
            }
        });
    }
    return super.shouldInterceptRequest(view, request);


在客户端进行这些优化处理之后,其实还可以在Html上面做优化:

1.一般我们在html文件的head里面单独引入css和内联js都不会阻塞html页面的解析,但是如果同时且先link css 然后加内联js,就会造成css的加载阻塞内联js执行,进而阻塞html解析。

所以编写html的时候要注意css的标签要靠前,css下面不要添加任何的内联js。请参照web页面加载优化建议

2.像React这样偏重的框架, 其中的js解析编译执行会占很多时间,在配置不是很高的手机上,很影响页面的渲染速度,所以在开发中一定要慎用,同时同一个app里在webview开发者方面尽量统一第三方框架,这样可以提高缓存的使用率


这里推荐一个美团知识分享网站

https://tech.meituan.com/

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80662102