WebViewClient没有调用shouldOverrideUrlLoading方法

32
问题非常简单。在应用程序中,我们希望跟踪当前显示的 URL。为此,我们使用来自 WebViewClient 的 shouldOverrideUrlLoading 回调,并将每个更新的 url 保存到类字段中。以下是相关代码:shouldOverrideUrlLoading
    mWebView.getSettings().setJavaScriptEnabled(true);
    mWebView.getSettings().setDomStorageEnabled(true); 
    mWebView.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            mCurrentUrl = url;

            // If we don't return false then any redirect (like redirecting to the mobile
            // version of the page) or any link click will open the web browser (like an
            // implicit intent).
            return false;
        }



        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            ...
        }
    });
    mWebView.loadUrl(mInitialUrl);

然而,至少有一种情况下,回调函数永远不会被触发,mCurrentUrl字段也不会被更新。
链接:https://m.pandora.net/es-es/products/bracelets/556000 上次更新的链接(点击产品时不会调用shouldOverrideUrlLoading):https://m.pandora.net/es-es/products/bracelets 我已经尝试使用onPageStarted()等回调函数,但是该URL也被过滤了,并且似乎没有可访问的上游URL,因为它是受保护的代码。
阅读有关WebView的Android文档后,我发现了这个:
链接:https://developer.android.com/guide/webapps/migrating.html#URLs 新的WebView在请求资源和解析使用自定义URL方案的链接时应用额外的限制。例如,如果您实现了shouldOverrideUrlLoading()或shouldInterceptRequest()等回调函数,则WebView仅对有效的URL调用它们。
但仍然不合理,因为上面的URL是通用的,应该符合标准。
有什么替代方法或解决方案吗?

URL有什么特别之处,比如请求是否使用了“post”方法? - Swordsman
你尝试过在自定义的WebViewClient中覆盖onReceivedError方法吗?也许你可以从该回调中检查请求和错误参数,以查看发生了什么。另外,也可以尝试在设备浏览器中键入该URL,以查看实际网页加载是否出现问题。 - w3bshark
页面运行良好,也没有收到任何错误。我认为答案在于HTML5历史记录API,它可以在不实际发出请求的情况下更改浏览器中显示的URL(因为内容在脚本框架内工作)。 - SuppressWarnings
你尝试过在 onPageFinished(WebView, String) 方法内部执行 mCurrentUrl = url 吗?如果你只是想获取当前的URL,这应该可以工作,不是吗? - TroniPM
@PMateus 是的,它也不起作用。答案似乎与Leo Nikkila在这里发布的内容有关。 - SuppressWarnings
是的...我明白了。嗯,你可以看到我的回答(太长了无法在评论中发布)。 - TroniPM
11个回答

36
当你在该网页上点击一个产品时,它会使用JavaScript加载新内容,并使用HTML5 History APIs更新地址栏中的可见URL。
从上面的MDN文章中可以看到:

这将导致URL栏显示http://mozilla.org/bar.html,但不会导致浏览器加载bar.html甚至检查bar.html是否存在。

这些有时被称为单页应用程序。由于实际加载的页面没有改变,因此页面加载的WebView回调不会被调用。
如果您确切地知道要拦截哪种HTTP请求,您可以使用每个请求都会调用的shouldInterceptRequest回调。很可能Web应用程序会从API加载一些数据,例如显示产品时,您可以检测到这些数据。
如果无法检测到此项功能,但您控制着Web应用程序,则可以使用Android JavaScript接口从Web页面直接调用Android应用程序中的方法。
如果您无法控制加载的页面,则仍然可以尝试将本地JavaScript文件注入到Web页面中,并观察何时使用历史记录APIs,然后通过JS接口调用Android应用程序中的方法。我在Chrome中使用先前链接中描述的方法尝试观察这些事件,看起来可以正常工作。

13

也许这可以帮助某些人,虽然问题中的签名是正确的,但Android Studio建议使用以下方法的签名:

public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {

然后就从未被调用。我花了一些时间才注意到正确的签名应该是:

public boolean shouldOverrideUrlLoading(WebView view, String url) {

很抱歉如果这不完全符合问题,但我相信这可能对处于同样情况的人有所帮助。并不总是容易注意到第二个参数是不同的。


6
问题发生在使用API 23及以下版本的设备上。为了支持所有Android版本,必须实现两个回调函数。我向Chromium提交了一个带有文档的漏洞报告:https://bugs.chromium.org/p/chromium/issues/detail?id=1064537。 - saschpe
只需将androidx.webkit:webkit:1.4.0添加为依赖项并使用WebViewClientCompat。这样,您只需要覆盖该方法一次即可。 - Carsten Hagemann
仍然对我不起作用。 - Taslim Oseni

8
请省略 mWebView.getSettings().setDomStorageEnabled(true); 然后再次尝试,如果发现新的url,则会调用shouldOverrideUrl()函数。

8

对于我来说,问题出在以下代码行 -

mWebView.getSettings().setSupportMultipleWindows(true);

在移除之后,应该重写shouldOverrideUrlLoading被调用。


我不得不明确地将其设置为false才能使我的下载工作正常。我猜这是因为锚点中有target="_blank" - Edgar

7

我曾经和你一样遇到了同样的问题,最终我通过扩展WebViewChromeClient并监听回调来解决了这个问题。

public void onReceivedTitle(WebView view, String title)

mWebView.setWebChromeClient(mSWWebChromeClient);

private WebChromeClient mSWWebChromeClient = new WebChromeClient() {

        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
            if (!view.getUrl().equals(mCurrentUrl)) {
                mCurrentUrl = view.getUrl();
                //make something
            }
        }

    }; 

7
在遇到这个问题并搜索解决方案后,我找到了一个对我非常有效的方法。 https://dev59.com/T2ox5IYBdhLWcg3wSCVW#56395424
 override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
    // your code here
    super.doUpdateVisitedHistory(view, url, isReload)
}

5

您可以尝试另一种方法:通过JavaScript捕获URL。请按照以下方式初始化webView:

webView.addJavascriptInterface(new WebAppInterface(getActivity()), "Android");

在页面完全加载后(您可以使用算法进行检查,如https://dev59.com/yW025IYBdhLWcg3wIyKf#6199854),然后:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    webView.evaluateJavascript("(function() {return window.location.href;})", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String url) {
            //do your scheme with variable "url"
        }
    });
} else {
    webView.loadUrl("javascript:Android.getURL(window.location.href);");
}

并声明你的 WebAppInterface:

public class WebAppInterface {
    Activity mContext;

    public WebAppInterface(Activity c) {
        mContext = c;
    }

    @JavascriptInterface
    public void getURL(final String url) {
        mContext.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //do your scheme with variable "url" in UIThread side. Over here you can call any method inside your activity/fragment
            }
        });

    }

}

你可以采用类似的方法来获取网址,或者页面内的其他内容。

4

添加以下代码:webView.getSetting().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 然后 shouldOverrideUrl 将会被触发。


应用此方法似乎可以解决问题,并且在预期时调用shouldOverrideUrl,但是设置MIXED_CONTENT_ALWAYS_ALLOW存在安全隐患。 - DaveAlden

3

onProgressChanged总是在重新加载、通过用户点击或XmlHttpRequest加载新页面时触发。比较之前的加载URL和当前的加载URL,您就会知道它是重新加载还是加载新页面。这在我的单页Web应用程序中表现得非常完美。

首先声明一个全局变量来存储上一个URL。

String strLastUrl = null;

那么重写onProgressChanged(WebView view, int progress)方法

mWebView.setWebChromeClient(new MyWebChromeClient(){

   @Override
   public void onProgressChanged(WebView view, int progress) {
            if (progress == 100) {
                //A fully loaded url will come here
                String StrNewUrl = view.getUrl();
                if(TextUtils.equals(StrNewUrl,strLastUrl)){
                   //same page was reloaded, not doing anything
                }else{
                   //a new page was loaded,write this new url to variable
                   strLastUrl = StrNewUrl;
                   //do your work here
                   Log.d("TAG", "A new page or xhr loaded, the new url is : " + strLastUrl);
                }
            }
            super.onProgressChanged(view, progress);
        }
});

我也尝试了上面的解决方案,但其中大多数在我的情况下存在问题:

  1. doUpdateVisitedHistory 有时无法返回XmlHttpRequest造成的"#"之后的正确URL。
  2. onReceivedTitle在我的情况下不起作用,因为XMLHttpRequest检索到的响应没有<title></title>标记。
  3. JavascriptInterface方法也可以工作,但我担心它会与JavaScript引起安全相关问题。

1
最佳解决方案,正如Lynch所提到的,上面提供的解决方案都对我无效,但他的答案有效。 - Cosmic Dev

2
public class AndroidMobileAppSampleActivity extends Activity {
 /** Called when the activity is first created. */
String mCurrentUrl="";
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    WebView mWebView = (WebView) findViewById(R.id.mainWebView);

    WebSettings webSettings = mWebView.getSettings();
    webSettings.setJavaScriptEnabled(true);

    mWebView.setWebViewClient(new MyCustomWebViewClient());
    mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);

    mWebView.loadUrl("https://m.pandora.net/es-es/products/bracelets/556000");

}

private class MyCustomWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        mCurrentUrl = url;
        Log.i("mCurrentUrl",""+mCurrentUrl);  
        view.loadUrl(url);

        return true;
    }
}
}

试试这个...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接