美文网首页
VasSonic源码之并行加载

VasSonic源码之并行加载

作者: Sophia_dd35 | 来源:发表于2019-09-17 11:53 被阅读0次

    在WebView初始化的时候,会有一段白屏的时间,在这段时间网络完全是空闲,VasSonic采用并行加载模式,初始化和网络请求并行。

    在Demo的MainActivity的onCreate()中

    @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
            ....
    
           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
               ....
               // create sonic session and run sonic flow
               sonicSession = SonicEngine.getInstance().createSession(url, sessionConfigBuilder.build());
              ...
           }
           // 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
           ....
           // 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());
    
     /**
         *
         * @param url           url for SonicSession Object
         * @param sessionConfig SSonicSession config
         * @return This method will create and return SonicSession Object when url is legal.
         */
        public synchronized SonicSession createSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
            if (isSonicAvailable()) {//database没有在升级
                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;
        }
    
    /**
         *
         * @param sessionId possible sessionId
         * @param pick      When {@code pick} is true and there is SonicSession in {@link #preloadSessionPool},
         *                  it will remove from {@link #preloadSessionPool}
         * @return
         *          Return valid SonicSession Object from {@link #preloadSessionPool} if the specified sessionId is a key in {@link #preloadSessionPool}.
         */
        private SonicSession lookupSession(SonicSessionConfig config, String sessionId, boolean pick) {
            if (!TextUtils.isEmpty(sessionId) && config != null) {
                SonicSession sonicSession = preloadSessionPool.get(sessionId);
                if (sonicSession != null) {
                    //判断session缓存是否过期,以及sessionConfig是否发生变化
                    if (!config.equals(sonicSession.config) ||
                            sonicSession.config.PRELOAD_SESSION_EXPIRED_TIME > 0 && System.currentTimeMillis() - sonicSession.createdTime > sonicSession.config.PRELOAD_SESSION_EXPIRED_TIME) {
                        if (runtime.shouldLog(Log.ERROR)) {
                            runtime.log(TAG, Log.ERROR, "lookupSession error:sessionId(" + sessionId + ") is expired.");
                        }
                        preloadSessionPool.remove(sessionId);
                        sonicSession.destroy();
                        return null;
                    }
    
                    if (pick) {
                        preloadSessionPool.remove(sessionId);
                    }
                }
                return sonicSession;
            }
            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() {
        ...
       SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() {
                @Override
                public void run() {
                    runSonicFlow(true);
                }
            });
        ...
        }
    

    跟到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);
        }
    

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

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

    public class SonicSessionStream extends InputStream {
        private static final String TAG = "SonicSdk_SonicSessionStream";
        private BufferedInputStream netStream;
        private BufferedInputStream memStream;
        private ByteArrayOutputStream outputStream;
        private boolean netStreamReadComplete = true;
        private boolean memStreamReadComplete = true;
        private final WeakReference<SonicSessionStream.Callback> callbackWeakReference;
    
        public SonicSessionStream(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();
            }
            this.callbackWeakReference = new WeakReference(callback);
        }
        ...
        public synchronized int read() throws IOException {
            int c = -1;
    
            try {
                if (null != this.memStream && !this.memStreamReadComplete) {
                    c = this.memStream.read();
                }
    
                if (-1 == c) {
                    this.memStreamReadComplete = true;
                    if (null != this.netStream && !this.netStreamReadComplete) {
                        c = this.netStream.read();
                        if (-1 != c) {
                            this.outputStream.write(c);
                        } else {
                            this.netStreamReadComplete = true;
                        }
                    }
                }
    
                return c;
            } catch (Throwable var3) {
                SonicUtils.log("SonicSdk_SonicSessionStream", 6, "read error:" + var3.getMessage());
                if (var3 instanceof IOException) {
                    throw var3;
                } else {
                    throw new IOException(var3);
                }
            }
        }  
          ...
    }
    

    再看到前面的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<SonicSessionCallback> 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(msg)。

     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<String, String> headers) {
            loadDataWithBaseUrl(baseUrl, data, mimeType, encoding, historyUrl);
        }
    
        public void destroy() {
            if (null != webView) {
                webView.destroy();
                webView = null;
            }
        }
    }
    

    以上就是加载无缓存的情况下的操作,子线程在runSonicFlow中继续执行,会执行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) {
            // create connection for current session
             ...
            server = new SonicServer(this, createConnectionIntent(sessionData));
             ...
            // When cacheHtml is empty, run First-Load flow
            if (!hasCache) {
                handleFlow_FirstLoad();
                return;
            }
          ...
        }
    

    由于是第一次,所以没有缓存,接下来调用 handleFlow_FirstLoad();

     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);
            ...
            //缓存相关操作
            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.getResonseStream(wasInterceptInvoked)会从SonicServer读取网络数据直到客户端发起资源请求。到getResponseStream中,可以在函数readServerResponse中看到如果breakCondition为true就会退出while循环,然后函数返回true,在getResponseStream中就会return SonicSessionStream,这个就是上面返回的pendingWebResourceStream.

    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;
            }
        }
    
    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发起资源请求的时候。

    //MainActivity
    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;
                }
            });
    
    //SonicSessionClient
    public Object requestResource(String url) {
            if (session != null) {
                return session.onClientRequestResource(url);
            }
            return null;
    }
    
    //SonicSession
    public 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 (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;
        }
    

    相关文章

      网友评论

          本文标题:VasSonic源码之并行加载

          本文链接:https://www.haomeiwen.com/subject/xfifuctx.html