博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
VasSonic源码之并行加载
阅读量:6366 次
发布时间:2019-06-23

本文共 37458 字,大约阅读时间需要 124 分钟。

欢迎关注微信公众号: JueCode

VasSonic是腾讯开源的一套完整的Hybrid方案,Github地址: ,官方定义是一套轻量级和高性能的Hybrid框架,专注于提升H5首屏加载速度。今天主要分享下其中的一个技术,并行加载技术。在开始之前先了解一个核心概念SonicSession,VasSonic将一次URL请求抽象为SonicSession。SonicSession 在 VasSonic 的设计里面非常关键。其将资源的请求和 WebView 脱离开来,有了 SonicSession,结合 SonicCache,就可以不依赖 WebView 去做资源的请求,这样就可以实现 WebView 打开和资源加载并行、资源预加载等加速方案。

下面正式进入并行加载技术分析

并行加载其实主要是两个方面,一个是在WebView初始化时线程池发起网络请求,另外一个就是通过添加中间层 BridgeStream 来连接 WebView 和数据流,中间层 BridgeStream 会先把内存的数据读取返回后,再继续读取网络的数据,看一张官方的图片:

大家知道,客户端在WebView启动的时候需要先初始化内核,会有一段白屏的时间,在这段时间网络完全是空闲在等待的,非常浪费,VasSonic采用并行加载模式,初始化内核和发起网络请求并行。

在Demo中BrowserActivity中的onCreate中, 有两条线,一个是在if (MainActivity.MODE_DEFAULT != mode)中会创建SonicSession,然后在线程池中runSonicFlow,包括读取缓存,连接LocalServer, 拆分模板和数据等; 另外一个就是主线程中初始化WebView,这就实现了并行加载的目的。

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Intent intent = getIntent();        String url = intent.getStringExtra(PARAM_URL);        int mode = intent.getIntExtra(PARAM_MODE, -1);        if (TextUtils.isEmpty(url) || -1 == mode) {            finish();            return;        }        getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);        // init sonic engine if necessary, or maybe u can do this when application created        if (!SonicEngine.isGetInstanceAllowed()) {            SonicEngine.createInstance(new SonicRuntimeImpl(getApplication()), new SonicConfig.Builder().build());        }        SonicSessionClientImpl sonicSessionClient = null;        // if it's sonic mode , startup sonic session at first time        if (MainActivity.MODE_DEFAULT != mode) { // sonic mode            SonicSessionConfig.Builder sessionConfigBuilder = new SonicSessionConfig.Builder();            sessionConfigBuilder.setSupportLocalServer(true);            // if it's offline pkg mode, we need to intercept the session connection            if (MainActivity.MODE_SONIC_WITH_OFFLINE_CACHE == mode) {                sessionConfigBuilder.setCacheInterceptor(new SonicCacheInterceptor(null) {                    @Override                    public String getCacheData(SonicSession session) {                        return null; // offline pkg does not need cache                    }                });                sessionConfigBuilder.setConnectionInterceptor(new SonicSessionConnectionInterceptor() {                    @Override                    public SonicSessionConnection getConnection(SonicSession session, Intent intent) {                        return new OfflinePkgSessionConnection(BrowserActivity.this, session, intent);                    }                });            }            // create sonic session and run sonic flow            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!");                Toast.makeText(this, "create sonic session fail!", Toast.LENGTH_LONG).show();            }        }        // start init flow ...        // in the real world, the init flow may cost a long time as startup        // runtime、init configs....        setContentView(R.layout.activity_browser);        FloatingActionButton btnFab = (FloatingActionButton) findViewById(R.id.btn_refresh);        btnFab.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if (sonicSession != null) {                    sonicSession.refresh();                }            }        });        // init webview        WebView webView = (WebView) findViewById(R.id.webview);        webView.setWebViewClient(new WebViewClient() {            @Override            public void onPageFinished(WebView view, String url) {                super.onPageFinished(view, url);                if (sonicSession != null) {                    sonicSession.getSessionClient().pageFinish(url);                }            }            @TargetApi(21)            @Override            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {                return shouldInterceptRequest(view, request.getUrl().toString());            }            @Override            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {                if (sonicSession != null) {                    return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);                }                return null;            }        });        WebSettings webSettings = webView.getSettings();        // add java script interface        // note:if api level lower than 17(android 4.2), addJavascriptInterface has security        // issue, please use x5 or see https://developer.android.com/reference/android/webkit/        // WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)        webSettings.setJavaScriptEnabled(true);        webView.removeJavascriptInterface("searchBoxJavaBridge_");        intent.putExtra(SonicJavaScriptInterface.PARAM_LOAD_URL_TIME, System.currentTimeMillis());        webView.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic");        // init webview settings        webSettings.setAllowContentAccess(true);        webSettings.setDatabaseEnabled(true);        webSettings.setDomStorageEnabled(true);        webSettings.setAppCacheEnabled(true);        webSettings.setSavePassword(false);        webSettings.setSaveFormData(false);        webSettings.setUseWideViewPort(true);        webSettings.setLoadWithOverviewMode(true);        // webview is ready now, just tell session client to bind        if (sonicSessionClient != null) {            sonicSessionClient.bindWebView(webView);            sonicSessionClient.clientReady();        } else { // default mode            webView.loadUrl(url);        }    }复制代码

在SonicEngine会构建url对应的SonicSession,其中

sonicSession = SonicEngine.getInstance().createSession(url, sessionConfigBuilder.build());复制代码

跟到SonicEngine中,第一次进来缓存中没有对应的sessionId,所以走到internalCreateSession中,

public synchronized SonicSession createSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {        if (isSonicAvailable()) {            String sessionId = makeSessionId(url, sessionConfig.IS_ACCOUNT_RELATED);            if (!TextUtils.isEmpty(sessionId)) {                SonicSession sonicSession = lookupSession(sessionConfig, sessionId, true);                if (null != sonicSession) {                    sonicSession.setIsPreload(url);                } else if (isSessionAvailable(sessionId)) { // 缓存中未存在                    sonicSession = internalCreateSession(sessionId, url, sessionConfig);                }                return sonicSession;            }        } else {            runtime.log(TAG, Log.ERROR, "createSession fail for sonic service is unavailable!");        }        return null;}    /**     * Create sonic session internal     *     * @param sessionId session id     * @param url origin url     * @param sessionConfig session config     * @return Return new SonicSession if there was no mapping for the sessionId in {@link #runningSessionHashMap}     */    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.addSessionStateChangedCallback(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;    }    复制代码

在SonicSessionConfig中默认:

/** * The mode of SonicSession, include{@link QuickSonicSession} and {@link StandardSonicSession} */int sessionMode = SonicConstants.SESSION_MODE_QUICK;复制代码

所以后面我们以QuickSonicSession为例分析并行加载技术,接着到SonicSession中的start, runSonicFlow(true)会在线程池中运行,

/**     * Start the sonic process     */    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.");        for (WeakReference
ref : sessionCallbackList) { SonicSessionCallback callback = ref.get(); if (callback != null) { callback.onSonicSessionStart(); } } statistics.sonicStartTime = System.currentTimeMillis(); isWaitingForSessionThread.set(true); SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() { @Override public void run() { runSonicFlow(true); } }); notifyStateChange(STATE_NONE, STATE_RUNNING, null); }复制代码

跟到SonicRuntime中

/**     * Post a task to session thread(a high priority thread is better)     *     * @param task A runnable task     */    public void postTaskToSessionThread(Runnable task) {        SonicSessionThreadPool.postTask(task);    }复制代码

接着到SonicSessionThreadPool中, 其中线程池启的每个线程名称前缀是:"pool-sonic-session-thread-"

/** * SonicSession ThreadPool */class SonicSessionThreadPool {    /**     * Log filter     */    private final static String TAG = SonicConstants.SONIC_SDK_LOG_PREFIX + "SonicSessionThreadPool";    /**     * Singleton object     */    private final static SonicSessionThreadPool sInstance = new SonicSessionThreadPool();    /**     * ExecutorService object (Executors.newCachedThreadPool())     */    private final ExecutorService executorServiceImpl;    /**     * SonicSession ThreadFactory     */    private static class SessionThreadFactory implements ThreadFactory {        /**         * Thread group         */        private final ThreadGroup group;        /**         * Thread number         */        private final AtomicInteger threadNumber = new AtomicInteger(1);        /**         * Thread prefix name         */        private final static String NAME_PREFIX = "pool-sonic-session-thread-";        /**         * Constructor         */        SessionThreadFactory() {            SecurityManager securityManager = System.getSecurityManager();            this.group = securityManager != null ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup();        }        /**         * Constructs a new {@code Thread}.  Implementations may also initialize         * priority, name, daemon status, {@code ThreadGroup}, etc.         *         * @param r A runnable to be executed by new thread instance         * @return Constructed thread, or {@code null} if the request to         * create a thread is rejected         */        public Thread newThread(@NonNull Runnable r) {            Thread thread = new Thread(this.group, r, NAME_PREFIX + this.threadNumber.getAndIncrement(), 0L);            if (thread.isDaemon()) {                thread.setDaemon(false);            }            if (thread.getPriority() != 5) {                thread.setPriority(5);            }            return thread;        }    }    /**     * Constructor and initialize thread pool object     * default one core pool and the maximum number of threads is 6     *     */    private SonicSessionThreadPool() {        executorServiceImpl = new ThreadPoolExecutor(1, 6,                60L, TimeUnit.SECONDS,                new SynchronousQueue
(), new SessionThreadFactory()); } /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param task The runnable task * @return Submit success or not */ private boolean execute(Runnable task) { try { executorServiceImpl.execute(task); return true; } catch (Throwable e) { SonicUtils.log(TAG, Log.ERROR, "execute task error:" + e.getMessage()); return false; } } /** * Post an runnable to the pool thread * * @param task The runnable task * @return Submit success or not */ static boolean postTask(Runnable task) { return sInstance.execute(task); }}复制代码

并行处理是可以加快处理速度,如果终端初始化比较快,但是数据还没有完成返回,这样内核就会在空等,而内核是支持边加载边渲染,所以VasSonic在并行的同时,也利用了内核的这个特性。采用了一个中间层SonicSessionStream桥接内核和数据,也就是流式拦截:

先看下SonicSessionStream, SonicSessionStream用来桥接两个流,一个是内存流(memStream),一个是网络流(netStream), 在read的时候优先从内存流中读取,再从网络流读取。

/** * * A SonicSessionStream obtains input bytes * from a memStream and a netStream. * memStreamis read data from network, netStreamis unread data from network. * */public class SonicSessionStream extends InputStream {    /**     * Log filter     */    private static final String TAG = SonicConstants.SONIC_SDK_LOG_PREFIX + "SonicSessionStream";    /**     * Unread data from network     */    private BufferedInputStream netStream;    /**     * Read data from network     */    private BufferedInputStream memStream;    /**     * OutputStream include memStream data and netStream data     */    private ByteArrayOutputStream outputStream;    /**     * netStream data completed flag     */    private boolean netStreamReadComplete = true;    /**     * memStream data completed flag     */    private boolean memStreamReadComplete = true;    /**     * Constructor     *     * @param callback     Callback     * @param outputStream Read data from network     * @param netStream    Unread data from network     */    public SonicSessionStream(Callback callback, ByteArrayOutputStream outputStream, BufferedInputStream netStream) {        if (null != 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); } ... /** * *

* Reads a single byte from this stream and returns it as an integer in the * range from 0 to 255. Returns -1 if the end of the stream has been * reached. Blocks until one byte has been read, the end of the source * stream is detected or an exception is thrown. * * @throws IOException if the stream is closed or another IOException occurs. */ @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) { SonicUtils.log(TAG, Log.ERROR, "read error:" + e.getMessage()); if (e instanceof IOException) { throw e; } else {//Turn all exceptions to IO exceptions to prevent scenes that the kernel can not capture throw new IOException(e); } } return c; }}复制代码

再看到前面的`runSonicFlow中, 第一次发起请求firstRequest=true,之后会进入handleFlow_LoadLocalCache(cacheHtml),

private void runSonicFlow(boolean firstRequest) {        ...        if (firstRequest) {            cacheHtml = SonicCacheInterceptor.getSonicCacheData(this);            statistics.cacheVerifyTime = System.currentTimeMillis();            SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") runSonicFlow verify cache cost " + (statistics.cacheVerifyTime - statistics.sonicFlowStartTime) + " ms");            handleFlow_LoadLocalCache(cacheHtml); // local cache if exist before connection        }        boolean hasHtmlCache = !TextUtils.isEmpty(cacheHtml) || !firstRequest;        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(hasHtmlCache, sessionData);            statistics.connectionFlowFinishTime = System.currentTimeMillis();        }        // Update session state        switchState(STATE_RUNNING, STATE_READY, true);        isWaitingForSessionThread.set(false);        // Current session can be destroyed if it is waiting for destroy.        if (postForceDestroyIfNeed()) {            SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") runSonicFlow:send force destroy message.");        }    }复制代码

以QuickSonicSession为例,看下handleFlow_LoadLocalCache(cacheHtml), 会通过mainHandler给主线程发消息CLIENT_CORE_MSG_PRE_LOAD,

/**     * Handle load local cache of html if exist.     * This handle is called before connection.     *     * @param cacheHtml local cache of html     */    @Override    protected void handleFlow_LoadLocalCache(String cacheHtml) {        Message msg = mainHandler.obtainMessage(CLIENT_CORE_MSG_PRE_LOAD);        if (!TextUtils.isEmpty(cacheHtml)) {            msg.arg1 = PRE_LOAD_WITH_CACHE;            msg.obj = cacheHtml;        } 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);        for (WeakReference
ref : sessionCallbackList) { SonicSessionCallback callback = ref.get(); if (callback != null) { callback.onSessionLoadLocalCache(cacheHtml); } } }复制代码

在handlerMessage中:

@Override    public boolean handleMessage(Message msg) {        // fix issue[https://github.com/Tencent/VasSonic/issues/89]        if (super.handleMessage(msg)) {            return true; // handled by super class        }        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;    }复制代码

进入handleClientCoreMessage_PreLoad,在没有缓存情况下, WebView会调用loadUrl先行加载url页面。

/**     * Handle the preload message. If the type of this message is PRE_LOAD_NO_CACHE  and client did not     * initiate request for load url,client will invoke loadUrl method. If the type of this message is     * PRE_LOAD_WITH_CACHE and and client did not initiate request for loadUrl,client will load local data.     *     * @param msg The message     */    private void handleClientCoreMessage_PreLoad(Message msg) {        switch (msg.arg1) {            case PRE_LOAD_NO_CACHE: {                if (wasLoadUrlInvoked.compareAndSet(false, true)) {                    SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleClientCoreMessage_PreLoad:PRE_LOAD_NO_CACHE load url.");                    sessionClient.loadUrl(srcUrl, null);                } else {                    SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleClientCoreMessage_PreLoad:wasLoadUrlInvoked = true.");                }            }            break;            case PRE_LOAD_WITH_CACHE: {                if (wasLoadDataInvoked.compareAndSet(false, true)) {                    SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleClientCoreMessage_PreLoad:PRE_LOAD_WITH_CACHE load data.");                    String html = (String) msg.obj;                    sessionClient.loadDataWithBaseUrlAndHeader(srcUrl, html, "text/html",                            SonicUtils.DEFAULT_CHARSET, srcUrl, getCacheHeaders());                } else {                    SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleClientCoreMessage_PreLoad:wasLoadDataInvoked = true.");                }            }            break;        }    }复制代码

之后就会调用WebView的loadurl:

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; } }}复制代码

这个就是主线程在第一次加载无缓存的情况下的操作,子线程在runSonicFlow中接着往下走,Sonic在post消息到主线程之后会通过SonicSessionConnection建立一个URLConnection,接着通过这个连接获取服务器返回的数据。由于获取网络数据是个耗时的过程,所以在读取网络数据的过程中会不断的判断webView是否发起资源拦截请求(通过SonicSession的wasInterceptInvoked来判断),如果webview已经发起资源拦截请求,就中断网络数据的读取,将已经读取的数据和未读取的网络数据拼接成桥接流SonicSessionStream,并将其赋值给SonicSession的pendingWebResourceStream。如果整个网络数据读取完毕之后webview还没有初始化完,那么就会把之前post的CLIENT_CORE_MSG_PRE_LOAD的消息cancel调。同时post一个CLIENT_CORE_MSG_FIRST_LOAD的消息到主线程。之后再对html内容进行模版分割及数据保存。

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(hasHtmlCache, sessionData);            statistics.connectionFlowFinishTime = System.currentTimeMillis();        }        // Update session state        switchState(STATE_RUNNING, STATE_READY, true);复制代码

之后走到handleFlow_Connection(hasHtmlCache, sessionData);,

/**     * Initiate a network request to obtain server data.     *     * @param hasCache Indicates local sonic cache is exist or not.     * @param sessionData  SessionData holds eTag templateTag     */    protected void handleFlow_Connection(boolean hasCache, SonicDataHelper.SessionData sessionData) {            ...        server = new SonicServer(this, createConnectionIntent(sessionData));        // Connect to web server        int responseCode = server.connect();        if (SonicConstants.ERROR_CODE_SUCCESS == responseCode) {            responseCode = server.getResponseCode();            // If the page has set cookie, sonic will set the cookie to kernel.            long startTime = System.currentTimeMillis();            Map
> headerFieldsMap = server.getResponseHeaderFields(); if (SonicUtils.shouldLog(Log.DEBUG)) { SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") connection get header fields cost = " + (System.currentTimeMillis() - startTime) + " ms."); } startTime = System.currentTimeMillis(); setCookiesFromHeaders(headerFieldsMap, shouldSetCookieAsynchronous()); if (SonicUtils.shouldLog(Log.DEBUG)) { SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") connection set cookies cost = " + (System.currentTimeMillis() - startTime) + " ms."); } } ... // When cacheHtml is empty, run First-Load flow if (!hasCache) { handleFlow_FirstLoad(); return; } ... }复制代码

handleFlow_FirstLoad中,在函数中第一行代码server.getResponseStream(wasInterceptInvoked),从网络连接中持续读取数据流到outputstream中,如果WebView发起资源请求,就会置wasInterceptInvoked为true,这样在getResponseStream会构造SonicSessionStream

/**     *     * In this case sonic will always read the new data from the server until the client     * initiates a resource interception.     *     * If the server data is read finished, sonic will send CLIENT_CORE_MSG_FIRST_LOAD     * message with the new html content from server.     *     * If the server data is not read finished sonic will split the read and unread data into     * a bridgedStream{@link SonicSessionStream}.When client initiates a resource interception,     * sonic will provide the bridgedStream to the kernel.     *     * 

* If need save and separate data, sonic will save the server data and separate the server data * to template and data. * */ protected void handleFlow_FirstLoad() { pendingWebResourceStream = server.getResponseStream(wasInterceptInvoked); if (null == pendingWebResourceStream) { SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleFlow_FirstLoad error:server.getResponseStream is null!"); return; } String htmlString = server.getResponseData(false); boolean hasCompletionData = !TextUtils.isEmpty(htmlString); SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleFlow_FirstLoad:hasCompletionData=" + hasCompletionData + "."); mainHandler.removeMessages(CLIENT_CORE_MSG_PRE_LOAD); Message msg = mainHandler.obtainMessage(CLIENT_CORE_MSG_FIRST_LOAD); msg.obj = htmlString; msg.arg1 = hasCompletionData ? FIRST_LOAD_WITH_DATA : FIRST_LOAD_NO_DATA; mainHandler.sendMessage(msg); for (WeakReference

ref : sessionCallbackList) { SonicSessionCallback callback = ref.get(); if (callback != null) { callback.onSessionFirstLoad(htmlString); } } String cacheOffline = server.getResponseHeaderField(SonicSessionConnection.CUSTOM_HEAD_FILED_CACHE_OFFLINE); if (SonicUtils.needSaveData(config.SUPPORT_CACHE_CONTROL, cacheOffline, server.getResponseHeaderFields())) { if (hasCompletionData && !wasLoadUrlInvoked.get() && !wasInterceptInvoked.get()) { // Otherwise will save cache in com.tencent.sonic.sdk.SonicSession.onServerClosed switchState(STATE_RUNNING, STATE_READY, true); postTaskToSaveSonicCache(htmlString); } } else { SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleFlow_FirstLoad:offline->" + cacheOffline + " , so do not need cache to file."); } }复制代码

在server.getResponseStream(wasInterceptInvoked)会从SonicServer读取网络数据知道客户端发起资源请求,到getResponseStream中,可以在函数readServerResponse中看到如果breakCondition为true就会退出while循环,然后函数返回true,在getResponseStream中就会return SonicSessionStream,这个就是上面返回的pendingWebResourceStream.

/**     * Read all of data from {@link SonicSessionConnection#getResponseStream()} into byte array output stream {@code outputStream} until     * {@code breakCondition} is true when {@code breakCondition} is not null.     * Then return a {@code SonicSessionStream} obtains input bytes     * from  {@code outputStream} and a {@code netStream} when there is unread data from network.     *     * @param breakConditions This method won't read any data from {@link SonicSessionConnection#getResponseStream()} if {@code breakCondition} is true.     * @return Returns a {@code SonicSessionStream} obtains input bytes     * from  {@code outputStream} and a {@code netStream} when there is unread data from network.     */    public synchronized InputStream getResponseStream(AtomicBoolean breakConditions) {        if (readServerResponse(breakConditions)) {            BufferedInputStream netStream = !TextUtils.isEmpty(serverRsp) ? null : connectionImpl.getResponseStream();            return new SonicSessionStream(this, outputStream, netStream);        } else {            return null;        }    }        /**     * Read all of data from {@link SonicSessionConnection#getResponseStream()} into byte array output stream {@code outputStream} until     * {@code breakCondition} is true if {@code breakCondition} is not null.     *  And then this method convert outputStream into response string {@code serverRsp} at the end of response stream.     *     * @param breakCondition This method won't read any data from {@link SonicSessionConnection#getResponseStream()} if {@code breakCondition} is true.     * @return True when read any of data from {@link SonicSessionConnection#getResponseStream()} and write into {@code outputStream}     */    private boolean readServerResponse(AtomicBoolean breakCondition) {        if (TextUtils.isEmpty(serverRsp)) {            BufferedInputStream bufferedInputStream = connectionImpl.getResponseStream();            if (null == bufferedInputStream) {                SonicUtils.log(TAG, Log.ERROR, "session(" + session.sId + ") readServerResponse error: bufferedInputStream is null!");                return false;            }            try {                byte[] buffer = new byte[session.config.READ_BUF_SIZE];                int n = 0;                while (((breakCondition == null) || !breakCondition.get()) && -1 != (n = bufferedInputStream.read(buffer))) {                    outputStream.write(buffer, 0, n);                }                if (n == -1) {                    serverRsp = outputStream.toString(session.getCharsetFromHeaders());                }            } catch (Exception e) {                SonicUtils.log(TAG, Log.ERROR, "session(" + session.sId + ") readServerResponse error:" + e.getMessage() + ".");                return false;            }        }        return true;    }复制代码

wasInterceptInvoked是在什么时候设置为true?在WebView发起资源请求,

//BrowserActivitywebView.setWebViewClient(new WebViewClient() {            @Override            public void onPageFinished(WebView view, String url) {                super.onPageFinished(view, url);                if (sonicSession != null) {                    sonicSession.getSessionClient().pageFinish(url);                }            }            @TargetApi(21)            @Override            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {                return shouldInterceptRequest(view, request.getUrl().toString());            }            @Override            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {                if (sonicSession != null) {                    return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);                }                return null;            }        });复制代码
//SonicSessionClientpublic Object requestResource(String url) {        if (session != null) {            return session.onClientRequestResource(url);        }        return null;}复制代码
//SonicSessionpublic final Object onClientRequestResource(String url) {        String currentThreadName = Thread.currentThread().getName();        if (CHROME_FILE_THREAD.equals(currentThreadName)) {            resourceInterceptState.set(RESOURCE_INTERCEPT_STATE_IN_FILE_THREAD);        } else {            resourceInterceptState.set(RESOURCE_INTERCEPT_STATE_IN_OTHER_THREAD);            if (SonicUtils.shouldLog(Log.DEBUG)) {                SonicUtils.log(TAG, Log.DEBUG, "onClientRequestResource called in " + currentThreadName + ".");            }        }        Object object = isMatchCurrentUrl(url)                ? onRequestResource(url)                : (resourceDownloaderEngine != null ? resourceDownloaderEngine.onRequestSubResource(url, this) : null);        resourceInterceptState.set(RESOURCE_INTERCEPT_STATE_NONE);        return object;    }复制代码

发起资源请求的host和path如果都和构造SonicSession的url一致就会走到QuickSonicSession中的onRequestResource,这里就会用上面提到的pendingWebResourceStream构造webResourceResponse返回给WebView.

protected Object onRequestResource(String url) {        if (wasInterceptInvoked.get() || !isMatchCurrentUrl(url)) {            return null;        }        if (!wasInterceptInvoked.compareAndSet(false, true)) {            SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ")  onClientRequestResource error:Intercept was already invoked, url = " + url);            return null;        }        if (SonicUtils.shouldLog(Log.DEBUG)) {            SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ")  onClientRequestResource:url = " + url);        }        long startTime = System.currentTimeMillis();        if (sessionState.get() == STATE_RUNNING) {            synchronized (sessionState) {                try {                    if (sessionState.get() == STATE_RUNNING) {                        SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") now wait for pendingWebResourceStream!");                        sessionState.wait(30 * 1000);                    }                } catch (Throwable e) {                    SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") wait for pendingWebResourceStream failed" + e.getMessage());                }            }        } else {            if (SonicUtils.shouldLog(Log.DEBUG)) {                SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") is not in running state: " + sessionState);            }        }        SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") have pending stream? -> " + (pendingWebResourceStream != null) + ", cost " + (System.currentTimeMillis() - startTime) + "ms.");        if (null != pendingWebResourceStream) {            Object webResourceResponse;            if (!isDestroyedOrWaitingForDestroy()) {                String mime = SonicUtils.getMime(srcUrl);                webResourceResponse = SonicEngine.getInstance().getRuntime().createWebResourceResponse(mime,                        getCharsetFromHeaders(), pendingWebResourceStream, getHeaders());            } else {                webResourceResponse = null;                SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") onClientRequestResource error: session is destroyed!");            }            pendingWebResourceStream = null;            return webResourceResponse;        }        return null;    }复制代码

看一张onRequestResource的debug图:

VasSonic是一个比较完善的Hybrid框架,里面有很多可以学习的东西,篇幅所限,这次只分析到里面用到的并行加载技术,后面会有其他分享的内容,比如流式拦截,模板和数据拆分,LocalServer等。

今天的车就开到这了,欢迎关注后面分享。

转载地址:http://ijrma.baihongyu.com/

你可能感兴趣的文章
keepalived实现服务高可用
查看>>
iOS模型以及使用
查看>>
NSString 去除空格
查看>>
swift - 网络请求数据处理 - 协议处理
查看>>
[BZOJ1588]营业额统计(Splay)
查看>>
[BZOJ 4869][SHOI&SXOI2017]相逢是问候(扩展欧拉定理+线段树)
查看>>
2017-08-13
查看>>
条件语句优化面面观
查看>>
集成友盟微信登录没有回调
查看>>
在CentOS Linux系统上,添加新的端口,启用ssh服务
查看>>
dbcp数据库连接池简单使用
查看>>
leetcode-38-Count and Say
查看>>
从零开始写一个node爬虫(上)—— 数据采集篇
查看>>
java调用远程服务器shell脚本
查看>>
贪吃蛇
查看>>
前端MVC学习总结(四)——NodeJS+MongoDB+AngularJS+Bootstrap书店示例
查看>>
Myeclipse快捷键集合
查看>>
linux安装ftp
查看>>
[转]解读ASP.NET 5 & MVC6系列(8):Session与Caching
查看>>
js正则匹配中文
查看>>