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<String, String> 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<String> 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<String, String> 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<String, List<String>> 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<String> 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. <br>
* <p><b>Note: It doesn't support incrementally updated for template file.</b><p/>
*/
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、參考
感謝閱讀,有什么問題,可以給我留言