[Android] VasSonic H5加載優化加載庫 源碼解讀及需要注意的地方

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、參考

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

感謝閱讀,有什么問題,可以給我留言

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容