H5加速神器--vasSonic加载流程解析

November 28, 2018

vasSonic是一种H5加速方案,可以极大提高H5首屏速度。最近通过阅读github的vasSonic源码,结合文档中的流程讲解,绘制了一些sonic的逻辑流程图。绘制的流程是Android的quick模式下的。

1.Sonic简单介绍

sonic的核心思想是: 1.并行加载网页内容和webview。普通的页面加载是先启动webView再通过webview的loadURL来加载页面,串行操作。而sonic在启动webView的同时就开始拉取页面的信息,并行初始化,优化启动的时间。 2.使用缓存来优化网页的加载时间。sonic是把页面分为了模板和数据,通过标记来判断页面是否变化,如果没有发生变化,就无需更新直接使用缓存页面;如果是服务器的数据发生了变化,就只更新数据;只有当模板变化时才重新拉取整个页面。

所以,sonic在使用中的场景主要分一下几种情况:

  1. 首次打开,没有缓存,拉取整个页面。
  2. 非首次打开,有缓存不需要更新。
  3. 非首次打开,有缓存,需要更新数据,仅拉取数据。
  4. 非首次打开,需要更新模板,拉取整个页面。

2.Sonic时序图

在实际场景中,因为sonic准备数据的初始化时间和webView启动的初始化时间是不确定的,有的机型上可能sonic比webView快,有些机型上可能webView比sonic快,初始化时序的不同,也会有不同的处理方式。所以,下面通过时序图来绘制sonic在不同场景,不同初始化时序下的大致处理流程。

横轴是时间

  1. 没有缓存时,webView初始化比较快,初始化完成后,sonic的数据还没有下载完成,这时sonic会把流桥接给webView,让webView自己继续下载读取数据。当webView下载html完成之后,会调用stream的close方法,这时sonic再保存数据进行缓存。

image-20181128192613681-3404373

  1. 没有缓存,webview初始化比较慢,当webView初始化完成之后,sonic已经把数据加载完成了。这时sonic直接把html交给webView加载。

image-20181128192819669-3404499

  1. 有缓存,无需更新。这个就比较简单了,服务器返回304,告知客户端数据已经是最新的了,无需再执行更新。

    image-20181128193140190-3404700

  2. 有缓存,需要更新数据,webView初始化速度比较快。这时,webview会先加载本地已经缓存的html,然后客户端通过调用jsBridge通知webView数据已经更新。

image-20181128193314492-3404794

  1. 有缓存,需要更新数据,webView初始化速度慢。这种情况,sonic已经拿到了最新的数据,并且拼接好了新的html,所以可以直接把html交给webView。

image-20181128193505740-3404905

  1. 有缓存,需要更新模板,webView快,网络较慢。这时,因为sonic需要更新模板,所以拉取全部的网页内容,而同时,webView会先加载本地已经缓存好的html,当webView渲染完了之后,会调用pageFinish。这时如果sonic还没有下载完页面,会建立桥接把数据交给webView下载。有点类似场景1了。

image-20181128193941954-3405181

  1. 有缓存,需要更新模板,webView较快,网络也快。这时,webView加载完本地数据时,sonic已经准备好了新的页面,所以直接把html传给webView就可以了。

image-20181128194100133-3405260

  1. 有缓存,需要更新模板,webView初始化很慢。这时,webView没有机会去加载本地的缓存,当webView初始化完成的时候,sonic已经下载完了最新的页面,所以webView直接加载了最新的页面。

    image-20181128194144232-3405304

以上就是sonic的大致时序图。

3.Sonic流程图

有了时序图,可以大致知道sonic有哪些处理场景,然后这里再补充一个sonic的流程图,可以从全局看到sonic是怎么处理和判断的。主要绘制了sonic的内部逻辑流程,webView方面的就只简略提了一下。

sonic逻辑流程图

4.数据桥接

虽然流程大致了解,但是有些实现细节还是让人比较好奇,比如webView的桥接是怎么做到的?这里阅读了sonic Android的源码,了解一些实现细节,也记录在这里。

  1. sonic是怎么使下载数据桥接到webView的?

    先看当被打断时,stream的操作。

    SonicServer.java

    public synchronized InputStream getResponseStream(AtomicBoolean breakConditions) {
            if (readServerResponse(breakConditions)) {
                //被打断,比如是场景1的webView初始化完成(clieanReady)
                
                //netStream用来代表未读取的部分
                //如果serverRsp不为空,说明已经下载完成了,否则就把没有读完的stream赋值给netStream
                BufferedInputStream netStream = !TextUtils.isEmpty(serverRsp) ? null : connectionImpl.getResponseStream();
                return new SonicSessionStream(this, outputStream, netStream);
            } else {
                return null;
            }
        }

    再看下SonicSessionStream的构造方法

    SonicSessionStream.java

    //继承了InputStream
    public class SonicSessionStream extends InputStream {   
    
    public SonicSessionStream(Callback callback, ByteArrayOutputStream outputStream, BufferedInputStream netStream) {
            if (null != netStream) {
                //netStream不为空,说明流还没有读取完成,用一个标记位来标识。
                this.netStream = netStream;
                this.netStreamReadComplete = false;
            }
    
            if (outputStream != null) {
                //已经读取的部分流
                this.outputStream = outputStream;
                this.memStream = new BufferedInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
                this.memStreamReadComplete = false;
            } else {
                this.outputStream = new ByteArrayOutputStream();
            }
    
            callbackWeakReference = new WeakReference<Callback>(callback);
        }

    关键的桥接逻辑,把封装好的Stream交给webView之后,webView会调用read方法读取流,这时根据已经读取状态,先返回本地已经读好的流,再返回网络的流。

    SonicSessionStream.java

    @Override
    public synchronized int read() throws IOException {
    
        int c = -1;
    
        try {
            //判断本地的流是否读完
            if (null != memStream && !memStreamReadComplete) {
                c = memStream.read();
            }
    
            if (-1 == c) {
                memStreamReadComplete = true;
                //判断并读取网络流
                if (null != netStream && !netStreamReadComplete) {
                    c = netStream.read();
                    if (-1 != c) {
                        outputStream.write(c);
                    } else {
                        netStreamReadComplete = true;
                    }
                }
            }
        } catch (Throwable e) {
         
        }
    
        return c;
    }

抽象起来可以把Stream想象成水桶,read方法就是把一个水桶里的水倒到另外一个水桶里。 所以流程大概是这样的:

开始的时候,流都在网络:

image-20181128203950721-3408790

因为webView还在初始化,sonic先把网络流缓存起来。

image-20181128204017681-3408817

webView初始化完成了,先从缓存流里面读取数据。 image-20181128204111688-3408871

缓存流读完了,再从网络流里面读取数据。

image-20181128204253852-3408973

本文首发于 一粟(https://www.zeyio.com),欢迎转载,但是必须保留本文的署名和链接。

本文永久链接:https://www.zeyio.com/how-sonic-works/