WebView

WebView·開車指南

2016-08-31 BugDev BugDev

Tips

  1. 由於WebView的用法實在太多,如果您只是想查詢某個功能的使用——建議Ctrl+F(Commad+F)在本頁面搜索關鍵字查找。

  2. 文章給前半部分大多是方法的介紹,若嫌瑣碎可直接拖到最後看代碼演示。 


Thanks for reading~!  ٩(♡ε♡ )۶ 



前言


喝酒不開車,開車不喝酒。 



目錄


  1. WebView簡介

  2. WebView基本使用

  3. WebView常用方法

  4. WebSettings

  5. WebViewClient

  6. WebChromeClient

  7. JavaScript與WebView交互

  8. WebView加載優化

  9. 駕照考試

  10. 上路



WebView簡介


爲了方便開發者實現在app內展示網頁並與網頁交互的需求,Android SDK提供了WebView組件。
它繼承自AbsoluteLayout,展示網頁的同時,也可以在其中放入其他的子View。
現如今,Hybrid應用似乎佔據的APP的主流類型,那麼關於WebView的使用就變得越發的重要。
從Android 4.4(KitKat)開始,原本基於WebKit的WebView開始基於Chromium內核,這一改動大大提升了WebView組件的性能以及對HTML5,CSS3,JavaScript的支持。不過它的API卻沒有很大的改動,在兼容低版本的同時只引進了少部分新的API,並不需要你做很大的改動。
不過有幾點改變需要注意,但我嘗試着翻譯了下,發現還是英文原文說得好,所以我貼鏈接吧~~~

https://developer.android.com/guide/webapps/migrating.html


在WebView中,有幾個地方是我們可以使用來定製我們的WebView各種行爲的,分別是:WebSettingsJavaScriptInterfaceWebViewClient以及WebChromeClient。這些我都會在接下來的文章中一一介紹。



WebView基本使用


下面簡單介紹下WebView的基本使用: 首先新建一個工程,在layout文件裏放入一個WebView控件(當然也可以通過Java代碼動態放入,這裏不演示了)



然後在Activity的onCreate方法裏寫入如下代碼:



接着在AndroidManifest聲明訪問網絡的權限:

就,完事了~ 這時運行app,它已經可以訪問指定地址的網頁了。 



上面提到了WebView繼承自AbsoluteLayout,可以在其中放入一些子View,那也順手來一下。 Layout文件改爲:


Activity的onCreate里加上:


這時,運行app,裏面就會多出一個Button~ 但如果你真的運行的話,你就會發現,app會自動跳到瀏覽器並打開指定的網頁,而並非在app內展示網頁,那這就與我們的初衷背道而馳了,那麼要如何實現網頁在App內打開呢?這就引出了下面的章節會提到的東西:WebViewClient。我先將代碼貼出,具體實現原理留到下節說明。

最終XML佈局就如上面那樣,Java代碼(最終)如下:


效果圖: 



WebView簡介


接下來再介紹一些WebView的常用方法,具體演示會在後面章節的代碼裏統一展示。

  • String getUrl():獲取當前頁面的URL。 

  • reload():重新reload當前的URL,即刷新。 


  • boolean canGoBack():用來確認WebView裏是否還有可回退的歷史記錄。通常我們會在WebView裏重寫返回鍵的點擊事件,通過該方法判斷WebView裏是否還有歷史記錄,若有則返回上一頁。 

  • boolean canGoForward():用來確認WebView是否還有可向前的歷史記錄。 

  • boolean canGoBackOrForward(int steps):以當前的頁面爲起始點,用來確認WebView的歷史記錄是否足以後退或前進給定的步數,正數爲前進,負數爲後退。 

  • goBack():在WebView歷史記錄後退到上一項。 

  • goForward():在WebView歷史記錄裏前進到下一項。 

  • goBackOrForward(int steps):以當前頁面爲起始點,前進或後退歷史記錄中指定的步數,正數爲前進,負數爲後退。 


  • clearCache(boolean includeDiskFiles):清空網頁訪問留下的緩存數據。需要注意的時,由於緩存是全局的,所以只要是WebView用到的緩存都會被清空,即便其他地方也會使用到。該方法接受一個參數,從命名即可看出作用。若設爲false,則只清空內存裏的資源緩存,而不清空磁盤裏的。 

  • clearHistory():清除當前webview訪問的歷史記錄。 

  • clearFormData():清除自動完成填充的表單數據。需要注意的是,該方法僅僅清除當前表單域自動完成填充的表單數據,並不會清除WebView存儲到本地的數據。 



  • onPause():當頁面被失去焦點被切換到後臺不可見狀態,需要執行onPause操作,該操作會通知內核安全地暫停所有動作,比如動畫的執行或定位的獲取等。需要注意的是該方法並不會暫停JavaScript的執行,若要暫停JavaScript的執行請使用接下來的這個方法。 

  • onResume():在先前調用onPause()後,我們可以調用該方法來恢復WebView的運行。 


  • pauseTimers():該方法面向全局整個應用程序的webview,它會暫停所有webview的layout,parsing,JavaScript Timer。當程序進入後臺時,該方法的調用可以降低CPU功耗。 

  • resumeTimers():恢復pauseTimers時的所有操作。 

  • destroy():銷燬WebView。需要注意的是:這個方法的調用應在WebView從父容器中被remove掉之後。我們可以手動地調用



  • getScrollY():該方法返回的當前可見區域的頂端距整個頁面頂端的距離,也就是當前內容滾動的距離。 

  • getHeight():方法都返回當前WebView這個容器的高度。其實以上兩個方法都屬於View。 

  • getContentHeight():該方法返回整個HTML頁面的高度,但該高度值並不等同於當前整個頁面的高度,因爲WebView有縮放功能, 所以當前整個頁面的高度實際上應該是原始HTML的高度再乘上縮放比例。因此,準確的判斷方法應該是


  • pageUp(boolean top):將WebView展示的頁面滑動至頂部。 

  • pageDown(boolean bottom):將WebView展示的頁面滑動至底部。 


WebSettings


WebSettings是用來管理WebView配置的類。當WebView第一次創建時,內部會包含一個默認配置的集合。若我們想更改這些配置,便可以通過WebSettings裏的方法來進行設置。
WebSettings對象可以通過WebView.getSettings()獲得,它的生命週期是與它的WebView本身息息相關的,如果WebView被銷燬了,那麼任何由WebSettings調用的方法也同樣不能使用。


獲取WebSettings對象


WebSettings常用方法

  • (幾乎所有的set方法都有相應的get方法,這裏就只介紹set了。另,所有未寫方法返回值類型的皆爲空類型

  • setJavaScriptEnabled(boolean flag):設置WebView是否可以運行JavaScript。 

  • setJavaScriptCanOpenWindowsAutomatically(boolean flag):設置WebView是否可以由JavaScript自動打開窗口,默認爲false,通常與JavaScript的window.open()配合使用。 



  • setAllowFileAccess(boolean allow):啓用或禁用WebView訪問文件數據。 

  • setBlockNetworkImage(boolean flag):禁止或允許WebView從網絡上加載圖片。需要注意的是,如果設置是從禁止到允許的轉變的話,圖片數據並不會在設置改變後立刻去獲取,而是在WebView調用reload()的時候纔會生效。
    這個時候,需要確保這個app擁有訪問Internet的權限,否則會拋出安全異常。
    通常沒有禁止圖片加載的需求的時候,完全不用管這個方法,因爲當我們的app擁有訪問Internet的權限時,這個flag的默認值就是false。 

  • setSupportZoom(boolean support):設置是否支持縮放。 

  • setBuiltInZoomControls(boolean enabled):顯示或不顯示縮放按鈕(wap網頁不支持)。 

  • setSupportMultipleWindows(boolean support):設置WebView是否支持多窗口。 

  • setLayoutAlgorithm(WebSettings.LayoutAlgorithm l):指定WebView的頁面佈局顯示形式,調用該方法會引起頁面重繪。默認值爲LayoutAlgorithm#NARROW_COLUMNS。 

  • setNeedInitialFocus(boolean flag):通知WebView是否需要設置一個節點獲取焦點當WebView#requestFocus(int,android.graphics.Rect)被調用時,默認爲true。 


  • setAppCacheEnabled(boolean flag):啓用或禁用應用緩存。 

  • setAppCachePath(String appCachePath):設置應用緩存路徑,這個路徑必須是可以讓app寫入文件的。該方法應該只被調用一次,重複調用會被無視~ 

  • setCacheMode(int mode)用來設置WebView的緩存模式。當我們加載頁面或從上一個頁面返回的時候,會按照設置的緩存模式去檢查並使用(或不使用)緩存。 
    緩存模式有四種: 
    1. LOAD_DEFAULT:默認的緩存使用模式。在進行頁面前進或後退的操作時,如果緩存可用並未過期就優先加載緩存,否則從網絡上加載數據。這樣可以減少頁面的網絡請求次數。 
    2. LOAD_CACHE_ELSE_NETWORK:只要緩存可用就加載緩存,哪怕它們已經過期失效。如果緩存不可用就從網絡上加載數據。 
    3. LOAD_NO_CACHE:不加載緩存,只從網絡加載數據。 
    4. LOAD_CACHE_ONLY:不從網絡加載數據,只從緩存加載數據。 
    通常我們可以根據網絡情況將這幾種模式結合使用,比如有網的時候使用LOAD_DEFAULT,離線時使用LOAD_CACHE_ONLY、LOAD_CACHE_ELSE_NETWORK,讓用戶不至於在離線時啥都看不到。 


  • setDatabaseEnabled(boolean flag):啓用或禁用數據庫緩存。 

  • setDomStorageEnabled(boolean flag):啓用或禁用DOM緩存。 



  • setUserAgentString(String ua):設置WebView的UserAgent值。 

  • setDefaultEncodingName(String encoding):設置編碼格式,通常都設爲“UTF-8”。 

  • setStandardFontFamily(String font):設置標準的字體族,默認“sans-serif”。 

  • setCursiveFontFamily:設置草書字體族,默認“cursive”。 

  • setFantasyFontFamily:設置CursiveFont字體族,默認“cursive”。 

  • setFixedFontFamily:設置混合字體族,默認“monospace”。 

  • setSansSerifFontFamily:設置梵文字體族,默認“sans-serif”。 

  • setSerifFontFamily:設置襯線字體族,默認“sans-serif” 

  • setDefaultFixedFontSize(int size):設置默認填充字體大小,默認16,取值區間爲[1-72],超過範圍,使用其上限值。 

  • setDefaultFontSize(int size):設置默認字體大小,默認16,取值區間[1-72],超過範圍,使用其上限值。 

  • setMinimumFontSize:設置最小字體,默認8. 取值區間[1-72],超過範圍,使用其上限值。 

  • setMinimumLogicalFontSize:設置最小邏輯字體,默認8. 取值區間[1-72],超過範圍,使用其上限值。 


    以上就是一些WebSettings的常用方法,具體的使用以及一些緩存的問題會在接下來的代碼以及文章中有更加直觀的說明。 



WebViewClient


從名字上不難理解,這個類就像WebView的委託人一樣,是幫助WebView處理各種通知和請求事件的,我們可以稱他爲WebView的“內政大臣”。

  • onLoadResource(WebView view, String url):該方法在加載頁面資源時會回調,每一個資源(比如圖片)的加載都會調用一次。 

  • onPageStarted(WebView view, String url, Bitmap favicon):該方法在WebView開始加載頁面且僅在Main frame loading(即整頁加載)時回調,一次Main frame的加載只會回調該方法一次。我們可以在這個方法裏設定開啓一個加載的動畫,告訴用戶程序在等待網絡的響應。 

  • onPageFinished(WebView view, String url):該方法只在WebView完成一個頁面加載時調用一次(同樣也只在Main frame loading時調用),我們可以可以在此時關閉加載動畫,進行其他操作。 

  • onReceivedError(WebView view, WebResourceRequest request, WebResourceError error):該方法在web頁面加載錯誤時回調,這些錯誤通常都是由無法與服務器正常連接引起的,最常見的就是網絡問題。 
    這個方法有兩個地方需要注意: 
    1.這個方法只在與服務器無法正常連接時調用,類似於服務器返回錯誤碼的那種錯誤(即HTTP ERROR),該方法是不會回調的,因爲你已經和服務器正常連接上了(全怪官方文檔(︶^︶));
    2.這個方法是新版本的onReceivedError()方法,從API23開始引進,與舊方法onReceivedError(WebView view,int errorCode,String description,String failingUrl)不同的是,新方法在頁面局部加載發生錯誤時也會被調用(比如頁面裏兩個子Tab或者一張圖片)。這就意味着該方法的調用頻率可能會更加頻繁,所以我們應該在該方法裏執行儘量少的操作。 

  • onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse):上一個方法提到onReceivedError並不會在服務器返回錯誤碼時被回調,那麼當我們需要捕捉HTTP ERROR並進行相應操作時應該怎麼辦呢?API23便引入了該方法。當服務器返回一個HTTP ERROR並且它的status code>=400時,該方法便會回調。這個方法的作用域並不侷限於Main Frame,任何資源的加載引發HTTP ERROR都會引起該方法的回調,所以我們也應該在該方法裏執行儘量少的操作,只進行非常必要的錯誤處理等。 

  • onReceivedSslError(WebView view, SslErrorHandler handler, SslError error):當WebView加載某個資源引發SSL錯誤時會回調該方法,這時WebView要麼執行handler.cancel()取消加載,要麼執行handler.proceed()方法繼續加載(默認爲cancel)。需要注意的是,這個決定可能會被保留並在將來再次遇到SSL錯誤時執行同樣的操作。 

  • WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request):當WebView需要請求某個數據時,這個方法可以攔截該請求來告知app並且允許app本身返回一個數據來替代我們原本要加載的數據。 
    比如你對web的某個js做了本地緩存,希望在加載該js時不再去請求服務器而是可以直接讀取本地緩存的js,這個方法就可以幫助你完成這個需求。你可以寫一些邏輯檢測這個request,並返回相應的數據,你返回的數據就會被WebView使用,如果你返回null,WebView會繼續向服務器請求。 

  • boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request):哈~ 終於到了這個方法,在最開始的基礎演示時我們用到了這個方法。從實踐中我們知道,當我們沒有給WebView提供WebViewClient時,WebView如果要加載一個url會向ActivityManager尋求一個適合的處理者來加載該url(比如系統自帶的瀏覽器),這通常是我們不想看到的。於是我們需要給WebView提供一個WebViewClient,並重寫該方法返回true來告知WebView url的加載就在app中進行。這時便可以實現在app內訪問網頁。 


  • onScaleChanged(WebView view, float oldScale, float newScale):當WebView得頁面Scale值發生改變時回調。 

  • boolean shouldOverrideKeyEvent(WebView view, KeyEvent event):默認值爲false,重寫此方法並return true可以讓我們在WebView內處理按鍵事件。 



WebChromeClient


如果說WebViewClient是幫助WebView處理各種通知、請求事件的“內政大臣”的話,那麼WebChromeClient就是輔助WebView處理Javascript的對話框,網站圖標,網站title,加載進度等偏外部事件的“外交大臣”。

  • onProgressChanged(WebView view, int newProgress):當頁面加載的進度發生改變時回調,用來告知主程序當前頁面的加載進度。 


  • onReceivedIcon(WebView view, Bitmap icon):用來接收web頁面的icon,我們可以在這裏將該頁面的icon設置到Toolbar。 

  • onReceivedTitle(WebView view, String title):用來接收web頁面的title,我們可以在這裏將頁面的title設置到Toolbar。 


    以下兩個方法是爲了支持web頁面進入全屏模式而存在的(比如播放視頻),如果不實現這兩個方法,該web上的內容便不能進入全屏模式。

  • onShowCustomView(View view, WebChromeClient.CustomViewCallback callback):該方法在當前頁面進入全屏模式時回調,主程序必須提供一個包含當前web內容(視頻 or Something)的自定義的View。 

  • onHideCustomView():該方法在當前頁面退出全屏模式時回調,主程序應在這時隱藏之前show出來的View。 


  • Bitmap getDefaultVideoPoster():當我們的Web頁面包含視頻時,我們可以在HTML裏爲它設置一個預覽圖,WebView會在繪製頁面時根據它的寬高爲它佈局。而當我們處於弱網狀態下時,我們沒有比較快的獲取該圖片,那WebView繪製頁面時的gitWidth()方法就會報出空指針異常~ 於是app就crash了。。
    這時我們就需要重寫該方法,在我們尚未獲取web頁面上的video預覽圖時,給予它一個本地的圖片,避免空指針的發生。 

  • View getVideoLoadingProgressView():重寫該方法可以在視頻loading時給予一個自定義的View,可以是加載圓環 or something。 



  • boolean onJsAlert(WebView view, String url, String message, JsResult result):處理Javascript中的Alert對話框。 

  • boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result):處理Javascript中的Prompt對話框。 

  • boolean onJsConfirm(WebView view, String url, String message, JsResult result):處理Javascript中的Confirm對話框 



  • boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams):該方法在用戶進行了web上某個需要上傳文件的操作時回調。我們應該在這裏打開一個文件選擇器,如果要取消這個請求我們可以調用filePathCallback.onReceiveValue(null)並返回true。 



  • onPermissionRequest(PermissionRequest request):該方法在web頁面請求某個尚未被允許或拒絕的權限時回調,主程序在此時調用grant(String [])deny()方法。如果該方法沒有被重寫,則默認拒絕web頁面請求的權限。 

  • onPermissionRequestCanceled(PermissionRequest request):該方法在web權限申請權限被取消時回調,這時應該隱藏任何與之相關的UI界面。 


JS與WebView交互


既然嗨鳥應用大行其道,那麼毫無疑問Android與JavaScript的交互我們也必須瞭解清楚,下面來介紹一下JavaScript與Android是如何互相調用的。 


利用WebView調用網頁上的JavaScript代碼

在WebView中調用Js的基本格式爲webView.loadUrl("javascript:methodName(parameterValues)"); 

現有以下這段JavaScript代碼


1. WebView調用JavaScript無參無返回值函數    

2. WebView調用JavScript有參無返回值函數


3. WebView調用JavaScript有參數有返回值的函數




JavaScript通過WebView調用Java代碼

從API19開始,Android提供了@JavascriptInterface對象註解的方式來建立起Javascript對象和Android原生對象的綁定,提供給JavScript調用的函數必須帶有@JavascriptInterface。

演示一 JavaScript調用Android Toast方法

1. 編寫Java原生方法並用使用@JavascriptInterface註解


2.註冊JavaScriptInterface


addJavascriptInterface的作用是把this所代表的類映射爲JavaScript中的android對象。

3.編寫JavaScript代碼




演示二 JavaScript調用有返回值的Java方法

1.定義一個帶返回值的Java方法,並使用@JavaInterface:



2.添加JavaScript的映射



3.通過JavaScript調用Java方法

以上就是Js與WebView交互的一些介紹,希望能對你有幫助。 




WebView加載優化




當WebView的使用頻率變得頻繁的時候,對於其各方面的優化就變得逐漸重要了起來。可以知道的是,我們每加載一個 H5頁面,都會有很多的請求。除了HTML主URL自身的請求外,HTML外部引用的 JS、CSS、字體文件、圖片都是一個個獨立的HTTP 請求,雖然請求是併發的,但當網頁整體數量達到一定程度的時候,再加上瀏覽器解析、渲染的時間,Web整體的加載時間變得很長。同時請求文件越多,消耗的流量也會越多。那麼對於加載的優化就變得非常重要,這方面的經驗我也沒有什麼別的,大概三個方面:


一個,就是資源本地化的問題

首先可以明確的是,以目前的網絡條件,通過網絡去服務器獲取資源的速度是遠遠比不上從本地讀取的。談論各種優化策略其實恰恰忽略了“需要加載”纔是阻擋速度提升的最大絆腳石。所以我們的思路一,就是將一些較重的資源比如js、css、圖片甚至HTML本身進行本地化處理,在每次加載到這些資源的時候,從本地讀取進行加載,可以簡單記憶爲“存·取·更”

具體實現思路爲:

  1. “存”——將上述重量級資源打包進apk文件,每次加載相應文件時時從本地取即可。也可不打包,在第一次加載時以及接下來的若干間隔時間裏動態下載存儲,將所有的資源文件都存在Android的asset目錄下;

  2. “取”——重寫WebViewClient的WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)方法,通過一定的判別方法(例如正則表達式)攔截相應的請求,從本地讀取相應資源並返回;

  3. “更”——建立起Cache Control機制,定期或使用API通知的形式控制本地資源的更新,保證本地資源是最新和可用的。


這裏附上一篇博客鏈接,非常棒可供參考:

http://tutorials.jenkov.com/android/android-web-apps-using-android-webview.html#caching-web-resources-in-the-android-device


第二個,就是緩存的問題

倘若你不採用或不完全採用第一條資源本地化的思路,那麼你的WebView緩存是必須要開啓的(雖然這一思路和第一條有重合的地方)。


在網絡正常時,採用默認緩存策略,在緩存可獲取並且沒有過期的情況下加載緩存,否則通過網絡獲取資源以減少頁面的網絡請求次數。 
這裏值得提起的是,我們經常在app裏用WebView展示頁面時,並不想讓用戶覺得他是在訪問一個網頁。因爲倘若我們的app裏網頁非常多,而我們給用戶的感覺又都像在訪問網頁的話,我們的app便失去了意義。(我的意思是爲什麼用戶不直接使用瀏覽器呢?) 
所以這時,離線緩存的問題就值得我們注意。我們需要讓用戶在沒有網的時候,依然能夠操作我們的app,而不是面對一個和瀏覽器裏的網絡錯誤一樣的頁面,哪怕他能進行的操作十分有限。 
這裏我的思路是,在開啓緩存的前提下,WebView在加載頁面時檢測網絡變化,倘若在加載頁面時用戶的網絡突然斷掉,我們應當更改WebView的緩存策略。


既然有緩存,就要有緩存控制,與一相似的是我們也要建立緩存控制機制,定期或接受服務器通知來進行緩存的清空或更新。


第三個,就是延遲加載和執行js

在WebView中,onPageFinished()的回調意味着頁面加載的完成。但該方法會在JavScript腳本執行完成後纔會觸發,倘若我們要加載的頁面使用了JQuery,會在處理完DOM對象,執行完$(document).ready(function() {})後纔會渲染並顯示頁面。這是不可接受的,所以我們需要對Js進行延遲加載,當然這部分是Web前端的工作。


如果說還有什麼

那就是JsBridge一律不得濫用,這個對頁面加載的完成速度是有很大影響的,倘若一個頁面很多操作都通過JSbridge來控制,再怎麼優化也無濟於事(因爲畢竟有那麼多操作要實際執行)。同時要注意的是,不管你是否對資源進行緩存,都請將資源在服務器端進行壓縮。因爲無論是資源的獲取和更新,都是要從服務器獲取的,所以對於資源文件的壓縮其實是最直接也最應該做的事情之一,但是一般服務器端都會做好,所以主要就是上面這三件事。 


駕照考試


介紹了這麼多,希望能對你有點幫助。接下來時純實戰時間,我會將上面所介紹的很多知識點在接下來的代碼裏實際應用一遍,希望能夠帶給你更加直觀的使用感受。



Java部分


效果圖:



上路


好了,到此爲止你已閱讀完所有的指南,送你一輛車,上路吧。 



參考文章


Android官方文檔

https://developer.android.com/guide/index.html


Android Web Apps Using Android WebView

http://tutorials.jenkov.com/android/android-web-apps-using-android-webview.html


史上最全WebView詳解

https://yq.aliyun.com/articles/32559


H5 緩存機制淺析 移動端 Web 加載性能優化|Bugly社區

http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=267


WebView加載速度優化

http://www.jianshu.com/p/427600ca2107


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章