Exploration on the second opening strategy of Android Webview web page

Posted by Darkness31 on Fri, 31 Dec 2021 22:36:06 +0100

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<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() {
        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:
        <service
            android:name=".PreWebService"
            android:process=":web"/>
        <activity
            android:name=".WebActivity"
            android:process=":web"/>

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<String> 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<String, String> 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<String, String> 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:

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 cancel led\_ 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 URL s 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 at the same time 2.webview reuse pool 3. Settings of WebView setting 4. Prefetch web pages and cache, obtain html in advance and cache 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());

Enter the preCreateSession method to see

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. However, only the size of the preloadsessionpool that meets the requirements is less than max_ PRELOAD_ SESSION_ It will be created only when count. Let's move on to the next method internalCreateSession to see how it is 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. Quicksonicession and standardsonicession types. Finally, start the session for preloading. We started from sonic session Start () continues.

 public void start() {
       ...

        for (WeakReference<SonicSessionCallback> 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 is runSonicFlow(true). This method performs network request operations in sonic's special 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: handleflow is called on the first request_ The loadlocalcache method actually calls the handleflow of the quicksonicession or standardsonicession created earlier_ Loadlocalcache is mainly used to send a message CLIENT\_CORE\_MSG\_PRE\_LOAD and whether it contains cache. Then call handleflow when the network exists_ The connection (hashtmlcache, sessiondata) method performs the requested work. Next, enter handleflow_ Use the connection method to see how to establish a connection.

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, where URLConnection is created through sonicessionconnection

 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 return to handleFlow_Connection. Now that the URLConnection is created, 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 at the readServerResponse method. What it does is to obtain 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 to handleflow again_ Connection method

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

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. In this way, all the data of the web page can be obtained locally. Just wait for webview to start loading the url, and return the saved pendingWebResourceStream when shouldInterceptRequest to realize rapid loading.

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 from https://juejin.cn/post/6844903887111979021 , in case of infringement, please contact to delete.

Topics: Android