一、问题场景
总的来说,Webview的页面加载可分为四种流程。
- 正常流程:
- 重定向(302)流程:
- 内部跳转(location.href)流程:
- 历史栈回退(history.back)流程:
其中,历史栈回退流程比较特殊。这是因为,历史栈回退首先触发的是shouldInterceptRequest,而非loadUrl或shouldOverrideUrlLoading。
private String loadUrl;
public boolean shouldOverrideUrlLoading(WebView view, String url) {
loadUrl = url;
return false;
}
@Override
public void loadUrl(String url) {
loadUrl = url;
super.loadUrl(url);
}
public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) {
String url = request.getUrl().toString();
if (loadUrl.equals(url)) {
//进行拦截处理
}
return null;
}
这使得对WebView页面加载进行拦截处理时,由于缺少了loadUrl和shouldOverrideUrlLoading提前告知页面loadUrl的过程,而页面(loadUrl)、各种资源(js、css、jpg等)以及业务请求(ajax)都会先在shouldInterceptRequest中回调后再执行加载,从而无法确定当前要加载的url是不是需要拦截的loadUrl,变得非常棘手。
如果一定要对history.back进行拦截,那该怎么办呢?经过一番折腾终于解决了问题,鉴于网上(google keywords: webview dectect history.back)也有一些同行遇到了相同问题,却未看到有人给出行之有效的解决方案,于是将在下文撰写我的解决办法。
二、WebView栈列表
WebView提供了一个获取栈列表WebBackForwardList的方法,
copyBackForwardList()
WebBackForwardList源码的javadoc描述里非常清晰地说明了这点。
/**
* 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();
}
我们可以利用WebBackForwardList获取WebView的栈历史快照WebHistoryItem对象,这个对象里提供了获取页面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();
...
}
三、解决方案
遍历栈列表获取各个栈快照对象,并解析得到一个栈url列表,值得注意的是,copyBackForwardList()和getCurrentIndex()需要在WebView创建线程(主线程)调用,而shouldInterceptRequest是在子线程中回调的,从而updateBackForwardList()不能直接shouldInterceptRequest中调用,也不建议通过线程阻塞的方式在shouldInterceptRequest中等待url栈解析完再进行拦截判断,毕竟除了要拦截的loadUrl,还有大量其他的请求也会在这里回调执行判断。
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());
}
}
推荐在onPageFinished()执行完后,调用updateBackForwardList()解析url栈,一方面onPageFinished()本身是在主线程中回调的,另一方面这时页面加载完毕,不会影响页面加载性能。
@Override
public void onPageFinished(WebView view, final String url) {
super.onPageFinished(view, url);
...//业务逻辑
updateBackForwardList();
}
接下来就可以很方便地进行拦截判断了,如果拦截的url在栈url列表中存在,就说明js执行了history.back(history.forward执行流程相同),需要做拦截处理。
public WebResourceResponse shouldInterceptRequest(WebView view, final WebResourceRequest request) {
String url = request.getUrl().toString();
if (loadUrl.equals(url) || backForwardUrlList.contains(url)) {
//进行拦截处理
}
return null;
}