最近一直在作web前端開發,作了預約酒店系統,後臺管理系統,小程序等,正好趁機複習一下Android的WebViewjavascript
先簡單介紹一下,Android在4.4以後採用了Chrome內核,因此咱們在開發web頁面的時候,es6的語法,css3的樣式等大可放心使用css
WebView自身的一些方法html
//方式1. 加載一個網頁:
webView.loadUrl("http://www.google.com/");
//方式2:加載apk包中的html頁面
webView.loadUrl("file:///android_asset/test.html");
//方式3:加載手機本地的html頁面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
複製代碼
正常狀況下,在WebView界面,用戶點擊返回鍵是直接退出該頁面的,着固然不是咱們想要的,咱們想要的是網頁本身的前進和後退,因此下面介紹網頁前進和後退的一些API前端
//判斷是否能夠後退
Webview.canGoBack()
//後退網頁
Webview.goBack()
//判斷是否能夠前進
Webview.canGoForward()
//前進網頁
Webview.goForward()
// 參數傳負的話表示後退,傳正值的話表示的是前進
Webview.goBackOrForward(int steps)
複製代碼
對返回鍵的監聽,來實現網頁的後退
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
複製代碼
如何防止WebView內存泄漏java
防止內存泄漏的一個原則就是:生命週期長的不要跟生命週期短的玩。爲了防止WebView不形成內存泄漏,android
override fun onDestroy() {
if (webView != null) {
webView!!.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
webView!!.clearHistory()
(webView!!.parent as ViewGroup).removeView(webView)
webView!!.destroy()
webView = null
}
super.onDestroy()
}
複製代碼
做用:對WebView進行配置和管理css3
WebSettings webSettings = webView.getSettings();
// 設置能夠與js交互,爲了防止資源浪費,咱們能夠在Activity
// 的onResume中設置爲true,在onStop中設置爲false
webSettings.setJavaScriptEnabled(true);
//設置自適應屏幕,二者合用
//將圖片調整到適合webview的大小
webSettings.setUseWideViewPort(true);
// 縮放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
//設置編碼格式
webSettings.setDefaultTextEncodingName("utf-8");
// 設置容許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//設置緩存的模式
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
複製代碼
關於緩存的設置:es6
當加載 html 頁面時,WebView會在/data/data/包名目錄下生成 database 與 cache 兩個文件夾,請求的 URL記錄保存在 WebViewCache.db,而 URL的內容是保存在 WebViewCache 文件夾下web
緩存模式以下:
//LOAD_CACHE_ONLY: 不使用網絡,只讀取本地緩存數據
//LOAD_DEFAULT: (默認)根據cache-control決定是否從網絡上取據。
//LOAD_NO_CACHE: 不使用緩存,只從網絡獲取數據.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,不管是否過時,或no-cache,都使用緩存中的數據。
複製代碼
離線加載chrome
if (NetStatusUtil.isConnected(getApplicationContext())) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根據cache-control決定是否從網絡上取數據。
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//沒網,則從本地獲取,即離線加載
}
webSettings.setDomStorageEnabled(true); // 開啓 DOM storage API 功能
webSettings.setDatabaseEnabled(true); //開啓 database storage API 功能
webSettings.setAppCacheEnabled(true);//開啓 Application Caches 功能
String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); //設置 Application Caches 緩存目錄
複製代碼
webView!!.webViewClient = object : WebViewClient() {
// 啓用WebView,而不是系統自帶的瀏覽器
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
view.loadUrl(url)
return true
}
// 頁面開始加載,咱們能夠在這裏設置loading
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
tv_start.text = "開始加載了..."
}
// 頁面加載結束,關閉loading
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
tv_end.text = "加載結束了..."
}
// 只要加載html,js,css的資源,每次都會回調到這裏
override fun onLoadResource(view: WebView?, url: String?) {
loge("onLoadResource invoked")
}
// 在這裏咱們能夠加載咱們本身的404頁面
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
loge("加載錯誤:${error.toString()}")
}
// webview默認設計是不開啓https的,下面的設置是容許使用https
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
handler?.proceed()
}
// js調用Android的方法,在這裏能夠,該方法不存在經過註解的方式的內存泄漏,可是想拿到Android的返回值的話很難,
// 能夠經過Android調用js的代碼的形式來傳遞返回值,例以下面的方式
// Android:MainActivity.java
// mWebView.loadUrl("javascript:returnResult(" + result + ")");
// JS:javascript.html
// function returnResult(result){
// alert("result is" + result);
// }
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val uri = Uri.parse(request?.url.toString())
// 通常根據scheme(協議格式) & authority(協議名)判斷(前兩個參數)
//假定傳入進來的 url = "js://webview?arg1=111&arg2=222"(同時也是約定好的須要攔截的)
if (uri.scheme == "js") {
if (uri.authority == "webview") {
toast_custom("js調用了Android的方法")
val queryParameterNames = uri.queryParameterNames
queryParameterNames.forEach {
loge(it + ":" + uri.getQueryParameter(it))
}
}
return true
}
return super.shouldOverrideUrlLoading(view, request)
}
// 攔截資源 一般用於h5的首頁頁面,將經常使用的一些資源,放到本地
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
if(request?.url.toString().contains("logo.gif")){
var inputStream: InputStream? = null
inputStream = applicationContext.assets.open("images/test.png")
return WebResourceResponse("image/png","utf-8", inputStream)
}
return super.shouldInterceptRequest(view, request)
}
}
複製代碼
注意:5.1以上默認禁止了https和http的混用,下面的設置是開啓
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
複製代碼
webView!!.webChromeClient = object : WebChromeClient() {
// 網頁加載的進度
override fun onProgressChanged(view: WebView?, newProgress: Int) {
tv_progress.text = "$newProgress%"
}
// 得到網頁的標題
override fun onReceivedTitle(view: WebView?, title: String?) {
tv_title.text = title
}
//js Alert
override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
AlertDialog.Builder(this@WebActivity)
.setTitle("JsAlert")
.setMessage(message)
.setPositiveButton("OK") { _, _ -> result?.confirm() }
.setCancelable(false)
.show()
return true
}
// js Confirm
override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
return super.onJsConfirm(view, url, message, result)
}
//js Prompt
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
return super.onJsPrompt(view, url, message, defaultValue, result)
}
}
複製代碼
Android調用js
1.經過webview的loadUrl
注意:該方式必須在webview加載完畢以後才能調用,也就是webviewClient的onPageFinished()方法回調以後,並且該方法的執行 會刷新界面,效率較低
js代碼:
function callJs(){
alert("Android 調用了 js代碼) } kotlin代碼: webView?.loadUrl("javascript:callJs()") 複製代碼
2.經過webview的evaluateJavaScript
比起第一種方法,效率更高,可是要在4.4以後才能使用
js代碼:
function callJs(){
// alert("Android 調用了 js代碼) return {name:'wfq',age:25} } kotlin代碼: webView?.evaluateJavascript("javascript:callJs()") { // 這裏直接拿到的是js代碼的返回值 toast(it) // {name:'wfq',age:25} } 複製代碼
js調用Android
1.經過webview的addJavaScriptInterface進行對象映射
咱們能夠單獨定義一個類,全部須要交互的方法能夠所有寫在這個類裏面,固然也能夠直接寫在Activity裏面,下面以直接定義在Activity裏面爲例,優勢:使用方便,缺點:存在漏洞(4.2以前),請看下面的「WebView的一些漏洞以及如何防止」
kotlin中定義被js調用的方法
@JavascriptInterface
fun hello(name: String) {
toast("你好,我是來自js的消息:$msg")
}
js代碼
function callAndroid(){
android.hello("我是js的,我來調用你了")
}
kotlin中們在webview裏面設置Android與js的代碼的映射
webView?.addJavascriptInterface(this, "android")
複製代碼
2.經過webviewClient的shouldOverrideUrlLoading的回調來攔截url
具體使用:解析該url的協議,若是監測到是預先約定好的協議,那麼就調用相應的方法。比較安全,可是使用麻煩,js獲取Android的返回值的話很麻煩,只能經過上面介紹的經過loadurl()去執行js代碼把返回值經過參數傳遞回去
首先在js中約定號協議
function callAndroid(){
// 約定的url協議爲:js://webview?name=wfq&age=24
document.location = "js://webview?name=wfq&age=24"
}
在kotlin裏面,當loadurl的時候就會回調到shouldOverrideUrlLoading()裏面
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val uri = Uri.parse(request?.url.toString())
// 通常根據scheme(協議格式) & authority(協議名)判斷(前兩個參數)
//假定傳入進來的 js://webview?name=wfq&age=24
if (uri.scheme == "js") {
if (uri.authority == "webview") {
toast_custom("js調用了Android的方法")
val queryParameterNames = uri.queryParameterNames
queryParameterNames.forEach {
loge(it + ":" + uri.getQueryParameter(it))
}
}
return true
}
return super.shouldOverrideUrlLoading(view, request)
}
複製代碼
3.經過webChromeClient的onJsAlert,onJsConfirm,onJsPrompt回調來攔截對話框
經過攔截js對話框,獲得他們的消息,而後解析便可,爲了安全,建議內容採用上面介紹的url協議, 經常使用的攔截的話就是攔截prompt,由於它能夠返回任意值,alert沒有返回值,confirm只能返回兩種類型,肯定和取消
js代碼
function clickprompt(){
var result=prompt("wfq://demo?arg1=111&arg2=222");
alert("demo " + result);
}
kotlin代碼
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
val uri = Uri.parse(message)
if (uri.scheme == "wfq") {
if (uri.authority == "demo") {
toast_custom("js調用了Android的方法")
val queryParameterNames = uri.queryParameterNames
queryParameterNames.forEach {
loge(it + ":" + uri.getQueryParameter(it))
}
// 將須要返回的值經過該方式返回
result?.confirm("js調用了Android的方法成功啦啦啦啦啦")
}
return true
}
return super.onJsPrompt(view, url, message, defaultValue, result)
}
因爲攔截了彈框,因此js代碼的alert須要處理 這裏的message即是上面代碼的返回值經過alert顯示出來的信息
override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
AlertDialog.Builder(this@WebActivity)
.setTitle("JsAlert")
.setMessage(message)
.setPositiveButton("OK") { _, _ -> result?.confirm() }
.setCancelable(false)
.show()
return true
}
複製代碼
上面三種方式的區別: addJavascriptInterface() 方便簡潔,4.0如下存在漏洞,4.0以上經過@JavascriptInterface註解修復漏洞 WebViewClient.shouldOverrideUrlLoading()回調,不存在漏洞,使用複雜,須要定義協議的約束,可是返回值的話有些麻煩,在不須要返回值的狀況下可使用這個方式 經過WebChromeClient的onJsAlerta,onJsConfirm,onJsPrompt,不存在漏洞問題,使用複雜,須要進行協議的約束,能夠返回值,能知足大多數狀況下的互調通訊
參考騰訊大神Carson_Ho的簡書 www.jianshu.com/p/3a345d27c…
webview默認開啓了密碼保存功能,在用戶輸入密碼後會彈出提示框詢問用戶是否保存密碼,保存後密碼會被明文保存在 /data/data/com.package.name/databases/webview.db 下面,手機root後能夠查看,那麼如何解決?
WebSettings.setSavePassword(false) // 關閉密碼保存提醒功能
複製代碼
addJavascriptInterface漏洞,首先先明白一點,js調用Android代碼的時候,咱們常用的是addJavascriptInterface, JS調用Android的其中一個方式是經過addJavascriptInterface接口進行對象映射,那麼Android4.2以前,既然拿到了這個對象,那麼這個對象中的全部方法都是能夠調用的,4.2以後,須要被js調用的函數加上@JavascriptInterface註解後來避免該漏洞
因此怎麼解決
對於Android 4.2之前,須要採用攔截prompt()的方式進行漏洞修復
對於Android 4.2之後,則只須要對被調用的函數以 @JavascriptInterface進行註解
複製代碼
下面來看下WebView中getSettings類的方法對 WebView 安全性的影響 setAllowFileAccess()
// 設置是否容許 WebView 使用 File 協議
// 默認設置爲true,即容許在 File 域下執行任意 JavaScript 代碼
webView.getSettings().setAllowFileAccess(true);
若是設置爲false的話,便不會存在威脅,可是,webview也沒法使用本地的html文件
複製代碼
setAllowFileAccessFromFileURLs()
// 設置是否容許經過 file url 加載的 Js代碼讀取其餘的本地文件
// 在Android 4.1前默認容許
// 在Android 4.1後默認禁止
webView.getSettings().setAllowFileAccessFromFileURLs(true);
咱們應該明確的設置爲false,禁止讀取其餘文件
複製代碼
setAllowUniversalAccessFromFileURLs()
// 設置是否容許經過 file url 加載的 Javascript 能夠訪問其餘的源(包括http、https等源)
// 在Android 4.1前默認容許(setAllowFileAccessFromFileURLs()不起做用)
// 在Android 4.1後默認禁止
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
複製代碼
h5頁面加載慢,慢的緣由:頁面渲染慢,資源加載慢
h5的緩存,資源預加載,資源攔截
h5的緩存 Android WebView自帶的緩存 1.瀏覽器緩存
根據 HTTP 協議頭裏的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段來控制文件緩存的機制
瀏覽器本身實現,我需咱們處理
複製代碼
2.App Cache
方便構建Web App的緩存,存儲靜態文件(如JS、CSS、字體文件)
WebSettings settings = getSettings();
String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
settings.setAppCachePath(cacheDirPath);
settings.setAppCacheMaxSize(20*1024*1024);
settings.setAppCacheEnabled(true);
複製代碼
3.Dom Storage
WebSettings settings = getSettings();
settings.setDomStorageEnabled(true);
複製代碼
4.Indexed Database
// 只需設置支持JS就自動打開IndexedDB存儲機制
// Android 在4.4開始加入對 IndexedDB 的支持,只需打開容許 JS 執行的開關就行了。
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);
複製代碼
資源預加載 預加載webview對象,首次初始化WebView會比第二次慢不少的緣由:初始化後,即便webview已經釋放,可是WebView的一些共享的對象依然是存在的,咱們能夠在Application裏面提早初始化一個Webview的對象,而後能夠直接loadurl加載資源
資源攔截 能夠將跟新頻率低的一些資源靜態文件放在本地,攔截h5的資源網絡請求並進行檢測,若是檢測到,就直接拿本地的資源進行替換便可
// 攔截資源 一般用於h5的首頁頁面,將經常使用的一些資源,放到本地
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
if(request?.url.toString().contains("logo.jpg")){
var inputStream: InputStream? = null
inputStream = applicationContext.assets.open("images/test.jpg")
return WebResourceResponse("image/png","utf-8", inputStream)
}
return super.shouldInterceptRequest(view, request)
}
複製代碼
在manifest的Application標籤下面使用:android:usesCleartextTraffic="true"
#保留annotation, 例如 @JavascriptInterface 等 annotation
-keepattributes *Annotation*
#保留跟 javascript相關的屬性
-keepattributes JavascriptInterface
#保留JavascriptInterface中的方法
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
#這個類是用來與js交互,因此這個類中的 字段 ,方法, 不能被混淆、全路徑名稱.類名
-keepclassmembers public class com.youpackgename.xxx.H5CallBackAndroid{
<fields>;
<methods>;
public *;
private *;
}
複製代碼
1.在WebViewActivity裏面,開啓調試
// 開啓調試
WebView.setWebContentsDebuggingEnabled(true)
複製代碼
2.chrome瀏覽器地址欄輸入 chrome://inspect
3.手機打開USB調試,打開webview頁面,點擊chrome頁面的最下面的inspect,這樣,即可以進入了web開發,看控制檯,網絡請求等