Exploration on the second opening strategy of Android WebView web page

Time:2022-1-15
What are the pain points?

Web pages load slowly, white screen, use Caton.

Why is there such a problem?

1. The web page loading process will start only when the loadurl () method is called. 2 JS bloated problem 3 Too many pictures loaded 4 WebView itself

How does webiew load web pages?

WebView initialization – > DOM download → DOM parsing → CSS request + download → CSS parsing → rendering → drawing → composition

What is the optimization direction?

1. WebView itself optimization

  • Early Kernel initialization code:
public class App extends Application {

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

Effect: see the figure below

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

    private WebPools() {
        mWebViews = new LinkedBlockingQueue<>();
    }
    public static WebPools getInstance() {
        for (; ; ) {
            if (mWebPools != null)
                return mWebPools;
            if (mAtomicReference.compareAndSet(null, new WebPools()))
                return mWebPools=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(new MutableContextWrapper(activity));
            }
        } else {
            MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
            mMutableContextWrapper.setBaseContext(activity);
            return mWebView;
        }
    }
    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();
        }
    }
}

Problems caused by memory leakage: the effect of using pre created and reused pools

  • Independent process, process preload Code:

Before starting the WebView page, start prewebservice to create the [web] process. When starting webactivity, the system finds that the [web] process already exists, so it doesn’t need to spend time forking out a new [web] process.

  • Use X5 kernel and directly use Tencent’s X5 kernel to replace the native browser kernel
  • effect:

    • First open

    • Secondary opening

  • Other solutions: 1 Set WebView cache 2 Load animation / finally let the picture download 3 Turn off picture loading when rendering 4 Set timeout 5 Enable hardware and software acceleration

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

3. Optimization of web page: the front-end engineer of the web page optimizes the web page, or works with the mobile terminal to realize incremental and dynamic update of the web page. The app has built-in CSS, JS files and version control

Note: if you hope to speed up the loading speed of web pages only through WebView setting, you will be disappointed. Only modify the settings, can do very little improvement. Therefore, this paper focuses on analyzing and comparing the advantages and disadvantages of the third-party WebView framework that can be used now.


VasSonic

//Import Tencent / vassonic
    implementation 'com.tencent.sonic:sdk:3.1.0'

STEP2:

//Create a class that inherits sonicruntime
//The sonicruntime class mainly provides sonic runtime environment, including context, user UA, ID (user's unique ID, corresponding user's unique ID when storing data), 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 )
    {
        //Log settings
    }
    
    //Get cookie
    @Override
    public String getCookie( String url )
    {
        return null;
    }
    
    //Set cookie ID
    @Override
    public boolean setCookie(
            String url ,
            List cookies )
    {
        return false;
    }
    
    //Obtain user UA information
    @Override
    public String getUserAgent()
    {
        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()
    {
        return "ttpp";
    }
    
    //Is sonic acceleration used
    @Override
    public boolean isSonicUrl( String url )
    {
        return true;
    }
    
    //Create web resource request
    @Override
    public Object createWebResourceResponse(
            String mimeType ,
            String encoding ,
            InputStream data ,
            Map headers )
    {
        return null;
    }
    
    //Whether the network is allowed
    @Override
    public boolean isNetworkValid()
    {
        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 sonic cache address
    @Override
    public File getSonicCacheDir()
    {
        return super.getSonicCacheDir();
    }
}

STEP3:

//Create a class that inherits sonicsessionclien
//Sonicsessionclient is mainly responsible for communicating with WebView, such as calling methods such as loadurl and loaddatawithbaseurl of WebView.
public class WebSessionClientImpl extends SonicSessionClient
{
    private WebView webView;
    
    //Bind WebView
    public void bindWebView(WebView webView) {
        this.webView = webView;
    }
    
    //Load web page
    @Override
    public void loadUrl(String url, Bundle extraData) {
        webView.loadUrl(url);
    }
    
    //Load web page
    @Override
    public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding,
            String historyUrl) {
        webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
    }
    
    //Load web 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 );
        }
    }
}

STEP4:

//Create 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()
    {
        getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        
        //Initialization can be placed in the oncreate method of activity or application
        if( !SonicEngine.isGetInstanceAllowed() )
        {
            SonicEngine.createInstance( new TTPRuntime( getApplication() ),new SonicConfig.Builder().build() );
        }
        //Set preload
        SonicSessionConfig config = new SonicSessionConfig.Builder().build();
        SonicEngine.getInstance().preCreateSession( url,config );
        
        WebSessionClientImpl client = null;
        //Sonicsessionconfig sets timeout, cache size and other related parameters.
        //Create a sonicsession object and bind the client for the session. After the session is created, sonic will load data asynchronously
        sonicSession = SonicEngine.getInstance().createSession( url,config );
        if( null!= sonicSession )
        {
            sonicSession.bindClient( client = new WebSessionClientImpl() );
        }
        //Get 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() );
            }
            //Bind WebView for clinet. When WebView is ready to initiate loadurl, notify sonicsession through onclientredy method of sonicession that WebView ready can start loadurl. At this time, sonic will execute the corresponding logic of WebView (loadurl or loaddata, etc.) according to the local data conditions
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(
                    WebView view ,
                    String url )
            {
                if( sonicSession != null )
                {
                    return (WebResourceResponse)sonicSession.getSessionClient().requestResource( url );
                }
                return null;
            }
        });
        //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);

        //Bind WebView for clinet. When WebView is ready to initiate loadurl, notify sonicsession through onclientredy method of sonicession that WebView ready can start loadurl. At this time, sonic will execute the corresponding logic of WebView (loadurl or loaddata, etc.) according to the local data conditions。
        if( client != null )
        {
            client.bindWebView( webView );
            client.clientReady();
        }
        else
        {
            webView.loadUrl( url );
        }
    }
    
    @Override
    public void onBackPressed()
    {
        super.onBackPressed();
    }
    
    @Override
    protected void onDestroy()
    {
        if( null != sonicSession )
        {
            sonicSession.destroy();
            sonicSession = null;
        }
        super.onDestroy();
    }
}

Simply analyze its core idea: parallelism, and make full use of the initialization time of WebView to process some data. When the activity containing WebView is started, the initialization logic of WebView and the logic of sonic will be executed in parallel. This sonic logic is the preloading principle of web pages:

  • Quick mode classification:

    1. No cache mode process:

      sonicQuickModeFirst.png

The WebView process on the left: after WebView initialization, the onClientReady method of SonicSession is called to notify WebView that it has been initialized.

client.clientReady();

Sonic process on the right:

  1. Creating a sonicengine object
  2. Get locally cached URL data through soniccacheinterceptor
  3. Send a client when the data is empty\_ CORE\_ MSG\_ PRE\_ Load message to main thread
  4. Establish a urlconnection through sonicsessionconnection
  5. Connect to get the data returned by the server, and constantly judge whether the WebView initiates a resource interception request when reading the network data. If it is sent, the reading of network data will be interrupted. The read and unread data will be spliced into a bridge stream sonicessionstream and assigned to the pending webresourcestream of sonicession. If the WebView has not been initialized after the network reading, the client will be cancelled\_ CORE\_ MSG\_ PRE\_ Load message and send client at the same time\_ CORE\_ MSG\_ FIRST\_ Load message
  6. After that, the HTML content is divided into templates and the data is saved
  7. If WebView handles client\_ CORE\_ MSG\_ PRE\_ Load this message, it will call the loadurl of WebView, and then WebView will call its own resource interception method. In this method, it will return the previously saved pendingwebresourcestream to WebView for parsing and rendering,
  8. If WebView is dealing with client\_ CORE\_ MSG\_ FIRST\_ In the load message, if the WebView does not have a loadurl, it will call the loaddatawithbaseurl method to load the network data read before, so that the WebView can directly parse and render.

2. Full cache process with cache mode: the process of WebView on the left is consistent with that of no cache. The process of sonic on the right will obtain whether the local data is empty through soniccacheinterceptor. If it is not empty, a client will occur\_ CORE\_ MSG\_ PRE\_ After the load message, the WebView will use loaddatawithbaseurl to load the web page for rendering

  • effect

    • First open

    • Secondary opening


TBS Tencent browsing service

For the integration method, please follow the on the official website. Put the effect picture directly here


Baidu app solution

Let’s take a look at the WebView processing scheme of Baidu app

  1. Back end direct out back end direct out – the page is statically direct out, and the back-end server obtains all the first screen content of HTML, including the content and style required for the first screen display. In this way, when the client obtains the whole web page and loads it, the kernel can render directly. Here, the server should provide an interface to the client to get all the contents of the web page. Moreover, some of the obtained web pages need to use the macro replacement of the client’s variables. When the client loads the web page, it is replaced with specific content, which has been adapted to the settings of different users, such as font size, page color, etc. However, there are still some problems with this scheme, that is, the network pictures are not processed, and it still takes time to obtain the pictures.

2. Intelligent prefetching – advance network requests to obtain some landing page HTML from the network in advance and cache it locally. When users click to view, they only need to load it from the cache.

3. General interception – cache sharing and request parallel direct output solve the problem of text display speed, but the picture loading and rendering speed is not ideal. The shouldinterceptrequest callback of the kernel intercepts the landing page image request, the client calls the image download framework to download, and fills it into the webresourceresponse of the kernel in a pipeline manner. That is, all URLs are intercepted in shouldinterceptrequest, and then only the suffix is PNG/. For image resources such as JPG, use a third-party image download tool similar to fresco to download and return an InputStream.

Summary:

  • Do it in advance: including pre creating WebView and pre fetching data
  • Parallel work: including image direct output & blocking loading, starting asynchronous threads in the framework initialization stage to prepare data, etc
  • Lightweight: for the front end, try to reduce the page size and delete unnecessary JS and CSS, which can not only shorten the network request time, but also improve the kernel parsing time
  • Simplification: for simple information display pages and scenes that do not require high content dynamics, you can consider using direct output instead of hybrid. The display content can be rendered directly without JS asynchronous loading
    • *

Today’s headline program

What about today’s headlines? 1. CSS / JS and other files of the article details page are preset in the assets folder, and version control can be carried out 2 While the WebView is pre created, an HTML spliced with java code is pre loaded to parse JS / CSS resources in advance. 3. The article details page uses the pre created WebView, which has pre loaded HTML, and then calls JS to set the page content 3 For image resources, use ContentProvider to obtain, while images are downloaded using fresco

content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65cinMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965/3

Sort out the ideas of these big factories. Purpose: Web page second opening strategy:

  • For client 1 Pre create (during application oncreate) WebView 1.1 pre create and load HTML text with CSS / JS 2 WebView reuse pool 3 WebView setting 4 Prefetch the web page and cache it. Get HTML in advance and cache it locally. You need to load it from the cache. 5 Resource interception and parallel loading, kernel initialization and resource loading are carried out at the same time.
  • For server 1 Straight out of the web page assembly, the server obtains all the contents of the web page, and the client directly loads 2 Version control of client local HTML resources
  • For web front end 1 Delete unnecessary JS / CSS 2 Use vassonic with the client to update and download only specific content.
    • *

Own ideas:

  1. If you only need the client to open the web page in seconds, you feel that you have only done half of it. You’d better work together at the front and back ends to optimize it.
  2. However, it is also possible to only optimize the client. The author actually tested that through prefetching, it can indeed open the web page in seconds.
  3. This year, it will be on 5g. It is possible that under 5g network, web page loading is not a problem at all.
    • *
antic

Fix the white screen phenomenon: when the system processes view drawing, there is an attribute setdrawduringwindowsanimating, which is used to control whether the window can be drawn normally during animation. Just between Android 4.2 and Android n, the system considers the process of component switching, and this field is false, We can use reflection to modify this property manually

/**
     *Enables the page to be rendered normally during the activity transition animation
     */
    private void setDrawDuringWindowsAnimating(View view) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
                || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //1 Android n above & Android 4.1 below does not have this problem and does not need to be handled
            return;
        }
        //4.2 setdrawduringwindowsanimating does not exist and needs special processing
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            handleDispatchDoneAnimating(view);
            return;
        }
        try {
            //4.3 and above, reflect setdrawduringwindowsanimating to realize rendering during animation
            ViewParent rootParent = view.getRootView().getParent();
            Method method = rootParent.getClass()
                    .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
            method.setAccessible(true);
            method.invoke(rootParent, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * android4. 2. It can be solved by reflecting handledispatchdoneanimating
     */
    private void handleDispatchDoneAnimating(View paramView) {
        try {
            ViewParent localViewParent = paramView.getRootView().getParent();
            Class localClass = localViewParent.getClass();
            Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
            localMethod.setAccessible(true);
            localMethod.invoke(localViewParent);
        } catch (Exception localException) {
            localException.printStackTrace();
        }
    }

Vassonic preload part source code analysis

The main idea of sonic and the main cache logic flow have been explained earlier. Let’s take a look at how it operates the preload function in combination with the source code.

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

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

get intopreCreateSessionMethod look

public synchronized boolean preCreateSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
    //Is the database ready
    if (isSonicAvailable()) {
        //Generate a unique sessionid according to 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;
                }
       //Judge whether the preload pool is full
       if (preloadSessionPool.size() < config.MAX_PRELOAD_SESSION_COUNT) {
          //Network judgment
          if (isSessionAvailable(sessionId) && runtime.isNetworkValid()) {
              //Create a sonic session 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;
    }

Analysis: this method only needs to create sonic session. But only the preload pool is satisfied(preloadSessionPool)The size of is less thanMAX_PRELOAD_SESSION_COUNTWill be created when. Let’s move on to the next methodinternalCreateSessionGo and see how it was created

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

This method creates different cache types according to the sessionmode type in sessionconfig.  QuickSonicSessionas well asStandardSonicSessionType. Finally, start the session for preloading. We fromsonicSession.start()Keep looking.

public void start() {
       ...

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

The main method isrunSonicFlow(true)This method performs network request operations in sonic’s dedicated thread pool.

private void runSonicFlow(boolean firstRequest) {
        ...

        //First request
        if (firstRequest) {
            //Get HTML cache empty for the first time
            cacheHtml = SonicCacheInterceptor.getSonicCacheData(this);
            statistics.cacheVerifyTime = System.currentTimeMillis();
            SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") runSonicFlow verify cache cost " + (statistics.cacheVerifyTime - statistics.sonicFlowStartTime) + " ms");
            //Send message 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()) {
            //Network does not exist
            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 {
            //Start request
            handleFlow_Connection(hasHtmlCache, sessionData);
            statistics.connectionFlowFinishTime = System.currentTimeMillis();
        }

        ...
    }

Analysis: on the first request, callhandleFlow_LoadLocalCacheMethod is actually created before the callQuickSonicSessionperhapsStandardSonicSessionofhandleFlow_LoadLocalCacheThe main function is to send a message client\_ CORE\_ MSG\_ PRE\_ Load and whether it contains cache. Then called when the network existshandleFlow_Connection(hasHtmlCache, sessionData)Method to perform the requested work. Next enterhandleFlow_ConnectionMethod to see how the connection is established.

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

The method is very long. Let’s look at it part by part. First, create a sonicerver object throughSonicSessionConnectionCreateURLConnection

public SonicServer(SonicSession session, Intent requestIntent) {
        this.session = session;
        this.requestIntent = requestIntent;
        connectionImpl = SonicSessionConnectionInterceptor.getSonicSessionConnection(session, requestIntent);
    }
public static SonicSessionConnection getSonicSessionConnection(SonicSession session, Intent intent) {
        SonicSessionConnectionInterceptor interceptor = session.config.connectionInterceptor;
        //Is there any interception
        if (interceptor != null) {
            return interceptor.getConnection(session, intent);
        }
        return new SonicSessionConnection.SessionConnectionDefaultImpl(session, intent);
    }
public SessionConnectionDefaultImpl(SonicSession session, Intent intent) {
            super(session, intent);
            //Create urlconnection
            connectionImpl = createConnection();
            initConnection(connectionImpl);
        }

Then backhandleFlow_Connection, now that it’s createdURLConnectionThen you can connect to request data.

int responseCode = server.connect();
protected int connect() {
        long startTime = System.currentTimeMillis();
        //Whether the connection is normal return code
        int resultCode = connectionImpl.connect();
        ...

        if (SonicConstants.ERROR_CODE_SUCCESS != resultCode) {
            return resultCode; // error case
        }

        startTime = System.currentTimeMillis();
        //Connection request return code
        responseCode = 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;
                return SonicConstants.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 {
                return SonicConstants.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;
    }

Mainly look atreadServerResponseIn this method, all it does is get the return data stream and splice 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;
    }

Let’s go back tohandleFlow_Connectionmethod

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

The end of sonic processing

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.");
        }
    }

Create a responsestream to return when the WebView loads resources and remove the client\_ CORE\_ MSG\_ PRE\_ Load message, send client\_ CORE\_ MSG\_ FIRST\_ Load the message and save the data, so that all the data of the web page can be obtained locally. When WebView starts loading the URL, clickshouldInterceptRequestThe saved pendingwebresourcestream can be loaded quickly.

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

Related tutorials

Android Foundation Series tutorials:

Android foundation course u-summary_ Beep beep beep_ bilibili

Android foundation course UI layout_ Beep beep beep_ bilibili

Android basic course UI control_ Beep beep beep_ bilibili

Android foundation course UI animation_ Beep beep beep_ bilibili

Android basic course – use of activity_ Beep beep beep_ bilibili

Android basic course – fragment usage_ Beep beep beep_ bilibili

Android basic course – Principles of hot repair / hot update technology_ Beep beep beep_ bilibili

This article is transferred fromhttps://juejin.cn/post/6844903887111979021, in case of infringement, please contact to delete.