The pitfalls I have stepped on in WebView over the years

I previously introduced the interaction method between Webview and JS in the article “Full Analysis of Interaction between WebView and JS in Android”, but the Webview control is simply a love-hate one. All kinds of errors you can’t think of appear on all kinds of strange mobile phones. On, there are various different versions, so I want to use this blog to summarize some pitfalls that must be paid attention to in Webview development.

1. WebView memory leak problem Problem description: Webview memory leak is still very serious, especially when the page you load is relatively large. Solution: 1) The activity that displays the webview can open another process, so that it can be separated from the main process of our app. Even if the webview has problems such as OOM crash, it will not affect the main program. How to implement it is actually very simple. Just add Android:process=”packagename.web” to the activity tag of Androidmanifest.xml, and when the process ends, please manually call System.exit(0).

  1. If you really don’t want to use the method of opening additional processes to solve the problem of webview memory leakage, then the following method can avoid this situation to a large extent.
public void releaseAllWebViewCallback() {<!-- -->
         if (android.os.Build.VERSION.SDK_INT < 16) {<!-- -->
             try {<!-- -->
                 Field field = WebView.class.getDeclaredField("mWebViewCore");
                 field = field.getType().getDeclaredField("mBrowserFrame");
                 field = field.getType().getDeclaredField("sConfigCallback");
                 field.setAccessible(true);
                 field.set(null, null);
             } catch (NoSuchFieldException e) {<!-- -->
                 if (BuildConfig.DEBUG) {<!-- -->
                     e.printStackTrace();
                 }
             } catch (IllegalAccessException e) {<!-- -->
                 if (BuildConfig.DEBUG) {<!-- -->
                     e.printStackTrace();
                 }
             }
         } else {<!-- -->
             try {<!-- -->
                 Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
                 if (sConfigCallback != null) {<!-- -->
                     sConfigCallback.setAccessible(true);
                     sConfigCallback.set(null, null);
                 }
             } catch (NoSuchFieldException e) {<!-- -->
                 if (BuildConfig.DEBUG) {<!-- -->
                     e.printStackTrace();
                 }
             } catch (ClassNotFoundException e) {<!-- -->
                 if (BuildConfig.DEBUG) {<!-- -->
                     e.printStackTrace();
                 }
             } catch (IllegalAccessException e) {<!-- -->
                 if (BuildConfig.DEBUG) {<!-- -->
                     e.printStackTrace();
                 }
             }
         }
     }

Just call this method in the destroy method of webview.

2. Be careful to return true in shouldoverrideurlloading

When WebviewClient is set, if there is no need to intercept the URL in shouldoverrideurlloading, but simply continue to load the URL, it is recommended to load the URL by returning false instead of loadUrl. 1) This callback will not be notified when the request method is “POST”. 2) Because if you use loadUrl to load, it will be particularly troublesome to perform webview.goBack when loading a URL with a jump. For example, the loading link is as follows: A1->(A2->A3->A4)->A5. The jumps are in brackets. If the return false method is used, then when goBack, you can directly return to the A1 web page from the second step. You only need to execute goBack twice to return to A1 from A5. If you use loadUrl, you cannot directly return to the A web page from the second step. Because loadUrl considers each jump in the second step to be a new web page load, goBack needs to be executed four times from A5 to A1.

True should be returned only when there is no need to load the URL but to intercept and perform other processing, such as intercepting special URLs such as tel:xxx for dialing processing.

3. Crash caused by getSettings().setBuiltInZoomControls(true). Problem description: After this method is called, if you touch the screen and the pop-up prompt box has not disappeared, if the activity ends, an error will be reported. This happens to many phones above 3.0 and below 4.4

Solution: Manually set the webiew to setVisibility(View.GONE) in the onDestroy method of the activity

4. Problems with the onPageFinished function Problem description: You can never be sure whether the web page content is really loaded when WebView calls this method. This method may be called multiple times when the currently loading web page jumps. Most developers refer to http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview- finishing-loading-a-url-in-android is the highly voted answer above, but the solutions listed there are not perfect.

Solution: When your WebView needs to load various web pages and needs to take some operations when the page is loaded, WebChromeClient.onProgressChanged() may be more reliable than WebViewClient.onPageFinished() Some. If anyone has a better solution, please leave a message.

5. WebView background power consumption problem.

Problem description: When your program calls WebView to load a web page, WebView will start some threads by itself. If you do not destroy WebView correctly, these remaining threads will always run in the background, resulting in Your application’s battery consumption remains high.

Solution: Call System.exit(0) directly in Activity.onDestroy() so that the application is completely removed from the virtual machine, so there will be no problems.

6. Unable to release js in the background, causing power consumption

Problem description: In some mobile phones, if there are some js in the HTML loaded by the webview that have been executing things such as animations, if the webview is hung in the background at the moment, these resources will not be Users cannot detect it when it is released, causing the CPU to be occupied all the time and consume power very quickly.

Solution: Set setJavaScriptEnabled(); to false and true respectively in Activity’s onstop and onresume.

7. How to set your own title bar using the title of the web page?

WebChromeClient mWebChromeClient = new WebChromeClient() {<!-- -->
    @Override
    public void onReceivedTitle(WebView view, String title) {<!-- -->
        super.onReceivedTitle(view, title);
        txtTitle.setText(title);
    }
};

mWedView.setWebChromeClient(mWebChromeClient());

However, it was found that on some mobile phones, when going back through webview.goBack(), onReceiveTitle() is not triggered. This will cause the title to still be the title of the previous subpage and not switch back.

There are two situations to deal with here: 1) It can be determined that the sub-pages in the webview only have secondary pages and no deeper levels. Here we only need to determine whether the current page is the initial main page. If you can goBack, just set the title back. Yes. 2) There may be multi-level pages in the webview or multi-level pages may be added in the future. This situation is more complicated to handle: because onReceiveTitle will definitely be triggered during normal sequential loading, you need to maintain the webview loading yourself. A url stack and the mapping relationship between url and title. Then you need an ArrayList to hold the loaded url, and a HashMap to save the url and corresponding title. When loading in normal order, save the url and the corresponding title. When the webview rolls back, remove the current url and take out the url of the web page to be rolled back to, find the corresponding title and set it.

One more thing to mention here is that when there is a loading error, such as no network, the title obtained in onReceiveTitle is The web page cannot be found. Therefore, it is recommended not to use the obtained title when onReceiveError is triggered.

8. How to hide the zoom control?

if (DeviceUtils.hasHoneycomb()) {<!-- -->
      mWebView.getSettings().setDisplayZoomControls(false);
}
It's better to add these two sentences
mWebView.getSettings().setSupportZoom(true);
mWebView.getSettings().setBuiltInZoomControls(true);

9. How to know whether the WebView has scrolled to the bottom of the page?

if (mWebView.getContentHeight() * mWebView.getScale()
    == (mWebView.getHeight() + mWebView.getScrollY())) {<!-- -->
    }

Attached are the official website’s comments on several methods.

getContentHeight() @return the height of the HTML content
getScale() @return the current scale
getHeight() @return The height of your view
getScrollY() @return The top edge of the displayed part of your view, in pixels.

10. How to clear cache and history?

mWebView.clearCache(true);
mWebView.clearHistory();

11. How to clear cookies?

CookieSyncManager.createInstance(this);
CookieSyncManager.getInstance().startSync();
CookieManager.getInstance().removeSessionCookie();

12. Why does the JS call fail after packaging? Reason: No obfuscation was added to proguard-rules.pro.

-keep class con.demo.activity.web.utils.JsShareInterface {<!-- -->
   public void share(java.lang.String);
}

13. Audio is played in the WebView page, and the audio is still playing after exiting the Activity

The following method needs to be called in onDestory() of Activity

1. webView.destroy();

But an error may occur:

10-10 15:01:11.402: E/ViewRootImpl(7502): sendUserActionEvent() mView == null
10-10 15:01:26.818: E/webview(7502): java.lang.Throwable: Error: WebView.destroy() called while still attached!
10-10 15:01:26.818: E/webview(7502): at android.webkit.WebViewClassic.destroy(WebViewClassic.java:4142)
10-10 15:01:26.818: E/webview(7502): at android.webkit.WebView.destroy(WebView.java:707)
10-10 15:01:26.818: E/webview(7502): at com.didi.taxi.ui.webview.OperatingWebViewActivity.onDestroy(OperatingWebViewActivity.java:236)
10-10 15:01:26.818: E/webview(7502): at android.app.Activity.performDestroy(Activity.java:5543)
10-10 15:01:26.818: E/webview(7502): at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1134)
10-10 15:01:26.818: E/webview(7502): at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3619)
10-10 15:01:26.818: E/webview(7502): at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3654)
10-10 15:01:26.818: E/webview(7502): at android.app.ActivityThread.access$1300(ActivityThread.java:159)
10-10 15:01:26.818: E/webview(7502): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1369)
10-10 15:01:26.818: E/webview(7502): at android.os.Handler.dispatchMessage(Handler.java:99)
10-10 15:01:26.818: E/webview(7502): at android.os.Looper.loop(Looper.java:137)
10-10 15:01:26.818: E/webview(7502): at android.app.ActivityThread.main(ActivityThread.java:5419)
10-10 15:01:26.818: E/webview(7502): at java.lang.reflect.Method.invokeNative(Native Method)
10-10 15:01:26.818: E/webview(7502): at java.lang.reflect.Method.invoke(Method.java:525)
10-10 15:01:26.818: E/webview(7502): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)
10-10 15:01:26.818: E/webview(7502): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
10-10 15:01:26.818: E/webview(7502): at dalvik.system.NativeStart.main(Native Method)

When the webview calls destroy, the webview is still bound to the Activity. This is because the context object of the Activity is passed in when the custom webview is built, so the webview needs to be removed from the parent container first, and then the webview is destroyed:

rootLayout.removeView(webView);
webView.destroy();

14. Handle non-hyperlink requests in WebView (such as Ajax requests) Sometimes it is necessary to add request headers, but for non-hyperlink requests, there is no way to intercept them in shouldOverrinding and use webView.loadUrl(String url ,HashMap headers) method to add request headers currently uses a temporary solution: First, you need to add a special mark/protocol in the URL, such as intercepting the corresponding request in the onWebViewResource method, and then splicing the request header to be added in the form of get The end of the url is in the shouldInterceptRequest() method, which can intercept all resource requests in the web page, such as loading JS, images, Ajax requests, etc.

 @SuppressLint("NewApi")
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view,String url) {<!-- -->
        // Non-hyperlink (such as Ajax) requests cannot add request headers directly. They are now spliced to the end of the URL. Here, an imei is spliced as an example.

        String ajaxUrl = url;
        // If marked: req=ajax
        if (url.contains("req=ajax")) {<!-- -->
           ajaxUrl + = " & amp;imei=" + imei;
        }

        return super.shouldInterceptRequest(view, ajaxUrl);

    }

15. Block the webview long press event, because the system’s copy control will be called when the webview is long pressed

mWebView.setOnLongClickListener(new OnLongClickListener() {<!-- -->

          @Override
          public boolean onLongClick(View v) {<!-- -->
              return true;
          }
      });

16. Display images first on the page

@Override
public void onLoadResource(WebView view, String url) {<!-- -->
  mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_LOAD_RESOURCE, url);
    if (url.indexOf(".jpg") > 0) {<!-- -->
     hideProgress(); //Display the page when requesting an image
     mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_HIDE_PROGRESS, view.getUrl());
     }
    super.onLoadResource(view, url);
}

17. Customize the error display interface for WebView Override the onReceivedError() method in WebViewClient:

/**
 * Display a custom error prompt page and cover it with a View in WebView
 */
protected void showErrorPage() {<!-- -->
    LinearLayout webParentView = (LinearLayout)mWebView.getParent();

    initErrorPage();
    while (webParentView.getChildCount() > 1) {<!-- -->
        webParentView.removeViewAt(0);
    }
    LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT);
    webParentView.addView(mErrorView, 0, lp);
    mIsErrorPage = true;
}
protected void hideErrorPage() {<!-- -->
    LinearLayout webParentView = (LinearLayout)mWebView.getParent();

    mIsErrorPage = false;
    while (webParentView.getChildCount() > 1) {<!-- -->
        webParentView.removeViewAt(0);
    }
}


   protected void initErrorPage() {<!-- -->
    if (mErrorView == null) {<!-- -->
        mErrorView = View.inflate(this, R.layout.online_error, null);
        Button button = (Button)mErrorView.findViewById(R.id.online_error_btn_retry);
        button.setOnClickListener(new OnClickListener() {<!-- -->
            public void onClick(View v) {<!-- -->
                mWebView.reload();
            }
        });
        mErrorView.setOnClickListener(null);
    }
}

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {<!-- -->
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>mErrorView.setVisibility(View.VISIBLE);
<span style="white-space:pre"> </span>super.onReceivedError(view, errorCode, description, failingUrl);
}

Finally, let me mention some tips on WebView: 1. There are also skills in creating webview. It is best not to use webview in layout.xml. You can use a viewgroup container and use code to dynamically addview to the container. (webview), so that the webview can be destroyed in onDestory() and the memory can be cleaned up in time. In addition, it should be noted that when creating a webview, you need to use the applicationContext instead of the context of the activity. When it is destroyed, the activity object will no longer be occupied. Everyone should know this. The last one left When the webview needs to be destroyed in time, in onDestory(), you should first remove the webview from the viewgroup, and then call webview.removeAllViews(); webview.destory();

create

ll = new LinearLayout(getApplicationContext());
ll.setOrientation(LinearLayout.VERTICAL);
wv = new WebView(getApplicationContext());

destroy

@Override
rotated void onDestroy() {<!-- -->
   ll.removeAllViews();
   wv.stopLoading();
   wv.removeAllViews();
   wv.destroy();
   wv = null;
   ll = null;
   super.onDestroy();

2. In addition, many people don’t know that webview actually has its own complete cookie mechanism. Making good use of this can greatly increase the access speed to the client.

Write picture description here

In fact, cookies are stored in this table. Many people want an effect: a web page update cookie will take effect without refreshing the page after setting the cookie. The method to achieve this is different below 2.3 and above 2.3, but the current Android version basically does not have 2.3.

public void updateCookies(String url, String value) {<!-- -->
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {<!-- --> // 2.3 and below
             CookieSyncManager.createInstance(getContext().getApplicationContext());
         }
         CookieManager cookieManager = CookieManager.getInstance();
         cookieManager.setAcceptCookie(true);
         cookieManager.setCookie(url, value);
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {<!-- -->
             CookieSyncManager.getInstance().sync();
         }
     }

3. For further optimization, after the activity is passively killed, it is best to save the webview state, so that the user can see the previous state when opening it next time. Well, just do it, webview supports saveState(bundle) and restoreState(bundle) Method, so it’s simple, let’s look at the code: Save state:

@Override
protected void onSaveInstanceState(Bundle outState) {<!-- -->
super.onSaveInstanceState(outState);
wv.saveState(outState);
Log.e(TAG, "save state...");
}

Restoring state: In activity’s onCreate(bundle savedInstanceState), call this:

if(null!=savedInstanceState){<!-- -->
    wv.restoreState(savedInstanceState);
    Log.i(TAG, "restore state");
}else{<!-- -->
    wv.loadUrl("http://3g.cn");
}

4. Delayed loading of WebView images: If some pages contain network images, the time we wait for loading images on mobile devices may be very long, so we need to delay loading of images so that it does not affect the speed of loading the page: Define variables:

boolean blockLoadingNetworkImage=false;

Set when WebView is initialized:

blockLoadingNetworkImage = true;

webView.setWebViewClient(new WebViewClient() {<!-- -->
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {<!-- -->
                //When the return value is true, control to open the WebView, if false, call the system browser or third-party browser
                return true;
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {<!-- -->
                super.onPageStarted(view, url, favicon);
                if (!blockLoadingNetworkImage){<!-- -->
                    webView.getSettings().setBlockNetworkImage(true);
                }
            }

            @Override
            public void onPageFinished(WebView view, String url) {<!-- -->
                super.onPageFinished(view, url);
                if (blockLoadingNetworkImage){<!-- -->
                    webView.getSettings().setBlockNetworkImage(false);
                }
            }
        });

For more Android advanced guides, you can Scan the code to unlock “Android Ten Major Section Documents”

1. Android vehicle application development system study guide (with actual project practice)

2. Android Framework study guide to help you become a system-level development expert

3.2023 Latest Android Intermediate and Advanced Interview Questions Summary + Analysis, Say Goodbye to Zero Offers

4. Enterprise-level Android audio and video development learning route + project practice (with source code)

5. Android Jetpack builds high-quality UI interfaces from entry to proficiency

6. Flutter technology analysis and practical implementation, the first choice for cross-platform

7. Kotlin improves the architectural foundation in all aspects from entry to actual use

8. Advanced Android plug-in and componentization (including practical tutorials and source code)

9. Android performance optimization practice + 360° all-round performance tuning

10. Android from beginner to proficient, the path to advancement for experts

It’s not easy to code, so pay attention. ?( ′?` )