Android WebView - 詳解

Android WebView - 詳解

前言

結合H5頁面開發的App日漸多了起來,而WebView正是Html與Native的紐帶,今天就藉着一個新的項目需求順便做一下WebView的知識總結,如有錯漏,懇請大家指點指點。(項目需求:將適配好的網頁打包成App,並能夠調用系統攝像頭進行二維碼識別、拍照或是選擇本地圖片上傳、獲取用戶位置等)

WebView 小科普

官方文檔

Class Overview

A View that displays web pages. This class is the basis upon which you can roll

your own web browser or simply display some online content within your Activity.

It uses the WebKit rendering engine to display web pages and includes methods

to navigate forward and backward through a history, zoom in and out, perform text searches and more.

理解:WebView是一個顯示網頁的一個View,基本應用於瀏覽器或是Activity中網頁的簡單顯示。使用了WebKit渲染引擎實現一系列神奇的功能。

Basic usage

By default, a WebView provides no browser-like widgets, does not enable JavaScript and web page errors are ignored.

理解:默認情況,WebView並沒有開啓對JavaScript的支持,僅起展示作用,因此,我們需要進一步配置WebView才能滿足各種各樣的需求。

WebView基本使用

在 AndroidManifest.xml 中添加聯網權限(如果webView需要聯網的話,僅加載本地html、js文件則不需要添加聯網權限)

 <uses-permission android:name="android.permission.INTERNET" />  
<!--另外附上一些可能會用到的權限:-->  
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
<!--獲取網絡狀態權限(情景:WebView聯網前,應檢查當前網絡狀態)-->  
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  
<!--通過WiFi或移動基站的方式獲取用戶錯略的經緯度信息-->  
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />  
<!--通過GPS芯片接收衛星的定位信息權限(情景:結合HTML5使用Geolocation API獲取位置時)-->  
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
<!--讀寫存儲權限(情景:拍照/選擇圖片,涉及圖片讀取、編輯、壓縮等功能時)--> 

一些 WebSettings 常用配置(其實還有許多沒有列出來,大家可以利用IDE,點擊進入方法,查看源碼繼續發掘)

WebSettings  mWebSetting = mWebView.getSettings();  //獲取WebSetting  
setJavaScriptEnabled(true);//讓WebView支持JavaScript  
setDomStorageEnabled(true);//啓用H5 DOM API (默認false)  
setDatabaseEnabled(true);//啓用數據庫api(默認false)可結合 setDatabasePath 設置路徑  
setCacheMode(WebSettings.LOAD_DEFAULT)//設置緩存模式  
setAppCacheEnabled(true);//啓用應用緩存(默認false)可結合 setAppCachePath 設置緩存路徑  
setAppCacheMaxSize()//已過時,高版本API上,系統會自行分配  
setPluginsEnabled(true);  //設置插件支持  
setRenderPriority(RenderPriority.HIGH);  //提高渲染的優先級  
setUseWideViewPort(true);  //將圖片調整到適合webview的大小  
setLoadWithOverviewMode(true); // 縮放至屏幕的大小  
setSupportZoom(true);  //支持縮放,默認爲true  
setBuiltInZoomControls(true); //設置內置的縮放控件(若SupportZoom爲false,該設置項無效)  
setDisplayZoomControls(false); //隱藏原生的縮放控件  
setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); //支持內容重新佈局  
supportMultipleWindows();  //支持多窗口  
setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  //關閉webview中緩存  
setAllowFileAccess(true);  //設置可以訪問文件  
setNeedInitialFocus(true); //當webview調用requestFocus時爲webview設置節點  
setJavaScriptCanOpenWindowsAutomatically(true); //支持通過JS打開新窗口  
setLoadsImagesAutomatically(true);  //自動加載圖片  
setDefaultTextEncodingName("utf-8");//設置編碼格式

//實際應用中,一般配置即可滿足基本需求  
mWebSettings.setSupportZoom(true);  
mWebSettings.setJavaScriptEnabled(true);  
mWebSettings.setLoadWithOverviewMode(true);  
mWebSettings.setUseWideViewPort(true);  
mWebSettings.setDefaultTextEncodingName("utf-8");  
mWebSettings.setLoadsImagesAutomatically(true); 
 

加載網頁、本地、assets中的html頁面基本方式

//網頁  
private static final String URL_NET = "http://www.google.com"; // 記得加 "http://"  
//assets 中的 html 資源  
private static final String URL_LOCAL ="file:///android_asset/xxx.html路徑";  
//SD 卡中的 html 資源  
private static final String URL_SD_CARD ="content://com.android.htmlfileprovider/mnt/sdcard/xxx.html";  
mWebView.loadUrl(URL_NET);  
mWebView.loadUrl(URL_LOCAL);  
mWebView.loadUrl(URL_SD_CARD);  

WebViewClient 與 WebChromeClient

WebViewClient與WebChromeClient的區別

  • WebViewClient 用於幫助WebView處理各種通知、請求事件
WebViewClient 常用方法 說明
shouldOverrideUrlLoading 加載時調用,可捕獲url
onPageStart 開始加載時調用(可以設置加載中提示)
onPageFinish 加載完成時調用(無法打開也是完成的一種,在這裏取消加載提示顯示)
onReceiveError 接收到錯誤信息時調用(通常在該方法中處理404之類的加載錯誤,但這裏有點坑,API23中,在低於API23的設備上運行時,該方法失效,不調用我猜原因可能是: 新版的onReceiveError不再接收網頁連接的錯誤,而是接收WebView自身運行出現的錯誤,另外有onReceivedHttpError方法來接收(然而在我的測試中,該方法還是未能接收到404錯誤)。可暫時用API23過時的方法“onReceivedError(WebView view, int errorCode, String description, String failingUrl)”替代新版本中的“onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)”;)傳送門:stackoverflow Q&A
  • WebChromeClient 用於輔助 WebView處理Javascript的對話框,網站圖標、Title、位置、加載進度等信息
WebChromeClient 常用方法 說明
onProgressChanged 加載進度
onReceivedTitle、onReceivedIcon 獲取網頁標題、圖標
onGeolocationPermissionsShowPrompt 頁面發起GEO定位請求時調用
WebChromeClient源碼中對各種方法都有詳細解析,需要用到的時候查一下即可

兩者用法都是簡單的set方法

mWebView.setWebViewClient(mWebViewClient);
mWebView.setChromeClient(mWebChromeClient);

WebView與Javascript交互

  • 前提條件:setJavaScriptEnabled(true)

  • 調用js方法

    mWebView.loadUrl(“javascript: 方法名(’”+參數+"’)");

  • js調用android中方法

    4.2及之前版本該方法存在漏洞:Android WebView的Js對象注入漏洞解決方案、JS與WebView交互存在的一些問題 :

    引用”漏洞描述”:
    1,WebView添加了JavaScript對象,並且當前應用具有讀寫SDCard的權限,也就是:android.permission.WRITE_EXTERNAL_STORAGE
    2,JS中可以遍歷window對象,找到存在“getClass”方法的對象的對象,然後再通過反射的機制,得到Runtime對象,然後調用靜態方法來執行一些命令,比如訪問文件的命令.
    3,再從執行命令後返回的輸入流中得到字符串,就可以得到文件名的信息了。然後想幹什麼就幹什麼,好危險。

//理解:該方法可以讓js控制app,很強勢,但在API17(4.2)及之前的版本存在安全問題
 addJavascriptInterface(Object object, String name);

 //使用方法
 mWebView.addJavascriptInterface(MethodObject,"name");

 //還需要寫一個方法類
 class MethodObject extends Object {
    //無參函數,js中通過:var str = window.name.HtmlcallJava(); 獲取到
    @JavascriptInterface
    public String HtmlcallJava() {
        return "Html call Java";
    }

    //有參函數,js中通過:window.jsObj.HtmlcallJava2("IT-homer blog");
    @JavascriptInterface
    public String HtmlcallJava2(final String param) {
        return "Html call Java : " + param;
    }
}

WebView小技巧

/**  
* 捕獲Url  
* 多頁面在同一個WebView打開  
**/  
@Override  
public boolean shouldOverrideUrlLoading(WebView view, String url) {  
if(url.equal("需要捕獲的url")){  
// 捕獲後的操作  
// 前面提到的項目需求中,調用系統攝像頭識別二維碼、拍照、選擇本地圖片等功能  
// 均可該方式簡單實現(當然addJavascriptInterface方式也能達到相同效果)  
return true;  
}  
view.loadUrl(url);//該方法可讓多頁面在同一個WebView中打開(不用新建Activity或是調用瀏覽器)  
return true;  
}  
//我們再回頭看看該方法的官方API,會發現上面的方法多少還是有點坑(重定向問題,出現原理)  
/**  
* Give the host application a chance to take over the control when a new  
* url is about to be loaded in the current WebView. 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.  
**/  
  
//該方法爲應用提供處理新url的機會,如果WebView沒有設置WebViewClient,WebView會調用系統來找到合適應用來處理該url;而如果設置了WebViewClient,該方法返回true說明該url由應用自行處理,而false則交給WebView自動處理。  
  
//那麼,問題來了:上面方法中,我們returne的是true,而處理代碼是讓WebView直接loadUrl(不管什麼情況都是直接loadUrl,並把該url加入歷史記錄),如果該url會重定向到其他url,如果調用了goBack,返回到該url,而該url又重定向到另外一個url,造成goBack失敗。  
  
//解決方式:將處理重定向的url交給webView本身,webView能自行判斷url是否爲重定向url,能夠確保歷史記錄準確性,自身跳轉則需要另想辦法:  
//1. 與前端人員協商能夠去掉重定向url?  
//2. 建立自身的歷史棧,捨棄goBack()方法,移除重定向url與重定向後的url,根據需求自行進行loadUrl(需要思考一個合理的跳轉邏輯)  
//3. 建立自身的歷史棧,與前端配合,提供js函數判斷是否爲重定向url,捕獲url調用js函數,若爲重定向url則作過濾處理,則不加入歷史棧  
public boolean shouldOverrideUrlLoading(WebView view, String url){  
return false;  
} 
/**  
* 返回上一瀏覽頁面  
**/  
public boolean onKeyDown(int keyCode, KeyEvent event) {  
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {  
mWebView.goBack();  
return true;  
}  
return super.onKeyDown(keyCode, event);  
}  
// WebClient 中 onReceivedError 的舊方法(API23 已過時)  
@Override  
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
showErrorTips();// 顯示錯誤頁面/提示(大家可以將 WebView 錯誤頁面替換掉、結合 mWebView.reload 實現點擊重連)  
}  

WebView 進階

WebView 內存泄漏問題

Android WebView Memory Leak

WebView解析網頁時會申請Native堆內存用於保存頁面元素,當頁面較複雜時會有很大的內存佔用。如果頁面包含圖片,內存佔用會更嚴重。並且打開新頁面時,爲了能快速回退,之前頁面佔用的內存也不會釋放。有時瀏覽十幾個網頁,都會佔用幾百兆的內存。這樣加載網頁較多時,會導致系統不堪重負,最終強制關閉應用,也就是出現應用閃退或重啓。

由於佔用的都是Native堆內存,所以實際佔用的內存大小不會顯示在常用的DDMS Heap工具中(這裏看到的只是Java虛擬機分配的內存,一般即使Native堆內存已經佔用了幾百兆,這裏顯示的還只是幾兆或十幾兆)。只有使用adb shell中的一些命令比如dumpsys meminfo 包名,或者在程序中使用Debug.getNativeHeapSize()才能看到。

據說由於WebView的一個BUG,即使它所在的Activity(或者Service)結束也就是onDestroy()之後,或者直接調用WebView.destroy()之後,它所佔用這些內存也不會被釋放。

解決這個問題最直接的方法是:把使用了WebView的Activity(或者Service)放在單獨的進程裏。然後在檢測到應用佔用內存過大有可能被系統幹掉或者它所在的Activity(或者Service)結束後,調用System.exit(0),主動Kill掉進程。由於系統的內存分配是以進程爲準的,進程關閉後,系統會自動回收所有內存。

WebView 緩存機制

推薦文章

Android WebView緩存機制詳解深入探究webView的緩存機制

經過一番搜索得來的結果:

  1. WebView緩存分爲:頁面緩存和數據緩存。頁面緩存指加載網頁時,對頁面或資源數據的緩存。一般使用RE管理器進入目錄: “/data/data/(packageName)/cache/org.chromium.android_webview“可看到; 數據緩存又分爲 AppCache 與 DOM Storage 。AppCache可以有選擇地緩存我們所想要緩存的東西;DOM Storage 則是HTML5的一個緩存機制,常用於存儲簡單的表單數據,關於DOM Storage,詳情可學習參考下 “淺談HTML5 的DOM Storage機制” 一文。
  2. webView的緩存模式:
    • LOAD_CACHE_ONLY: 不使用網絡,只讀取本地緩存數據
    • LOAD_DEFAULT: 根據cache-control決定是否從網絡上取數據。
    • LOAD_CACHE_NORMAL: API level 17中已經廢棄, 從API level 11開始作用同LOAD_DEFAULT模式
    • LOAD_NO_CACHE: 不使用緩存,只從網絡獲取數據.
    • LOAD_CACHE_ELSE_NETWORK,只要本地有,無論是否過期,或者no-cache,都使用緩存中的數據。
  3. 將緩存路徑轉移到外置sd卡 查看 Context 類API 發現這樣一個方法,重寫該方法即可(注意賦予相關權限,但Android 4.4 上權限限制,會使該方法失效) 另外趁機附上:Android 外部存儲權限分析(譯)Android存儲訪問及目錄
File extStorageAppCachePath = null;  
public File getCacheDir() {  
//讀寫權限判斷  
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {  
//獲取外置存儲路徑,生成相應緩存路徑  
File externalStorageDir = Environment.getExternalStorageDirectory();  
if (externalStorageDir != null) {  
extStorageAppCachePath = new File(externalStorageDir.getAbsolutePath() +  
File.separator + "Android" + File.separator + "data" + File.separator + getPackageName() + File.separator + "webViewCache");  
//路徑不存在,創建  
if (!extStorageAppCachePath.exists()) {  
if (!extStorageAppCachePath.mkdirs()) {  
//創建路徑失敗  
extStorageAppCachePath = null;  
return super.getCacheDir();  
} else {  
//成功創建,返回路徑  
if (extStorageAppCachePath != null) {  
return extStorageAppCachePath;  
}  
}  
} else {  
//路徑已存在,直接返回  
if (extStorageAppCachePath != null) {  
return extStorageAppCachePath;  
}  
}  
}  
}  
return super.getCacheDir();  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章