編輯:關於Android編程
結合H5頁面開發的App日漸多了起來,而WebView正是Html與Native的紐帶,今天就借著一個新的項目需求順便做一下WebView的知識總結,如有錯漏,懇請大家指點指點。(項目需求:將適配好的網頁打包成App,並能夠調用系統攝像頭進行二維碼識別、拍照或是選擇本地圖片上傳、獲取用戶位置等)
官方文檔
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才能滿足各種各樣的需求。
一些 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);
mWebView.setWebViewClient(mWebViewClient); mWebView.setChromeClient(mWebChromeClient);
mWebView.loadUrl("javascript: 方法名('"+參數+"')"); js調用android中方法
4.2及之前版本該方法存在漏洞:
引用”漏洞描述”:
1,WebView添加了JavaScript對象,並且當前應用具有讀寫SDCard的權限,也就是:android.permission.WRITE_EXTERNAL_STORAGE
2,JS中可以遍歷window對象,找到存在“getClass”方法的對象的對象,然後再通過反射的機制,得到Runtime對象,然後調用靜態方法來執行一些命令,比如訪問文件的命令.
3,再從執行命令後返回的輸入流中得到字符串,就可以得到文件名的信息了。然後想干什麼就干什麼,好危險。
/**
* This method can be used to allow JavaScript to control the host
* application. This is a powerful feature, but also presents a security
* risk for apps targeting{@link android.os.Build.VERSION_CODES#JELLY_BEAN}
* or earlier.
* /
//理解:該方法可以讓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 緩存機制
經過一番搜索得來的結果:
1. WebView緩存分為:頁面緩存和數據緩存。頁面緩存指加載網頁時,對頁面或資源數據的緩存。一般使用RE管理器進入目錄: “/data/data/(packageName)/cache/org.chromium.android_webview“可看到;
數據緩存又分為 AppCache 與 DOM Storage 。AppCache可以有選擇地緩存我們所想要緩存的東西;DOM Storage 則是HTML5的一個緩存機制,常用於存儲簡單的表單數據。
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 上權限限制,會使該方法失效)
/**
* Returns the absolute path to the application specific cache directory
* on the filesystem. These files will be ones that get deleted first when the
* device runs low on storage.
* There is no guarantee when these files will be deleted.
*
* Note: you should not rely on the system deleting these
* files for you; you should always have a reasonable maximum, such as 1 MB,
* for the amount of space you consume with cache files, and prune those
* files when exceeding that space.
*
* @return The path of the directory holding application cache files.
*
* @see #openFileOutput
* @see #getFileStreamPath
* @see #getDir
*/
public abstract File getCacheDir();
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();
}
結束語
本文主要是對自己學習WebView的過程、應用WebView遇到的一些問題,結合強大的網絡資源總結而來,如果錯漏,懇請指教,希望能給大家提供小小的幫助,在分享技術過程中,提升、成長!(有很長一段時間沒有發布過博客,貴在堅持,貴在堅持!)
一、簡介這個題目是別人面試UC優視集團Android逆向工程師一職位的面試題,相比較前面的面試題1,增加了一些難度。 二、題目分析1.使
一. 百度地圖城市定位和POI搜索知識 上一篇文章百度地圖開發(一)中講述了如何申請百度APIKey及解決顯示空白網格的問題.該篇文章主要講述如何定位城市位置、定位自己的
Android6.0 SystemUI啟動簡析及圖標顯示刷新。Android系統的SystemUI包含狀態欄、導航欄、快捷設置、通知欄及鎖屏界面等等;主要流程是從Syst
本節引言: 本節我們介紹的是Vibrator(振動器),是手機自帶的振動器,別去百度直接搜針振動器,因為 你的搜索結果可能是如圖所示的神秘的道具,或者其他神秘