What are the pain points?

Web pages load slowly, white screen, use lag.

Why is this a problem?

1. The web page loading process starts when the loadUrl() method is called. 2. Loading too many images 4. Webview itself problem

How does Webiew load web pages?

Webview initialization ->DOM download →DOM parsing →CSS request + download →CSS parsing → Rendering → Rendering → Composition

What is the direction of optimization?

1. Webview itself optimization

  • Advance kernel initialization code:
public class App extends Application {

    private WebView mWebView ;
    @Override
    public void onCreate(a) {
        super.onCreate();
        mWebView = new WebView(new MutableContextWrapper(this)); }}Copy the code

Effect: See the picture below

  • Webview reuse pool code:
public class WebPools {
    private final Queue<WebView> mWebViews;
    private Object lock = new Object();
    private static WebPools mWebPools = null;
    private static final AtomicReference<WebPools> mAtomicReference = new AtomicReference<>();
    private static final String TAG=WebPools.class.getSimpleName();

    private WebPools(a) {
        mWebViews = new LinkedBlockingQueue<>();
    }
    public static WebPools getInstance(a) {
        for(; ;) {if(mWebPools ! =null)
                return mWebPools;
            if (mAtomicReference.compareAndSet(null.new WebPools()))
                returnmWebPools=mAtomicReference.get(); }}public void recycle(WebView webView) {
        recycleInternal(webView);
    }
    public WebView acquireWebView(Activity activity) {
        return acquireWebViewInternal(activity);
    }
    private WebView acquireWebViewInternal(Activity activity) {
        WebView mWebView = mWebViews.poll();
        LogUtils.i(TAG,"acquireWebViewInternal webview:"+mWebView);
        if (mWebView == null) {
            synchronized (lock) {
                return new WebView(newMutableContextWrapper(activity)); }}else {
            MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
            mMutableContextWrapper.setBaseContext(activity);
            returnmWebView; }}private void recycleInternal(WebView webView) {
        try {
            if (webView.getContext() instanceof MutableContextWrapper) {
                MutableContextWrapper mContext = (MutableContextWrapper) webView.getContext();
             mContext.setBaseContext(mContext.getApplicationContext());
                LogUtils.i(TAG,"enqueue webview:"+webView);
                mWebViews.offer(webView);
            }
            if(webView.getContext() instanceof  Activity){
                //throw new RuntimeException("leaked");
                LogUtils.i(TAG,"Abandon this webview, It will cause leak if enqueue!"); }}catch(Exception e){ e.printStackTrace(); }}}Copy the code

Problems: Memory leaks using pre-created and reused pools

  • Standalone process, process preloaded code:
        <service
            android:name=".PreWebService"
            android:process=":web"/>
        <activity
            android:name=".WebActivity"
            android:process=":web"/>
Copy the code

Before starting the WebView page, start the PreWebService to create the [Web] process. When starting the WebActivity, the system will find that the [Web] process already exists, so there is no need to spend time to Fork a new [Web] process.

  • Use x5 kernel directly use Tencent X5 kernel, replace the native browser kernel
  • Effect:
    • For the first time open
    • Secondary open
  • Other solutions: 1. Set up webView cache 2. Load animation/finally let image download 3. Turn off image loading during rendering 4. Set timeout 5. Enable software and hardware acceleration

2. Optimization when loading resources This optimization mostly uses a third party, as described below

3. The optimization of the web page is optimized by the front-end engineer of the web page, or together with the mobile end, the web page will realize incremental update, dynamic update. App built-in CSS, JS files and control version

Note: If you’re hoping to speed up your web page with webView setting alone, you’re going to be disappointed. There is very little improvement that can be made by changing the Settings. Therefore, this article focuses on the analysis and comparison of the advantages and disadvantages of the third-party WebView framework that can be used now.

Now the big factory has the following methods:

  1. VasSonic
  2. TBS Tencent Browsing service
  3. Baidu APP Solution
  4. Toutiao Program

VasSonic

Reference article: blog.csdn.net/tencent__op… Access method: STEP1:

// Import Tencent/VasSonic implementation'com. Tencent. Sonic: SDK: 3.1.0'
Copy the code

STEP2:

// Create a class that inherits SonicRuntime
// The SonicRuntime class provides the sonic runtime environment, including Context, user UA, user ID, etc. The following code shows several methods of SonicRuntime.
public class TTPRuntime extends SonicRuntime
{
	/ / initialization
	public TTPRuntime( Context context )
	{
		super(context);
	}
	
	@Override
	public void log(
			String tag ,
			int level ,
			String message )
	{
		/ / the log Settings
	}
	
	/ / get a cookie
	@Override
	public String getCookie( String url )
	{
		return null;
	}
	
	/ / set cookid
	@Override
	public boolean setCookie( String url , List
       
         cookies )
       
	{
		return false;
	}
	
	// Obtain user UA information
	@Override
	public String getUserAgent(a)
	{
		return "Mozilla / 5.0 (Linux; Android 5.1.1. Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36";
	}
	
	// Get user ID information
	@Override
	public String getCurrentUserAccount(a)
	{
		return "ttpp";
	}
	
	// Whether to use Sonic acceleration
	@Override
	public boolean isSonicUrl( String url )
	{
		return true;
	}
	
	// Create a Web resource request
	@Override
	public Object createWebResourceResponse( String mimeType , String encoding , InputStream data , Map
       
         headers )
       ,>
	{
		return null;
	}
	
	// Network enabled or not
	@Override
	public boolean isNetworkValid(a)
	{
		return true;
	}
	
	@Override
	public void showToast(
			CharSequence text ,
			int duration )
	{}@Override
	public void postTaskToThread(
			Runnable task ,
			long delayMillis )
	{}@Override
	public void notifyError(
			SonicSessionClient client ,
			String url ,
			int errorCode )
	{}// Set the Sonic cache address
	@Override
	public File getSonicCacheDir(a)
	{
		return super.getSonicCacheDir(); }}Copy the code

STEP3:

// Create a class that inherits SonicSessionClien
//SonicSessionClient is responsible for communicating with webView, such as calling webView loadUrl, loadDataWithBaseUrl and other methods.
public class WebSessionClientImpl extends SonicSessionClient
{
	private WebView webView;
	
	/ / bind the webview
	public void bindWebView(WebView webView) {
		this.webView = webView;
	}
	
	// Load the page
	@Override
	public void loadUrl(String url, Bundle extraData) {
		webView.loadUrl(url);
	}
	
	// Load the page
	@Override
	public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
		webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
	}
	
	// Load the page
	@Override
	public void loadDataWithBaseUrlAndHeader( String baseUrl , String data , String mimeType , String encoding , String historyUrl , HashMap
       
         headers )
       ,>
	{
		if( headers.isEmpty() )
		{
			webView.loadDataWithBaseURL( baseUrl, data, mimeType, encoding, historyUrl );
		}
		else{ webView.loadUrl( baseUrl,headers ); }}}Copy the code

STEP4:

/ / create the activity
public class WebActivity extends AppCompatActivity
{
	private String url = "http://www.baidu.com";
	private SonicSession sonicSession;
	
	@Override
	protected void onCreate( @Nullable Bundle savedInstanceState )
	{
		super.onCreate( savedInstanceState );
		setContentView( R.layout.activity_web);
		initView();
	}
	
	private void initView(a)
	{
		getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
		
		// Initializations can be placed in the Activity or Application onCreate method
		if( !SonicEngine.isGetInstanceAllowed() )
		{
			SonicEngine.createInstance( new TTPRuntime( getApplication() ),new SonicConfig.Builder().build() );
		}
		// Set preloading
		SonicSessionConfig config = new SonicSessionConfig.Builder().build();
		SonicEngine.getInstance().preCreateSession( url,config );
		
		WebSessionClientImpl client = null;
		//SonicSessionConfig Sets timeout duration, cache size and other parameters.
        // Create a SonicSession object and bind clients to the session. After session creation sonic loads data asynchronously
		sonicSession = SonicEngine.getInstance().createSession( url,config );
		if( null! = sonicSession ) { sonicSession.bindClient( client =new WebSessionClientImpl() );
		}
		/ / get the webview
		WebView webView = (WebView)findViewById( R.id.webview_act );
		webView.setWebViewClient( new WebViewClient()
		{
			@Override
			public void onPageFinished( WebView view , String url )
			{
				super.onPageFinished( view , url );
				if( sonicSession ! =null) { sonicSession.getSessionClient().pageFinish( url ); }}@Nullable
			@Override
			public WebResourceResponse shouldInterceptRequest( WebView view , WebResourceRequest request )
			{
				return shouldInterceptRequest( view, request.getUrl().toString() );
			}
			SonicSession's onClientReady method tells SonicSession that the WebView is ready to start loadUrl when the WebView is ready to initiate loadUrl. In this case, sonic will execute webView logic (loadUrl, loadData, etc.) based on local data.
			@Nullable
			@Override
			public WebResourceResponse shouldInterceptRequest( WebView view , String url )
			{
				if( sonicSession ! =null )
				{
					return (WebResourceResponse)sonicSession.getSessionClient().requestResource( url );
				}
				return null; }});/ / the webview Settings
		WebSettings webSettings = webView.getSettings();
		webSettings.setJavaScriptEnabled(true);
		webView.removeJavascriptInterface("searchBoxJavaBridge_");
		//webView.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic");
		webSettings.setAllowContentAccess(true);
		webSettings.setDatabaseEnabled(true);
		webSettings.setDomStorageEnabled(true);
		webSettings.setAppCacheEnabled(true);
		webSettings.setSavePassword(false);
		webSettings.setSaveFormData(false);
		webSettings.setUseWideViewPort(true);
		webSettings.setLoadWithOverviewMode(true);

		SonicSession's onClientReady method tells SonicSession that the WebView is ready to start loadUrl when the WebView is ready to initiate loadUrl. In this case, sonic will execute webView logic (loadUrl, loadData, etc.) based on the local data.
		if( client ! =null )
		{
			client.bindWebView( webView );
			client.clientReady();
		}
		else{ webView.loadUrl( url ); }}@Override
	public void onBackPressed(a)
	{
		super.onBackPressed();
	}
	
	@Override
	protected void onDestroy(a)
	{
		if( null! = sonicSession ) { sonicSession.destroy(); sonicSession =null;
		}
		super.onDestroy(); }}Copy the code

Simple analysis of its core idea: parallel, make full use of the webView initialization time for some data processing. An activity that contains a WebView starts with the webView initialization logic and executes sonic logic in parallel. This sonic logic is how web pages are preloaded:

  • Quick mode
    1. No cache mode flow:

The webView flow on the left: After the webView is initialized, SonicSession’s onClientReady method is called to inform the WebView that it has been initialized.

client.clientReady();
Copy the code

Sonic flow on the right:

  1. Create SonicEngine objects
  2. Get locally cached URL data via SonicCacheInterceptor
  3. If the data is empty, send a CLIENT_CORE_MSG_PRE_LOAD message to the main thread
  4. Establish a URLConnection with SonicSessionConnection
  5. The connection obtains the data returned by the server, and continuously determines whether the WebView initiates the resource interception request when reading the network data. If yes, it interrupts the reading of network data, concatenates the read and unread data into a bridge SonicSessionStream and assigns the value to SonicSession’s pendingWebResourceStream. If the webView is not initialized after the network reading is completed, The CLIENT_CORE_MSG_PRE_LOAD message is cancelled and the CLIENT_CORE_MSG_FIRST_LOAD message is sent
  6. Then the HTML content template segmentation and data storage
  7. If the webView handles the CLIENT_CORE_MSG_PRE_LOAD message, it calls the loadUrl of the WebView, after which the WebView calls its own resource interceptor method, in which, PendingWebResourceStream is returned to the WebView to parse the render,
  8. If the webView handles the CLIENT_CORE_MSG_FIRST_LOAD message, the webView will call loadDataWithBaseUrl if the loadUrl has not passed, so that the webView can do the parsing rendering directly.

2. Complete cache process with cache mode: The flow of the webView on the left is the same as that of no cache. The flow of the sonic on the right uses the SonicCacheInterceptor to check whether the local data is empty or not. If not, a CLIENT_CORE_MSG_PRE_LOAD message is generated. Then the webview will load the page with loadDataWithBaseUrl and render it

  • The effect
    • For the first time open
    • Secondary open

TBS Tencent Browsing service

website

Integration method, please follow the official website to operate. Here directly put the effect picture after use


Baidu APP Solution

Let’s take a look at baidu APP’s webview processing scheme

  1. Back end Straight Out Back end Straight out – Page static straight out The back end server obtains all the content of the FIRST HTML screen, including the content and style required for the first display. This allows the kernel to render directly when the client retrieves the entire web page and loads it. Here the server should provide an interface to the client to get the full content of the web page. And some of the web pages need to use the client variable use macro replacement, when the client loaded the web page to replace the specific content, has been adapted to different user Settings, such as font size, page color and so on. However, the problem with this scheme is that the network image is not processed, or it takes time to get the image.

2. Intelligent prefetch – Advance Network Request Part of the landing page HTML is obtained from the network in advance and cached locally. When users click to view the HTML, they only need to load it from the cache.

3. General interception-cache sharing and parallel direct request solve the problem of the speed of text presentation, but the speed of image loading and rendering is not ideal. The kernel’s shouldInterceptRequest callback intercepts the image request for the landing page. The client calls the image download framework to download the image and pipe it into the kernel’s WebResourceResponse. That is, shouldInterceptRequest intercepts all urls, and then only the suffix is. PNG/.jpg, using a third-party image download tool like Fresco and returning an InputStream.

Conclusion:

  • Do it ahead of time: This includes pre-creating webViews and pre-fetching data
  • Parallel work: including picture straight out & intercept loading, frame initialization stage open asynchronous thread to prepare data, etc
  • Lightweight: For the front end, minimizing page size and eliminating unnecessary JS and CSS can not only reduce network request times, but also improve kernel parsing times
  • Simplicity: For simple information display pages and scenes with low requirements for content dynamics, hybrid can be replaced by direct output. The display contents can be directly rendered without JS asynchronous loading

Toutiao Program

So what about today’s headlines? 1. CSS/JS and other files of article details page are preset in assets folder, and version control can be carried out. 2. 3. The article details page uses a pre-created WebView, which has been pre-loaded with HTML, and then calls JS to set the page content 3. For image resources, use ContentProvider to get them, and images are downloaded using Fresco

content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65ci nMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY 7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965 / 3Copy the code

Tidy up the idea of these several large factory purpose: web page second open strategy:

  • For the client 1. Pre-create (application onCreate) WebView 1.1 Pre-create while loading HTML text with CSS/JS 2. Webview reuse pool 3. Prefetch web page and cache, prefetch HTML and cache local, need to load from the cache can 5. Resource interception is loaded in parallel, and kernel initialization and resource loading occur simultaneously.
  • For the server 1. Directly out of the web page assembly, the server to get all the content of the web page, the client to get directly loaded 2. Version control of the client’s local HTML resources
  • 1. Delete unnecessary JS/CSS; 2. Cooperate with the client to use VasSonic to update and download only specific content.

My own thoughts:

  1. Page seconds open this demand, if only the client to do, the feeling is only half done, it is best to work together before and after the end to optimize.
  2. But only do the client side of the optimization is also possible, the author of the actual test, through the way of prefetching, can indeed do second open web pages.
  3. With 5G coming this year, it’s possible that web loading won’t be a problem at all.

tip

Fix the blank screen: System with a view to draw, with an attribute setDrawDuringWindowsAnimating, this attribute is used to control the window can do animation in the process of normal mapping, and between the Android to Android 4.2 N, For the flow of component switching, this field is false. We can manually modify this property by reflection

/** * Render the page properly */ private voidsetDrawDuringWindowsAnimating(View view) {
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { // 1 This problem does not exist for Android n or Android 4.1 or lower and does not need to be solvedreturn; } // 4.2 does not existsetDrawDuringWindowsAnimating, need special treatmentif (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            handleDispatchDoneAnimating(view);
            return; } try {// 4.3 and above, reflectionsetDrawDuringWindowsAnimating implemented in the process of animation rendering ViewParent rootParent = the getRootView (). The getParent (); Method method = rootParent.getClass() .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
            method.setAccessible(true);
            method.invoke(rootParent, true); } catch (Exception e) { e.printStackTrace(); }} / android4.2 can reflect handleDispatchDoneAnimating to solve * * * * / private void handleDispatchDoneAnimating (View paramView) {a try  { ViewParentlocalViewParent = paramView.getRootView().getParent();
            Class localClass = localViewParent.getClass();
            Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
            localMethod.setAccessible(true);
            localMethod.invoke(localViewParent);
        } catch (Exception localException) {
            localException.printStackTrace(); }}Copy the code

VasSonic preloads part of the source code analysis

Having explained the main idea of Sonic and the main cache logic, let’s take a look at how it works with the source code.

SonicSessionConfig.Builder sessionConfigBuilder = new SonicSessionConfig.Builder();
sessionConfigBuilder.setSupportLocalServer(true);

// Preload
boolean preloadSuccess = SonicEngine.getInstance().preCreateSession(DEMO_URL, sessionConfigBuilder.build());
Copy the code

Enter the preCreateSession method and take a look

public synchronized boolean preCreateSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
	// Whether the database is ready
	if (isSonicAvailable()) {
        // Generate a unique sessionId based on the URL and the account set in RunTime
        String sessionId = makeSessionId(url, sessionConfig.IS_ACCOUNT_RELATED);
        if(! TextUtils.isEmpty(sessionId)) { SonicSession sonicSession = lookupSession(sessionConfig, sessionId,false);
            if (null! = sonicSession) { runtime.log(TAG, Log.ERROR,"PreCreateSession: sessionId (" + sessionId + ") is already in preload pool.");
                    return false;
                }
       // Check whether the preload pool is full
       if (preloadSessionPool.size() < config.MAX_PRELOAD_SESSION_COUNT) {
          // Network judgment
          if (isSessionAvailable(sessionId) && runtime.isNetworkValid()) {
              // Create sonicSession to preload
              sonicSession = internalCreateSession(sessionId, url, sessionConfig);
              if (null! = sonicSession) {// Put it in the pool
                  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 + "."); }}}else {
            runtime.log(TAG, Log.ERROR, "preCreateSession fail for sonic service is unavailable!");
        }
        return false;
    }
Copy the code

Analysis: This method only does the sonic session creation work. However, it is created only when the size of the preloadSessionPool is smaller than MAX_PRELOAD_SESSION_COUNT. Let’s move on to the next method internalCreateSession and see how it’s created, okay

private SonicSession internalCreateSession(String sessionId, String url, SonicSessionConfig sessionConfig) {
        // The preloaded Session ID is not in the map of the running Session
        if(! runningSessionHashMap.containsKey(sessionId)) { SonicSession sonicSession;// Set the cache type
            if (sessionConfig.sessionMode == SonicConstants.SESSION_MODE_QUICK) {
                // Fast type
                sonicSession = new QuickSonicSession(sessionId, url, sessionConfig);
            } else {
                // Standard type
                sonicSession = new StandardSonicSession(sessionId, url, sessionConfig);
            }
            // Session state change monitor
            sonicSession.addSessionStateChangedCallback(sessionCallback);
            
            // Starts the session with true by default
            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;
    }
Copy the code

This method creates a different cache session type based on the sessionMode type in sessionConfig. QuickSonicSession and StandardSonicSession types. Finally, start the session for preloading. Let’s continue with sonicsession.start ().

 public void start(a) {...for (WeakReference<SonicSessionCallback> ref : sessionCallbackList) {
            SonicSessionCallback callback = ref.get();
            if(callback ! =null) {
                // Callback the startup statecallback.onSonicSessionStart(); }}...// Run the preload page method in the session thread
        SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() {
            @Override
            public void run(a) {
                runSonicFlow(true); }}); . }Copy the code

The main one is runSonicFlow(True), which performs network request operations in sonic’s dedicated thread pool.

private void runSonicFlow(boolean firstRequest) { ... // First requestif(firstRequest) {/ / get the HTML cache is empty cacheHtml = SonicCacheInterceptor. For the first time getSonicCacheData (this); statistics.cacheVerifyTime = System.currentTimeMillis(); SonicUtils.log(TAG, Log.INFO,"session(" + sId + ") runSonicFlow verify cache cost " + (statistics.cacheVerifyTime - statistics.sonicFlowStartTime) + " ms"); // Send messages CLIENT_CORE_MSG_PRE_LOAD arg1:PRE_LOAD_NO_CACHE handleFlow_LoadLocalCache(cacheHtml); } boolean hasHtmlCache = ! TextUtils.isEmpty(cacheHtml) || ! firstRequest; final SonicRuntime runtime = SonicEngine.getInstance().getRuntime();if(! Runtime.isnetworkvalid ()) {// The network does not existif(hasHtmlCache && ! TextUtils.isEmpty(config.USE_SONIC_CACHE_IN_BAD_NETWORK_TOAST)) { runtime.postTaskToMainThread(newRunnable() {
                    @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{// Start request handleFlow_Connection(hasHtmlCache, sessionData); statistics.connectionFlowFinishTime = System.currentTimeMillis(); }... }Copy the code

Analysis: At the time of the first request, Calling handleFlow_LoadLocalCache actually calls the QuickSonicSession or StandardSonicSession handleFlow_LoadLocalCache that was created earlier to send a message CLIEN T_CORE_MSG_PRE_LOAD and whether it contains cache. Then the handleFlow_Connection(hasHtmlCache, sessionData) method is called for the network presence. Next, go to the handleFlow_Connection method to see how the connection is established.

protected void handleFlow_Connection(boolean hasCache, SonicDataHelper.SessionData sessionData) {...// Create a network request
        server = new SonicServer(this, createConnectionIntent(sessionData)); . }Copy the code

It’s a long method so let’s look at it in sections, first of all this creates a SonicServer object, which creates a URLConnection through SonicSessionConnection

 public SonicServer(SonicSession session, Intent requestIntent) {
        this.session = session;
        this.requestIntent = requestIntent;
        connectionImpl = SonicSessionConnectionInterceptor.getSonicSessionConnection(session, requestIntent);
    }
Copy the code
 public static SonicSessionConnection getSonicSessionConnection(SonicSession session, Intent intent) {
        SonicSessionConnectionInterceptor interceptor = session.config.connectionInterceptor;
        // Whether there is an intercept
        if(interceptor ! =null) {
            return interceptor.getConnection(session, intent);
        }
        return new SonicSessionConnection.SessionConnectionDefaultImpl(session, intent);
    }
Copy the code
public SessionConnectionDefaultImpl(SonicSession session, Intent intent) {
            super(session, intent);
            / / create a URLConnection
            connectionImpl = createConnection();
            initConnection(connectionImpl);
        }
Copy the code

Back to handleFlow_Connection, now that you have created the URLConnection, you can now connect to request data.

int responseCode = server.connect();
Copy the code
 protected int connect(a) {
        long startTime = System.currentTimeMillis();
		// Whether the connection is normal return code
        intresultCode = connectionImpl.connect(); .if(SonicConstants.ERROR_CODE_SUCCESS ! = resultCode) {return resultCode; // error case
        }

        startTime = System.currentTimeMillis();
        // Connection request return coderesponseCode = connectionImpl.getResponseCode(); .// When eTag is empty
        if (TextUtils.isEmpty(eTag)) {
            readServerResponse(null);
            if(! TextUtils.isEmpty(serverRsp)) { eTag = SonicUtils.getSHA1(serverRsp); addResponseHeaderFields(getCustomHeadFieldEtagKey(), eTag); addResponseHeaderFields(CUSTOM_HEAD_FILED_HTML_SHA1, eTag); }else {
                return SonicConstants.ERROR_CODE_CONNECT_IOE;
            }

            if (requestETag.equals(eTag)) { // 304 case
                responseCode = HttpURLConnection.HTTP_NOT_MODIFIED;
                returnSonicConstants.ERROR_CODE_SUCCESS; }}// When templateTag is empty
        String templateTag = getResponseHeaderField(CUSTOM_HEAD_FILED_TEMPLATE_TAG);
        if (TextUtils.isEmpty(templateTag)) {
            if (TextUtils.isEmpty(serverRsp)) {
                readServerResponse(null);
            }
            if(! TextUtils.isEmpty(serverRsp)) { separateTemplateAndData(); templateTag = getResponseHeaderField(CUSTOM_HEAD_FILED_TEMPLATE_TAG); }else {
                returnSonicConstants.ERROR_CODE_CONNECT_IOE; }}//check If it changes template or update data.
        String requestTemplateTag = requestIntent.getStringExtra(SonicSessionConnection.CUSTOM_HEAD_FILED_TEMPLATE_TAG);
        if (requestTemplateTag.equals(templateTag)) {
            addResponseHeaderFields(SonicSessionConnection.CUSTOM_HEAD_FILED_TEMPLATE_CHANGE, "false");
        } else {
            addResponseHeaderFields(SonicSessionConnection.CUSTOM_HEAD_FILED_TEMPLATE_CHANGE, "true");
        }

        return SonicConstants.ERROR_CODE_SUCCESS;
    }
Copy the code

Take a look at the readServerResponse method, which takes the returned data stream and concatenates it into a string.

 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;
    }
Copy the code

Let’s go back to the handleFlow_Connection method

// When cacheHtml is empty, run First-Load flow
        if(! hasCache) { handleFlow_FirstLoad();return;
        }
Copy the code

The end of sonic processing

protected void handleFlow_FirstLoad(a) {
        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);


        booleanhasCompletionData = ! 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<SonicSessionCallback> 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."); }}Copy the code

Create ResponseStream to return when the webView loads the resource, remove the CLIENT_CORE_MSG_PRE_LOAD message, send the CLIENT_CORE_MSG_FIRST_LOAD message, and save the data so that When the webView starts loading the URL, shouldInterceptRequest returns pendingWebResourceStream to enable fast loading.

Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                if(sonicSession ! =null) {
                    // Return the preload data stream
                    return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);
                }
                return null;
            }
Copy the code