This article was first published on the wechat official account “Yu Gang said”

Android Webview H5 seconds open solution implementation

preface

Many apps now have embedded H5 pages, but slow WebView loading has been a problem for users, so this article discusses how to improve the loading speed of H5 pages.

Question why

First we need to know why webViews load so slowly. The rendering speed of H5 pages depends on two things

  1. If there are many JS files and parsing is complicated, rendering will be slow. Or if the hardware performance of the phone is poor, rendering will be slow.
  2. The download of page resources generally load a H5 page, will produce more network requests, such as pictures, JS files, CSS files, etc., need to download these resources to complete the rendering, which will also lead to slow page rendering speed

As for the first point above, it is actually mainly determined by the front-end code and mobile phone hardware. Since we are discussing the performance optimization of app here, we will not consider it for the moment, so we can make an article from the second point. The main idea is that some resource files use the local resources of APP, rather than downloading from the network. This improves the speed of page opening.

Code implementation

Take the example of loading the renyugang. IO /post/75 page.

The WebViewClient shouldInterceptRequest(WebView View, String URL) and shouldInterceptRequest(WebView View, WebResourceRequest Request) to intercept access to the url. When the URL matches the locally configured URL, Use local resources instead, otherwise use resources on the network.

YuGangShuoWebActivity:

mWebview.setWebViewClient(new WebViewClient() {

    // The Settings are displayed in the current Webview without the system browser

    @Override

    public boolean shouldOverrideUrlLoading(WebView view, String url) {

      view.loadUrl(url);

      return true;

    }



    @Override

    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

      // If the match matches a local resource, use the local resource instead

      if (mDataHelper.hasLocalResource(url)) {

          WebResourceResponse response =

                  mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),

                          url);

          if(response ! =null) {

              return response;

          }

      }

      return super.shouldInterceptRequest(view, url);

    }



    @TargetApi(VERSION_CODES.LOLLIPOP)

    @Override

    public WebResourceResponse shouldInterceptRequest(WebView view,

          WebResourceRequest request) {

      String url = request.getUrl().toString();

      if (mDataHelper.hasLocalResource(url)) {

          WebResourceResponse response =

                  mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),

                          url);

          if(response ! =null) {

              return response;

          }

      }

      return super.shouldInterceptRequest(view, request);

    }



}); 

Copy the code

DataHelper is a utility class with the following code:

public class DataHelper {



    private Map<String, String> mMap;



    public DataHelper({

        mMap = new HashMap<>();

        initData();

    }



    private void initData({

        String imageDir = "images/";

        String pngSuffix = ".png";

        mMap.put("http://renyugang.io/wp-content/themes/twentyseventeen/style.css? Ver = 4.9.8".

                "css/style.css");

        mMap.put("http://renyugang.io/wp-content/uploads/2018/06/cropped-ryg.png".

                imageDir + "cropped-ryg.png");

.

    }



    public boolean hasLocalResource(String url{

        return mMap.containsKey(url);

    }



    public WebResourceResponse getReplacedWebResourceResponse(Context context, String url{

        String localResourcePath = mMap.get(url);

        if (TextUtils.isEmpty(localResourcePath)) {

            return null;

        }

        InputStream is = null;

        try {

            is = context.getApplicationContext().getAssets().open(localResourcePath);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

        String mimeType;

        if (url.contains("css")) {

            mimeType = "text/css";

        } else if (url.contains("jpg")) {

            mimeType = "image/jpeg";

        } else {

            mimeType = "image/png";

        }

        WebResourceResponse response = new WebResourceResponse(mimeType, "utf-8".is);

        return response;

    }





}

Copy the code

Let’s take a look at the network requests before and after the modification.

Before optimization, there are n actual network requests made:

After optimization, only one network request is actually made. In order to distinguish the images from the network resources, I added “local” watermark to the two local images, and it is obvious that the local images are loaded at this time:



In addition, the WebViewClient shouldInterceptRequest(WebView View, String URL) and shouldInterceptRequest(WebView View, WebResourceRequest Request); if you override either of these methods, the following shouldInterceptRequest(WebView View, WebResourceRequest Request (WebResourceRequest Request) is usually used by systems larger than 5.0. My personal suggestion is to rewrite both methods.

About caching webViews

Let’s look at another interesting phenomenon, when we do not configure local resources, the first time we open the page, there are n requests. But when we exit and open the page again (not set to load local resources), we only get one request, which is very similar to loading local resources.



Why is that?

We uninstalled the app, captured the packet, and opened the page again, with the example of the banner image request.



We look at the response headers parameters for this request and notice the following fields:

Last-Modified,ETag,Expires,Cache-Control.

  • Cache-control For example, if cache-control :max-age=2592000, the Cache duration is 2592000 seconds, that is, 30 days in a month. If the file needs to be requested again within 30 days, the browser will not make the request and will use the locally cached file. This is a field in the HTTP/1.1 standard.

  • Expires for example Expires:Tue,25 Sep 2018 07:17:34 GMT. This means that the expiration time of the file is 7:17 GMT on September 25, 2018. Since I made the request at 15:00 on August 26, 2018 (Beijing time), it can be seen that it is valid for about one month. The browser will not make another request for the file until then. Expires is a field in HTTP/1.0 that can cause caching problems if the client and server time are out of sync, hence cache-control. When they are present together, cache-control has a higher priority.

  • Last-modified indicates the Last time the file was updated on the server. On the next request, If the file cache has expired, the browser sends it to the server with this time in the if-Modified-Since field, and the server compares the timestamp to determine whether the file has been Modified. If no changes are made, the server returns 304(unmodified) to tell the browser to continue using the cache; If any changes were made, 200 is returned, along with the most recent file.

  • Etag The value of Etag is a characteristic string that identifies a file. When the server queries whether a file is updated, the browser sends the characteristic string to the server by using the if-none-match field. The server matches the latest characteristic string of the file to determine whether the file is updated. Packet 304 was not updated, but packet 200 was. Etag and last-Modified can be used in combination with one or both, depending on requirements. When both are used together, the file is considered not updated as long as one of the criteria in the base is met.

Common usage is cache-control with last-Modified, and Expires with Etag.

But that may not be the case.

Now, five minutes later, we open the page again and observe the request.



In this request, we do not see the if-none-match field in the request, indicating that the Etag field is not used. But If you have the if-modified-since field in the request, which means that the cached file was last Modified in 1984, which means that the file that was requested from the server was last Modified in 1984, In response, we see that the last-modified field is still the same time, indicating that the file on the server has not been Modified, so 304 is returned. In response, the cache-control field is 300 seconds, indicating that the file will expire in 5 minutes. Expires does come up here, but as we mentioned above, cache-control takes precedence when both cache-control and Expires are present.

So, for the most part, we’re actually looking at the cache-Control and last-Modified fields.

All right, anyway, now we know why this phenomenon is happening, because of the caching of the WebView.

So how do you get webViews to support these caching protocols? The answer is not to configure it (use the default CacheMode), or set it manually

WebSettings webSettings = webView.getSettings();

webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);

Copy the code

The following is an explanation of the cache pattern in 5:

  • LOAD_CACHE_ONLY: Reads only the local cache data without using the network.
  • LOAD_DEFAULT: cache-control determines whether to fetch data from the network.
  • LOAD_CACHE_NORMAL: deprecated at API level 17 and used as LOAD_DEFAULT from API level 11
  • LOAD_NO_CACHE: Does not use cache and obtains data only from the network.
  • LOAD_CACHE_ELSE_NETWORK, as long as it is locally available, regardless of whether it is expired or no-cache, uses the data in the cache. Obtain it from the network only when there is no local cache.

So we usually set the default caching mode. With regard to the configuration of the cache, the main depends on the Web front end and back end Settings.

In addition to WebView’s built-in Cache, there are Application Cache, Dom Storage, Web SQL Database, and IndexedDB caches. The remaining caches, however, are not recommended by AppCache and are no longer supported by the standard, according to the official documentation. The others are not file caches, which is not what we’re going to talk about today, so I won’t cover them. H5 Cache mechanism for Mobile Web loading performance optimization and Android: A hand in hand guide to building a comprehensive WebView cache mechanism & resource loading solution

Other solutions to speed up WebView

Initialization of the WebView

Local Webview initialization takes quite a bit of time, and the first time you initialize a Webview is much slower than the second time. The reason is that after the first initialization of the WebView, even though the WebView has been released, some global services or resource objects shared by the WebView are still not released. In the second initialization, these objects do not need to be regenerated to make it faster. We can pre-initialize the WebView in the Application, and it will be much faster to initialize the WebView the second time, or we can use it directly.

Preloaded data

Preloading data means that when the client initializes the WebView, the network requests data directly from native. When the page initialization is complete, the data requested by its agent is obtained from native. Data request and WebView initialization can be carried out in parallel, thus shortening the overall page loading time. This preload list should contain the pages and resources of the required H5 module. The client can take over the cache of all requests. Do not use the webView default caching logic, to implement the caching mechanism, the principle is to intercept the WebViewClient shouldInterceptRequest methods.

The offline package

The offline package means that the H5 pages and resources are packaged and delivered to the client, and the client decompresses them directly into the local storage. The advantage is that because of its localization, the first screen loading speed is fast, the user experience is closer to the original, and it can be run offline without relying on the network. The disadvantage is that the development process/update mechanism is complicated, which requires the cooperation of the client and even the server. Here I take Hybrid App technology analysis – practical ideas mentioned in the article as an example for your reference.

Resources:

  • H5: Each code package has a unique and increasing version number;
  • Native: downloads the package and decompresses the resource file to the corresponding directory
  • Server: provides an interface to obtain the version number and download address of the latest code package online.

Process:

  • The front-end update code is packaged and uploaded to the specified server according to the version number.
  • Every time the page is opened, H5 requests the interface to obtain the version number of the latest online code package, and compares the version number with the local package. When the online version number is greater than the local package version number, the native download offline package is called
  • The client directly goes to the online address to download the latest code package, and unpacks it to the current directory file.

About the mechanism of offline package need to pay attention to a lot of problems, this article certainly can not take care of the complete, you can refer to the mobile H5 first screen second open optimization scheme discussion, Meituan Dianping Hybrid construction, “mobile terminal local H5 second open scheme exploration and implementation” these articles have a look.

Some open source solutions

The introduction of CacheWebView the library link my.oschina.net/yale8848/bl here… According to the author, it is mainly to solve the problem of Android’s own cache space is too small (12M). I have a brief look at the code, and mainly intercept these two methods:

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)

    @Override

    public WebResourceResponse interceptRequest( WebResourceRequest request) {

        if (mInterceptor==null) {

            return null;

        }

        return mInterceptor.interceptRequest(request);

    }



    @Override

    public WebResourceResponse interceptRequest(String url) {

        if (mInterceptor==null) {

            return null;

        }

        return mInterceptor.interceptRequest(url);

    }

Copy the code

Then use Okhttp to download the resource, and configure the cache interceptor to OkHttpClient, because Okhttp has good caching support, so overcome the WebView cache space is too small and uncontrollable.

VasSonic Tencent is a lightweight, high-performance Hybrid framework designed to speed up the loading of the first screen of a page. It supports both static and dynamic direct page loading, and is compatible with offline packages. The advantage is that it has good performance, fast speed and is produced by large factories. The disadvantage is that it has complex configuration and needs front and rear end access. For VasSonic/wiki and Tencent, let your H5 page first screen open seconds!

conclusion

How to improve the loading speed of WebView actually involves a lot of aspects, need to pay attention to the details are also a lot, there is no way to generalize. We need to be tailored according to the company’s business needs, according to the need to configure.

Demo: github.com/mundane7996…

Reference:

H5 and mobile terminal WebView cache mechanism analysis and actual combat Tencent offering a big move VasSonic, let your H5 page first screen seconds open! “Exploration and Implementation of Local H5 Second Opening Scheme on Mobile Terminal” Optimization Scheme of mobile H5 First Screen Second Opening Discuss Hybrid construction of Meituan Dianping H5 Cache Mechanism Analysis of Web loading Performance optimization on mobile terminal QQ members’ high-quality H5 architecture practice based on Hybrid Chat from the WebView cache to the Http cache mechanism essay Meituan | the nuggets technologies: the WebView performance, experience, analysis and optimization

Welcome to follow my wechat official account “Yu Gang said” to receive first-hand technical dry goods