1、VasSonic是什么?
一句话总结:优化webview对h5的加载速度
wiki原话:
VasSonic取名于世嘉游戏形象音速小子,是腾讯VAS(SNG增值产品部QQ会员)团队研发的一个轻量级的高性能的Hybrid框架,专注于提升页面首屏加载速度,完美支持静态直出页面和动态直出页面,兼容离线包等方案。
该框架使用终端应用层原生传输通道取代系统浏览器内核自身资源传输通道来请求页面主资源,在移动终端初始化的同时并行请求页面主资源并做到流式拦截,减少传统方案上终端初始化耗时长导致页面主资源发起请求时机慢或传统并行方案下必须等待主资源完成下载才能交给内核加载的影响。
另外通过客户端和服务器端双方遵守VasSonic格式规范(通过在html内增加注释代码区分模板和数据),该框架做到智能地对页面内容进行动态缓存和增量更新,减少对网络的依赖和数据传输的大小,大大提升H5页面的加载速度,让H5页面的体验更加接近原生,提升用户体验及用户留存率。
目前QQ会员、QQ游戏中心、QQ个性化商城、QQ购物、QQ钱包、企鹅电竞等业务已经在使用,日均PV在1.2亿以上(仅统计手Q内数据),页面首屏平均耗时在1s以下。
2、怎么引入到项目中
https://github.com/Tencent/VasSonic/wiki 这里有官方的Android及IOS引入说明
(1)SDK引入
compile 'com.tencent.sonic:sdk:1.0.0'
(2) SonicRuntime 的实现
public class SonicRuntimeImpl extends SonicRuntime {
public SonicRuntimeImpl(Context context) {
super(context);
}
/**
* 获取用户UA信息,这里的返回值会放在header的UserAgent中
* @return
*/
@Override
public String getUserAgent() {
return "";
}
/**
* 获取用户ID信息,避免多个用户切换可能使用到相同的缓存
* @return
*/
@Override
public String getCurrentUserAccount() {
return "sonic-demo-master";
}
@Override
public String getCookie(String url) {
CookieManager cookieManager = CookieManager.getInstance();
return cookieManager.getCookie(url);
}
@Override
public void log(String tag, int level, String message) {
switch (level) {
case Log.ERROR:
Log.e(tag, message);
break;
case Log.INFO:
Log.i(tag, message);
break;
default:
Log.d(tag, message);
}
}
@Override
public Object createWebResourceResponse(String mimeType, String encoding, InputStream data, Map headers) {
WebResourceResponse resourceResponse = new WebResourceResponse(mimeType, encoding, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
resourceResponse.setResponseHeaders(headers);
}
return resourceResponse;
}
@Override
public void showToast(CharSequence text, int duration) {
}
@Override
public void notifyError(SonicSessionClient client, String url, int errorCode) {
}
// 这里可以设置某个url是否为SonicUrl,如果指定为不是,则不会通过Sonic的方式加载url。
@Override
public boolean isSonicUrl(String url) {
return true;
}
@Override
public boolean setCookie(String url, List cookies) {
if (!TextUtils.isEmpty(url) && cookies != null && cookies.size() > 0) {
CookieManager cookieManager = CookieManager.getInstance();
for (String cookie : cookies) {
cookieManager.setCookie(url, cookie);
}
return true;
}
return false;
}
// 判断网络连接情况
@Override
public boolean isNetworkValid() {
return true;
}
@Override
public void postTaskToThread(Runnable task, long delayMillis) {
Thread thread = new Thread(task, "SonicThread");
thread.start();
}
@Override
public File getSonicCacheDir() {
if (BuildConfig.DEBUG) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "sonic/";
File file = new File(path.trim());
if(!file.exists()){
file.mkdir();
}
return file;
}
return super.getSonicCacheDir();
}
@Override
public String getHostDirectAddress(String url) {
return null;
}
}
(3)SonicSessionClient的实现
public class SonicSessionClientImpl extends SonicSessionClient {
private WebView webView;
public void bindWebView(WebView webView) {
this.webView = webView;
}
public WebView getWebView() {
return webView;
}
@Override
public void loadUrl(String url, Bundle extraData) {
webView.loadUrl(url);
}
@Override
public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
@Override
public void loadDataWithBaseUrlAndHeader(String baseUrl, String data, String mimeType, String encoding, String historyUrl, HashMap headers) {
loadDataWithBaseUrl(baseUrl, data, mimeType, encoding, historyUrl);
}
public void destroy() {
if (null != webView) {
webView.destroy();
webView = null;
}
}
}
(4)SonicJavaScriptInterface的实现
这个类根据自己情况,如果用了jsbridge等库,则这个类不用实现。
(5)相关代码插入
首先是SonicEngine的初始化:
//这段代码可以放在Activity或者Application的onCreate方法中
if (!SonicEngine.isGetInstanceAllowed()) {
SonicEngine.createInstance(new SonicRuntimeImpl(getApplication()), new SonicConfig.Builder().build());
}
接着如果要进行预先的加载:
SonicSessionConfig sessionConfig = new SonicSessionConfig.Builder().build();
boolean preloadSuccess = SonicEngine.getInstance().preCreateSession(DEMO_URL, sessionConfig);
之后:
sonicSession = SonicEngine.getInstance().createSession(url, sessionConfigBuilder.build());
if (null != sonicSession) {
sonicSession.bindClient(sonicSessionClient = new SonicSessionClientImpl());
} else {
// this only happen when a same sonic session is already running,
// u can comment following codes to feedback as a default mode.
throw new UnknownError("create session fail!");
}
最后当webview相关代码书写完成后调用如下:
if (sonicSessionClient != null) {
sonicSessionClient.bindWebView(webView);
sonicSessionClient.clientReady();
} else { // default mode
webView.loadUrl(url);
}
3、源码部分解读
说明:我这里只分析了预加载及首次加载。
首先是
SonicSessionConfig sessionConfig = new SonicSessionConfig.Builder().build();
内部实现其实就是创建了一个默认的SonicSessionConfig对象,这个类的作用是设置超时时间、缓存大小等相关参数。
然后调用SonicEngine.getInstance().preCreateSession(DEMO_URL, sessionConfig);
我们看看SonicEngine的preCreateSession方法的实现:
public synchronized boolean preCreateSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
// 创建sessionId
String sessionId = makeSessionId(url, sessionConfig.IS_ACCOUNT_RELATED);
if (!TextUtils.isEmpty(sessionId)) {
// 判断session缓存是否过期,以及sessionConfig是否发生变化 如果满足则从preloadSessionPool中移除
SonicSession sonicSession = lookupSession(sessionConfig, sessionId, false);
if (null != sonicSession) {
runtime.log(TAG, Log.ERROR, "preCreateSession:sessionId(" + sessionId + ") is already in preload pool.");
return false;
}
if (preloadSessionPool.size() < config.MAX_PRELOAD_SESSION_COUNT) {
if (isSessionAvailable(sessionId) && runtime.isNetworkValid()) {
// 创建session
sonicSession = internalCreateSession(sessionId, url, sessionConfig);
if (null != sonicSession) {
preloadSessionPool.put(sessionId, sonicSession);
return true;
}
}
} else {
runtime.log(TAG, Log.ERROR, "create id(" + sessionId + ") fail for preload size is bigger than " + config.MAX_PRELOAD_SESSION_COUNT + ".");
}
}
return false;
}
如上创建session的过程重要的已经注释了,里面的核心语句是 sonicSession = internalCreateSession(sessionId, url, sessionConfig);
接着我们看下相关源码:
private SonicSession internalCreateSession(String sessionId, String url, SonicSessionConfig sessionConfig) {
if (!runningSessionHashMap.containsKey(sessionId)) {
SonicSession sonicSession;
if (sessionConfig.sessionMode == SonicConstants.SESSION_MODE_QUICK) {
sonicSession = new QuickSonicSession(sessionId, url, sessionConfig);
} else {
sonicSession = new StandardSonicSession(sessionId, url, sessionConfig);
}
sonicSession.addCallback(sessionCallback);
if (sessionConfig.AUTO_START_WHEN_CREATE) {
sonicSession.start();
}
return sonicSession;
}
if (runtime.shouldLog(Log.ERROR)) {
runtime.log(TAG, Log.ERROR, "internalCreateSession error:sessionId(" + sessionId + ") is running now.");
}
return null;
}
这个函数中主要是判断seesionConfig的sessionMode参数,从而创建具体的SonicSession对象,并将 sessionId, url, sessionConfig 作为构造参数。之后调用sonicSession.start();
。我们接着来看SonicSession的start方法:
public void start() {
if (!sessionState.compareAndSet(STATE_NONE, STATE_RUNNING)) {
SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") start error:sessionState=" + sessionState.get() + ".");
return;
}
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") now post sonic flow task.");
statistics.sonicStartTime = System.currentTimeMillis();
isWaitingForSessionThread.set(true);
SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() {
@Override
public void run() {
runSonicFlow();
}
});
notifyStateChange(STATE_NONE, STATE_RUNNING, null);
}
这个方法核心是 在SonicRuntime中创建子线程从而执行runSonicFlow();
方法。接着我们来看子线程调用runSonicFlow方法:
private void runSonicFlow() {
......
String htmlString = SonicCacheInterceptor.getSonicCacheData(this);
boolean hasHtmlCache = !TextUtils.isEmpty(htmlString);
......
handleLocalHtml(htmlString);
final SonicRuntime runtime = SonicEngine.getInstance().getRuntime();
if (!runtime.isNetworkValid()) {
//Whether the network is available
if (hasHtmlCache && !TextUtils.isEmpty(config.USE_SONIC_CACHE_IN_BAD_NETWORK_TOAST)) {
runtime.postTaskToMainThread(new Runnable() {
@Override
public void run() {
if (clientIsReady.get() && !isDestroyedOrWaitingForDestroy()) {
runtime.showToast(config.USE_SONIC_CACHE_IN_BAD_NETWORK_TOAST, Toast.LENGTH_LONG);
}
}
}, 1500);
}
SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") runSonicFlow error:network is not valid!");
} else {
handleFlow_Connection(htmlString);
statistics.connectionFlowFinishTime = System.currentTimeMillis();
}
......
}
在这个方法中首先通过调用SonicCacheInterceptor.getSonicCacheData(this);
拿到缓存,但是因为是第一次加载所以htmlString为null,然后 调用handleLocalHtml()
方法,这个方法是抽象的,因此我们找到QuickSonicSession这个具体类,来看一下实现:
protected void handleLocalHtml(String localHtml) {
Message msg = mainHandler.obtainMessage(CLIENT_CORE_MSG_PRE_LOAD);
if (!TextUtils.isEmpty(localHtml)) {
msg.arg1 = PRE_LOAD_WITH_CACHE;
msg.obj = localHtml;
} else {
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") runSonicFlow has no cache, do first load flow.");
msg.arg1 = PRE_LOAD_NO_CACHE;
}
mainHandler.sendMessage(msg);
}
代码比较少,其实就是发送了一个CLIENT_CORE_MSG_PRE_LOAD + PRE_LOAD_NO_CACHE 消息给主线程。接着我们来看主线程对这个消息的处理,即QuickSonicSession的handleMessage
方法:
public boolean handleMessage(Message msg) {
super.handleMessage(msg);
if (CLIENT_CORE_MSG_BEGIN < msg.what && msg.what < CLIENT_CORE_MSG_END && !clientIsReady.get()) {
pendingClientCoreMessage = Message.obtain(msg);
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleMessage: client not ready, core msg = " + msg.what + ".");
return true;
}
switch (msg.what) {
case CLIENT_CORE_MSG_PRE_LOAD:
handleClientCoreMessage_PreLoad(msg);
break;
case CLIENT_CORE_MSG_FIRST_LOAD:
handleClientCoreMessage_FirstLoad(msg);
break;
case CLIENT_CORE_MSG_CONNECTION_ERROR:
handleClientCoreMessage_ConnectionError(msg);
break;
case CLIENT_CORE_MSG_SERVICE_UNAVAILABLE:
handleClientCoreMessage_ServiceUnavailable(msg);
break;
case CLIENT_CORE_MSG_DATA_UPDATE:
handleClientCoreMessage_DataUpdate(msg);
break;
case CLIENT_CORE_MSG_TEMPLATE_CHANGE:
handleClientCoreMessage_TemplateChange(msg);
break;
case CLIENT_MSG_NOTIFY_RESULT:
setResult(msg.arg1, msg.arg2, true);
break;
case CLIENT_MSG_ON_WEB_READY: {
diffDataCallback = (SonicDiffDataCallback) msg.obj;
setResult(srcResultCode, finalResultCode, true);
break;
}
default: {
if (SonicUtils.shouldLog(Log.DEBUG)) {
SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") can not recognize refresh type: " + msg.what);
}
return false;
}
}
return true;
}
这个方法主要判断当前客户端webview是否已经初始化完毕,即clientIsReady.get()
的返回值是否为真,而这个值则是在我们插入代码的最后调用SnoicSession.onClientReady
中更新值得。之后会分析,假如此时webview没有初始化完成,则我们将这个消息赋值给pendingClientCoreMessage,如果初始化已经好了,则调用handleClientCoreMessage_PreLoad
,这里我们假设还没有初始化完成,即把消息给了pendingClientCoreMessage。到这里主线程接受消息做了介绍,然后回到子线程runSonicFlow方法继续看,已经忘记这个方法的盆友回头再去看一眼。我们说完了handleLocalHtml方法。然后来看runSonicFlow中调用的另一个核心代码,handleFlow_Connection(htmlString);
protected void handleFlow_Connection(String htmlString) {
......
sessionConnection = SonicSessionConnectionInterceptor.getSonicSessionConnection(this, intent);
// connect
long startTime = System.currentTimeMillis();
int responseCode = sessionConnection.connect();
if (SonicConstants.ERROR_CODE_SUCCESS == responseCode) {
statistics.connectionConnectTime = System.currentTimeMillis();
if (SonicUtils.shouldLog(Log.DEBUG)) {
SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") connection connect cost = " + (System.currentTimeMillis() - startTime) + " ms.");
}
startTime = System.currentTimeMillis();
responseCode = sessionConnection.getResponseCode();
statistics.connectionRespondTime = System.currentTimeMillis();
if (SonicUtils.shouldLog(Log.DEBUG)) {
SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") connection response cost = " + (System.currentTimeMillis() - startTime) + " ms.");
}
// If the page has set cookie, sonic will set the cookie to kernel.
startTime = System.currentTimeMillis();
Map> HeaderFieldsMap = sessionConnection.getResponseHeaderFields();
if (SonicUtils.shouldLog(Log.DEBUG)) {
SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") connection get header fields cost = " + (System.currentTimeMillis() - startTime) + " ms.");
}
if (null != HeaderFieldsMap) {
String keyOfSetCookie = null;
if (HeaderFieldsMap.containsKey("Set-Cookie")) {
keyOfSetCookie = "Set-Cookie";
} else if (HeaderFieldsMap.containsKey("set-cookie")) {
keyOfSetCookie = "set-cookie";
}
if (!TextUtils.isEmpty(keyOfSetCookie)) {
List cookieList = HeaderFieldsMap.get(keyOfSetCookie);
SonicEngine.getInstance().getRuntime().setCookie(getCurrentUrl(), cookieList);
}
}
}
......
if (TextUtils.isEmpty(htmlString)) {
handleFlow_FirstLoad(); // first mode
} else {
......
}
saveHeaders(sessionConnection);
}
这个方法首先调用SonicSessionConnectionInterceptor.getSonicSessionConnection(this, intent);
拿到SessionConnection的具体对象,然后调用sessionConnection的connect
方法,connect方法只是简单的调用了internalConnect()
方法,这个方法是抽象的,因此我们需要到具体的实现类即SessionConnectionDefaultImpl
内部类中查看 :
protected synchronized int internalConnect() {
URLConnection urlConnection = getConnection();
if (urlConnection instanceof HttpURLConnection) {
HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
try {
httpURLConnection.connect();
return SonicConstants.ERROR_CODE_SUCCESS;
} catch (Throwable e) {
......
}
}
return SonicConstants.ERROR_CODE_UNKNOWN;
}
这里首先调用了getConnection()
方法,我们紧接着看一下这个方法:
private URLConnection getConnection() {
if (null == connectionImpl) {
connectionImpl = createConnection();
if (null != connectionImpl) {
String currentUrl = session.srcUrl;
SonicSessionConfig config = session.config;
connectionImpl.setConnectTimeout(config.CONNECT_TIMEOUT_MILLIS);
connectionImpl.setReadTimeout(config.READ_TIMEOUT_MILLIS);
/**
* {@link SonicSessionConnection#CUSTOM_HEAD_FILED_ACCEPT_DIFF} is need to be set If client needs incrementally updates.
* Note: It doesn't support incrementally updated for template file.
*/
connectionImpl.setRequestProperty(CUSTOM_HEAD_FILED_ACCEPT_DIFF, config.ACCEPT_DIFF_DATA ? "true" : "false");
String eTag = intent.getStringExtra(CUSTOM_HEAD_FILED_ETAG);
if (null == eTag) eTag = "";
connectionImpl.setRequestProperty("If-None-Match", eTag);
String templateTag = intent.getStringExtra(CUSTOM_HEAD_FILED_TEMPLATE_TAG);
if (null == templateTag) templateTag = "";
connectionImpl.setRequestProperty(CUSTOM_HEAD_FILED_TEMPLATE_TAG, templateTag);
connectionImpl.setRequestProperty("method", "GET");
connectionImpl.setRequestProperty("accept-Charset", "utf-8");
connectionImpl.setRequestProperty("accept-Encoding", "gzip");
connectionImpl.setRequestProperty("accept-Language", "zh-CN,zh;");
connectionImpl.setRequestProperty(CUSTOM_HEAD_FILED_SDK_VERSION, "Sonic/" + SonicConstants.SONIC_VERSION_NUM);
SonicRuntime runtime = SonicEngine.getInstance().getRuntime();
String cookie = runtime.getCookie(currentUrl);
if (!TextUtils.isEmpty(cookie)) {
connectionImpl.setRequestProperty("cookie", cookie);
} else {
SonicUtils.log(TAG, Log.ERROR, "create UrlConnection cookie is empty");
}
String userAgent = runtime.getUserAgent();
if (!TextUtils.isEmpty(userAgent)) {
userAgent += " Sonic/" + SonicConstants.SONIC_VERSION_NUM;
} else {
userAgent = "Sonic/" + SonicConstants.SONIC_VERSION_NUM;
}
connectionImpl.setRequestProperty("User-Agent", userAgent);
}
}
return connectionImpl;
}
这里主要是创建URLConnection,并设置header,包括UserAgent和其他如cookie等的头部信息。接着我们回到handleFlow_Connection
方法中,再接着看另一个核心代码 handleFlow_FirstLoad();
protected void handleFlow_FirstLoad() {
SonicSessionConnection.ResponseDataTuple responseDataTuple = sessionConnection.getResponseData(wasInterceptInvoked, null);
if (null == responseDataTuple) {
SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleFlow_FirstLoad error:responseDataTuple is null!");
return;
}
pendingWebResourceStream = new SonicSessionStream(this, responseDataTuple.outputStream, responseDataTuple.responseStream);
String htmlString = null;
if (responseDataTuple.isComplete) {
try {
htmlString = responseDataTuple.outputStream.toString("UTF-8");
} catch (Throwable e) {
pendingWebResourceStream = null;
SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleFlow_FirstLoad error:" + e.getMessage() + ".");
}
}
boolean hasCacheData = !TextUtils.isEmpty(htmlString);
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleFlow_FirstLoad:hasCacheData=" + hasCacheData + ".");
mainHandler.removeMessages(CLIENT_CORE_MSG_PRE_LOAD);
Message msg = mainHandler.obtainMessage(CLIENT_CORE_MSG_FIRST_LOAD);
msg.obj = htmlString;
msg.arg1 = hasCacheData ? FIRST_LOAD_WITH_CACHE : FIRST_LOAD_NO_CACHE;
mainHandler.sendMessage(msg);
......
}
这个方法核心代码是第一句,调用sessionConnection.getResponseData()
方法。我们来看一下
public synchronized ResponseDataTuple getResponseData(AtomicBoolean breakCondition, ByteArrayOutputStream outputStream) {
BufferedInputStream responseStream = getResponseStream();
if (null != responseStream) {
if (null == outputStream) {
outputStream = new ByteArrayOutputStream();
}
byte[] buffer = new byte[session.config.READ_BUF_SIZE];
try {
int n = 0;
while (!breakCondition.get() && -1 != (n = responseStream.read(buffer))) {
outputStream.write(buffer, 0, n);
}
ResponseDataTuple responseDataTuple = new ResponseDataTuple();
responseDataTuple.responseStream = responseStream;
responseDataTuple.outputStream = outputStream;
responseDataTuple.isComplete = -1 == n;
return responseDataTuple;
} catch (Throwable e) {
SonicUtils.log(TAG, Log.ERROR, "getResponseData error:" + e.getMessage() + ".");
}
}
return null;
}
首先我们应该明确这个方法现在还是在子线程中,然后其实主要就是获得outputStream并放在responseDataTuple中返回。但是这里注意breakCondition.get()
方法,当breakCondition的值为true时,会终止读取流,brekCondition
就是传入的wasInterceptInvoked
,而wasInterceptInvoked
从何而来呢,它是SonicSession的一个成员,默认值为false。那么这个值什么时候会为true呢,我们插入的代码中对webviewClient.shouldInterceptRequest
做了重写,然后调用了sonicSession.getSessionClient().requestResource(url)
方法,在这个方法中对wasInterceptInvoked
设置为true,为什么要这么做呢,主要是webview如果开始拦截资源的请求,则应该将还未读完的流和已读取的拼接成SonicSessionStream
,继而赋给pendingWebResourceStream
,这个流在onClientRequestResource
中进而封装成webResourceResponse
然后返回给shouldInterceptRequest
。完成资源的预加载流给webview。
然后我们来看一下当webview初始化结束时,UI线程调用 QuickSonicSession.onClientReady方法:
public boolean onClientReady() {
if (clientIsReady.compareAndSet(false, true)) {
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") onClientReady: have pending client core message ? -> " + (null != pendingClientCoreMessage) + ".");
if (null != pendingClientCoreMessage) {
Message message = pendingClientCoreMessage;
pendingClientCoreMessage = null;
handleMessage(message);
} else if (STATE_NONE == sessionState.get()) {
start();
}
return true;
}
return false;
}
这个方法将clientIsReady
的值置为true,表示webview已经初始化完成,然后post之前Message,就是当时handleFlow_FirstLoad
方法发出的Message
,当时由于webview没有初始化,所以讲Message保存在这个时候再发送出去,接着通过handleMessage
调用handleClientCoreMessage_FirstLoad
方法:
private void handleClientCoreMessage_FirstLoad(Message msg) {
switch (msg.arg1) {
case FIRST_LOAD_NO_CACHE: {
if (wasInterceptInvoked.get()) {
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleClientCoreMessage_FirstLoad:FIRST_LOAD_NO_CACHE.");
setResult(SONIC_RESULT_CODE_FIRST_LOAD, SONIC_RESULT_CODE_FIRST_LOAD, true);
} else {
SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleClientCoreMessage_FirstLoad:url was not invoked.");
}
}
break;
case FIRST_LOAD_WITH_CACHE: {
if (wasLoadUrlInvoked.compareAndSet(false, true)) {
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleClientCoreMessage_FirstLoad:oh yeah, first load hit 304.");
sessionClient.loadDataWithBaseUrlAndHeader(currUrl, (String) msg.obj, "text/html", "utf-8", currUrl, getHeaders());
setResult(SONIC_RESULT_CODE_FIRST_LOAD, SONIC_RESULT_CODE_HIT_CACHE, false);
} else {
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") FIRST_LOAD_WITH_CACHE load url was invoked.");
setResult(SONIC_RESULT_CODE_FIRST_LOAD, SONIC_RESULT_CODE_FIRST_LOAD, true);
}
}
break;
}
}
这时因为之前已经提前加载过url了,所以FIRST_LOAD_WITH_CACHE
为true,接着调用sessionClient.loadDataWithBaseUrlAndHeader(currUrl, (String) msg.obj, "text/html", "utf-8", currUrl, getHeaders());
方法实际就是调用webview的loadDataWithBaseURL
方法。
4、参考
https://github.com/Tencent/VasSonic/blob/master/sonic-android/docs/Sonic%20Quick%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.md
感谢阅读,有什么问题,可以给我留言