I. Problem scenario

In general, Webview page loading can be divided into four processes.

  • Normal process:

  • Redirection (302) Process:

  • Internal jump (location.href) flow:

  • History stack rollback process:

Among them, the history stack rollback process is special. This is because the historical stack rollback first triggers a shouldInterceptRequest, not onPageStarted or shouldOverrideUrlLoading.

private String loadUrl; public boolean shouldOverrideUrlLoading(WebView view, String url) { loadUrl = url; return false; } public void onPageStarted(WebView view, String url, Bitmap favicon) { loadUrl = url; } public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) { String url = request.getUrl().toString(); If (loadurl.equals (url)) {return null; }Copy the code

This makes the process of onPageStarted and shouldOverrideUrlLoading to tell the page loadUrl in advance when intercepting WebView page loading is missing. ShouldInterceptRequest (loadUrl), resources (JS, CSS, JPG, etc.), and business requests (Ajax) are all called back in the shouldInterceptRequest, so it becomes very tricky to determine if the url to be loaded is a loadUrl that needs to be intercepted.

What if you must intercept history.back? I finally solved the problem after some twists and turns. In view of the fact that some peers on the Internet (Google keywords: Webview Dectect history. Back) have encountered the same problem, but no effective solution has been given, I will write my solution below.

WebView stack list

WebView provides a way to get the stack list WebBackForwardList,

copyBackForwardList()
Copy the code

The Javadoc description of the WebBackForwardList source code makes this very clear.

/**
 * This class contains the back/forward list for a WebView.
 * WebView.copyBackForwardList() will return a copy of this class used to
 * inspect the entries in the list.
 */
public abstract class WebBackForwardList implements Cloneable, Serializable {
    /**
     * Return the current history item. This method returns {@code null} if the list is
     * empty.
     * @return The current history item.
     */
    @Nullable
    public abstract WebHistoryItem getCurrentItem();

    /**
     * Get the index of the current history item. This index can be used to
     * directly index into the array list.
     * @return The current index from 0...n or -1 if the list is empty.
     */
    public abstract int getCurrentIndex();

    /**
     * Get the history item at the given index. The index range is from 0...n
     * where 0 is the first item and n is the last item.
     * @param index The index to retrieve.
     */
    public abstract WebHistoryItem getItemAtIndex(int index);

    /**
     * Get the total size of the back/forward list.
     * @return The size of the list.
     */
    public abstract int getSize();

    /**
     * Clone the entire object to be used in the UI thread by clients of
     * WebView. This creates a copy that should never be modified by any of the
     * webkit package classes. On Android 4.4 and later there is no need to use
     * this, as the object is already a read-only copy of the internal state.
     */
    protected abstract WebBackForwardList clone();
}
Copy the code

We can use WebBackForwardList to get a snapshot of the WebView’s stack history, the WebHistoryItem object, which provides the method to get the page URL.

/** * A convenience class for accessing fields in an entry in the back/forward list * of a WebView. Each WebHistoryItem is a snapshot of the requested history * item. * @see WebBackForwardList */ public abstract class WebHistoryItem implements Cloneable { ... /** * Return the url of this history item. The url is the base url of this * history item. See getTargetUrl() for the url that is the actual target of * this history item. * @return The base url of this history item. */ public abstract String getUrl(); . }Copy the code

Iii. Solutions

It is worth noting that copyBackForwardList() and getCurrentIndex() need to be called in the WebView thread (main thread). ShouldInterceptRequest is called back from a child thread, so updateBackForwardList() can’t be called directly from a shouldInterceptRequest. It’s also not recommended to wait for the url stack to resolve in a shouldInterceptRequest thread. After all, there are plenty of other requests that need to be called back from this shouldInterceptRequest thread.

private List<String> backForwardUrlList = new ArrayList<>(); private void updateBackForwardList() { backForwardUrlList.clear(); WebBackForwardList backForwardList = copyBackForwardList(); int currentIndex = backForwardList.getCurrentIndex(); for (int index = 0; index <= currentIndex; index++) { WebHistoryItem webHistoryItem = backForwardList.getItemAtIndex(index); backForwardUrlList.add(webHistoryItem.getUrl()); }}Copy the code

It is recommended to call updateBackForwardList() to parse the URL stack after onPageFinished() is executed. OnPageFinished () itself is called back in the main thread, and it does not affect page loading performance.

@Override public void onPageFinished(WebView view, final String url) { super.onPageFinished(view, url); . // Business logic updateBackForwardList(); }Copy the code

If the intercepted URL exists in the stack URL list, it means that the JS has executed history.back (history. Forward execution process is the same), and the interception process needs to be done.

public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) { String url = request.getUrl().toString(); If (loadUrl. Equals (url) | | backForwardUrlList. The contains (url)) {/ / to intercept treatment} return null; }Copy the code