WebView#shouldOverrideUrlLoading究竟要返回true還是false

ZxLee發表於2018-02-05

各大教程的常見做法

初接觸Android WebView的萌新,在博覽前人程式碼的時候都會看到,WebView控制元件並不是一初始化後就直接loadUrl的,這其中還有一系列的初始化設定。

比如:如果沒有給WebView設定WebViewClient,那麼,你點選網頁內的a連結,該連結會跳轉到系統本身的瀏覽器去開啟,而如果想要自己處理這個a連結,就要設定自定義的WebViewClient例項,並且實現shouldOverrideUrlLoading方法。如何實現呢,網上常見的實現方式如下:

    @Override
    public boolean shouldOverrideUrlLoading(WebView webView, String url) {
        webView.loadUrl(url);
        return true;
    }
複製程式碼

這樣,就可以完美的讓a連結在自己的WebVie裡面載入啦。

返回值為什麼是boolean型別

看到這裡,也許你會想考究一下這個boolean返回值的作用。

boolean返回值的方法在Android SDK裡很常見,比如View#dispatchKeyEvent方法的返回值就是boolean,在實現該方法時,如果你返回true則表示開發者自己來處理這個keyevent,讓系統不要再往下一個View傳播了;返回false則表示你不處理,讓系統接著把事件告訴下一個View吧。

那麼,WebView#shouldOverrideUrlLoading的返回值也是這個作用嗎?

返回true的作用我們已經證實了,確實是表示開發者自己處理了這個url的載入。那麼返回false試試呢?

@Override
    public boolean shouldOverrideUrlLoading(WebView webView, String url) {
        return false;
    }
複製程式碼

編譯執行,發現和前一種處理方式的結果一樣,WebView自己來載入a連結。找找原始碼,發現shouldOverrideUrlLoading的註釋裡有這麼一句:

    /**
     * If WebViewClient is not
     * provided, by default WebView will ask Activity Manager to choose the
     * proper handler for the url. If WebViewClient is provided, return true
     * means the host application handles the url, while return false means the
     * current WebView handles the url.
     */
複製程式碼

就是說:如果沒有設定WebViewClient例項,那麼就會由系統預設瀏覽器來處理url,如果設定了WebViewClient且本方法返回true,就表示已經處理了這個url;如果返回false,就表示讓當前webview來處理url。如此看來,這個模式和事件機制還是很類似的,是一個責任鏈模式,如果你WebViewClient不想處理,那就把事件傳給WebView,WebView的處理方式就是載入。

另外可以看到shouldOverrideUrlLoading本身的返回值就是false,也就是說,其實根本用不著自定義這個方法,傳一個原生的WebViewClient例項給WebView,就能愉快的讓WebView自己處理所有a連結跳轉啦!

    WebView webView = new WebView(context);
    webView.setWebViewClient(new WebViewClient());
    webview.loadUrl("http://www.uc.cn");
複製程式碼

至於為什麼網上教程裡基本上都是寫return true的那種處理方法,這個,筆者自然也不得而知了。但是既然跟到了這裡,不妨更加深入一點。到底兩個處理方式有沒有區別呢?

shouldOverrideUrlLoading詳解

什麼時候會有shouldOverrideUrlLoading回撥?
在網頁內點選一個a連結,會回撥;
302跳轉前,會回撥;
用JavaScript裡的location.href來跳轉,會回撥;
主動呼叫WebView.loadUrl方法來載入url,不會回撥。
...
如果列舉下去,情況太多。所以還是看代(zhu)碼(shi)吧。

/**
     * Give the host application a chance to take over the control when a new
     * url is about to be loaded in the current WebView.
     ...
     * This method is not called for requests using the POST "method".
*/
複製程式碼

“給你的宿主app一個機會來控制‘當前WebView將要載入新的url’的情況”——簡單來說,就是除了你主動用webview載入的url,其他的載入都會回撥shouldOverrideLoading。
當然,後面還有一句註釋:post請求不會回撥shouldOverrideLoading。這個就很明顯是為的資料安全考慮了。

區別

終於要說到區別了,這個區別的情況也是筆者偶然間才發現的。
前端有時候會在html的<head>節點裡用JavaScript做一些判斷,然後跳轉到另一個url。比如,我們有一個a網頁,它的原始碼如下:

<html>
    <head>
        <script>
            location.href = "http://www.uc.cn"
        </script>
    </head>
</html>
複製程式碼

這個跳轉當然可以在shouldOverrideUrlLoading裡面捕獲到,然而,這個時候,用true還是false的方式來處理,就會出現區別了。
如果用主動loadUrl來實現,並且返回true,那麼,a網頁會被加入到WebView的歷史記錄中,也就是說,跳轉後你按返回鍵,是可以回到a網頁的,當然,這個時候又會觸發js裡面的跳轉,跳去了“http://www.uc.cn”。
如果用直接return false的方式來實現呢。a網頁是不會被加入WebView的歷史記錄的,返回鍵也無法回到a網頁,彷佛它從來沒有出現過。

產生這種區別的原因是什麼?這就關係到WebView的底層的實現了。location.href被重置時,會呼叫JNI層的NavigationScheduler類

bool NavigationScheduler::MustReplaceCurrentItem(LocalFrame* target_frame) {
  // Non-user navigation before the page has finished firing onload should not
  // create a new back/forward item. See https://webkit.org/b/42861 for the
  // original motivation for this.
  if (!target_frame->GetDocument()->LoadEventFinished() &&
      !Frame::HasTransientUserActivation(target_frame))
    return true;

  // Navigation of a subframe during loading of an ancestor frame does not
  // create a new back/forward item. The definition of "during load" is any time
  // before all handlers for the load event have been run. See
  // https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation
  // for this.
  Frame* parent_frame = target_frame->Tree().Parent();
  return parent_frame && parent_frame->IsLocalFrame() &&
         !ToLocalFrame(parent_frame)->Loader().AllAncestorsAreComplete();
}
複製程式碼

當這個方法返回true時,location.href傳的引數會替換掉當前url,也就是說,不留下歷史記錄。什麼時候返回true呢?在主文件的onload event發生之前改動href,就會返回true。我們的例子中的js是在<head>裡面執行的,屬於這一類情況。
所以,當我們在shouldOverrideUrlLoading裡面直接返回false,讓WebView使用自己預設的方式來載入,就不會有歷史記錄存在了。
然而,當我們通過主動呼叫loadUrl並返回true的方式來干預載入時,WebView會先執行完a網頁的載入(發生了onload event)之後,再執行loadUrl載入新的url,如此,a網頁就被加入到歷史記錄裡面了。

基於此,筆者的建議是,shouldOverrideUrlLoading回撥時,如果你不需要做其他的特殊操作,僅僅只是要載入新url的話,可以直接return false,讓系統WebView以自己的方式來載入url,會更健壯。

相關文章