vasSonic是一种H5加速方案,可以极大提高H5首屏速度。最近通过阅读github的vasSonic源码,结合文档中的流程讲解,绘制了一些sonic的逻辑流程图。绘制的流程是Android的quick模式下的。
1.Sonic简单介绍
sonic的核心思想是: 1.并行加载网页内容和webview。普通的页面加载是先启动webView再通过webview的loadURL来加载页面,串行操作。而sonic在启动webView的同时就开始拉取页面的信息,并行初始化,优化启动的时间。 2.使用缓存来优化网页的加载时间。sonic是把页面分为了模板和数据,通过标记来判断页面是否变化,如果没有发生变化,就无需更新直接使用缓存页面;如果是服务器的数据发生了变化,就只更新数据;只有当模板变化时才重新拉取整个页面。
所以,sonic在使用中的场景主要分一下几种情况:
- 首次打开,没有缓存,拉取整个页面。
- 非首次打开,有缓存不需要更新。
- 非首次打开,有缓存,需要更新数据,仅拉取数据。
- 非首次打开,需要更新模板,拉取整个页面。
2.Sonic时序图
在实际场景中,因为sonic准备数据的初始化时间和webView启动的初始化时间是不确定的,有的机型上可能sonic比webView快,有些机型上可能webView比sonic快,初始化时序的不同,也会有不同的处理方式。所以,下面通过时序图来绘制sonic在不同场景,不同初始化时序下的大致处理流程。
横轴是时间
- 没有缓存时,webView初始化比较快,初始化完成后,sonic的数据还没有下载完成,这时sonic会把流桥接给webView,让webView自己继续下载读取数据。当webView下载html完成之后,会调用stream的close方法,这时sonic再保存数据进行缓存。
- 没有缓存,webview初始化比较慢,当webView初始化完成之后,sonic已经把数据加载完成了。这时sonic直接把html交给webView加载。
-
有缓存,无需更新。这个就比较简单了,服务器返回304,告知客户端数据已经是最新的了,无需再执行更新。
-
有缓存,需要更新数据,webView初始化速度比较快。这时,webview会先加载本地已经缓存的html,然后客户端通过调用jsBridge通知webView数据已经更新。
- 有缓存,需要更新数据,webView初始化速度慢。这种情况,sonic已经拿到了最新的数据,并且拼接好了新的html,所以可以直接把html交给webView。
- 有缓存,需要更新模板,webView快,网络较慢。这时,因为sonic需要更新模板,所以拉取全部的网页内容,而同时,webView会先加载本地已经缓存好的html,当webView渲染完了之后,会调用pageFinish。这时如果sonic还没有下载完页面,会建立桥接把数据交给webView下载。有点类似场景1了。
- 有缓存,需要更新模板,webView较快,网络也快。这时,webView加载完本地数据时,sonic已经准备好了新的页面,所以直接把html传给webView就可以了。
-
有缓存,需要更新模板,webView初始化很慢。这时,webView没有机会去加载本地的缓存,当webView初始化完成的时候,sonic已经下载完了最新的页面,所以webView直接加载了最新的页面。
以上就是sonic的大致时序图。
3.Sonic流程图
有了时序图,可以大致知道sonic有哪些处理场景,然后这里再补充一个sonic的流程图,可以从全局看到sonic是怎么处理和判断的。主要绘制了sonic的内部逻辑流程,webView方面的就只简略提了一下。
4.数据桥接
虽然流程大致了解,但是有些实现细节还是让人比较好奇,比如webView的桥接是怎么做到的?这里阅读了sonic Android的源码,了解一些实现细节,也记录在这里。
-
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方法就是把一个水桶里的水倒到另外一个水桶里。 所以流程大概是这样的:
开始的时候,流都在网络:
因为webView还在初始化,sonic先把网络流缓存起来。
缓存流读完了,再从网络流里面读取数据。
本文首发于 一粟(https://www.zeyio.com),欢迎转载,但是必须保留本文的署名和链接。