WebView
WebView是谷歌提供的一個加載H5的控件,WebView這個控件又包含四大部分:WebSettings、WebViewClient、WebChromeClient、JavascriptInterface。通過四個類,我們可以為WebView設(shè)置基礎(chǔ)功能和監(jiān)聽,下面會逐一介紹四個類的方法。除了這四部分,WebView還有自己的API,先來介紹WebView的API。
WebView的API介紹
列出WebView一些常用的API,如加載url、前進后退、清理緩存、狀態(tài)管理更新
// 加載url:
webView.loadUrl(url)
// 往請求頭header增加參數(shù)
val hashMap: HashMap<String, String> = HashMap()
hashMap["name"] = "zhangsan"
webView.loadUrl(url, hashMap)
// 加載 HTML 頁面的一小段內(nèi)容
webView.loadData("", "text/html", "utf-8")
webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
// 后退、前進:
webView.canGoBack() //是否可以后退
webView.goBack() //后退一頁面
webView.canGoForward() //是否可以前進
webView.goForward() //前進一頁面
webView.goBackOrForward(-1) //后退或前進多少步,正前負退
//清除緩存數(shù)據(jù):
// 清除網(wǎng)頁訪問留下的緩存,由于內(nèi)核緩存是全局的因此這個方法不僅僅針對webview而是針對整個應(yīng)用程序.
webView.clearCache(true)
// 清除當(dāng)前webview訪問的歷史記錄,只會webview訪問歷史記錄里的所有記錄除了當(dāng)前訪問記錄.
webView.clearHistory()
// 這個api僅僅清除自動完成填充的表單數(shù)據(jù),并不會清除WebView存儲到本地的數(shù)據(jù)。
webView.clearFormData()
//WebView的狀態(tài)
webView.onResume() // 可見狀態(tài)
webView.onPause() // 頁面失去焦點變成不可見狀態(tài)
webView.pauseTimers() // 頁面失去焦點變成不可見狀態(tài),對整個應(yīng)用的webview起作用
webView.resumeTimers() //恢復(fù)pauseTimers時的動作
webView.destroy() //銷毀
WebSettings
WebSettings類的主要作用是為H5設(shè)置一些配置功能,如常用設(shè)置是否支持JSjavaScriptEnabled、設(shè)置緩存cacheMode。如下是WebSettings的API介紹。
webSetting = webView.settings
// 告訴WebView啟用JavaScript執(zhí)行。
webSetting?.javaScriptEnabled = true
//緩存
//緩存模式
webSetting?.cacheMode = WebSettings.LOAD_DEFAULT
// 開啟 DOM storage API 功能
webSetting?.domStorageEnabled = true
//開啟 database storage API 功能
webSetting?.databaseEnabled = true
//開啟 Application Caches 功能,還需要設(shè)置路徑
webSetting?.setAppCacheEnabled(true)
webSetting?.setAppCachePath("")
//設(shè)置WebView是否應(yīng)使用其屏幕上的縮放控件和手勢支持縮放。
//可以使用{@link #setBuiltInZoomControls}設(shè)置應(yīng)使用的特定縮放機制。
//此設(shè)置不會影響使用{@link WebView#zoomIn()}和{@link WebView#zoomOut()}方法執(zhí)行的縮放。默認值為{@code true}。
webSetting?.setSupportZoom(true)
//設(shè)置WebView是否應(yīng)使用其內(nèi)置的縮放機制。內(nèi)置的縮放機制包括屏幕縮放控件(顯示在WebView的內(nèi)容上)以及使用捏合手勢控制縮放??梢允褂脅@link #setDisplayZoomControls}設(shè)置是否顯示這些屏幕上的控件。默認值為{@code false}
webSetting?.builtInZoomControls = false
// 是否支持ViewPort的meta tag屬性,
// 如果頁面有ViewPort meta tag 指定的寬度,則使用meta tag指定的值,否則默認使用寬屏的視圖窗口
webSetting?.useWideViewPort = true
// 設(shè)置WebView是否以概述模式加載頁面,即縮小內(nèi)容以適合屏幕寬度。
// 當(dāng)內(nèi)容寬度大于WebView控件的寬度時,例如{@link #getUseWideViewPort} 已啟用。默認值為{@code false}
webSetting?.loadWithOverviewMode = true
//設(shè)置渲染線程的優(yōu)先級。與其他設(shè)置不同,此設(shè)置每個過程僅需要調(diào)用一次。默認值為{@link RenderPriority#NORMAL}。
webSetting?.setRenderPriority(WebSettings.RenderPriority.HIGH)
// 設(shè)置基礎(chǔ)布局算法。這將導(dǎo)致WebView的重新布局。默認值為{@link LayoutAlgorithm#NARROW_COLUMNS}。
webSetting?.layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL
//啟用或禁用WebView中的文件訪問。
//請注意,這僅啟用或禁用文件系統(tǒng)訪問。仍然可以使用file:/// android_asset和file:/// android_res訪問資產(chǎn)和資源。
webSetting?.allowFileAccess = true
//告訴WebView在調(diào)用{@link WebView#requestFocus(int,android.graphics.Rect)}時是否需要將節(jié)點設(shè)置為具有焦點。默認值為{@code true}。
webSetting?.setNeedInitialFocus(true)
//告訴JavaScript自動打開窗口。
// 這適用于JavaScript函數(shù){@code window.open()}。默認值為{@code false}
webSetting?.javaScriptCanOpenWindowsAutomatically = true
//支持自動加載圖片
webSetting?.loadsImagesAutomatically = true
//設(shè)置解碼html頁面時使用的默認文本編碼名稱。 *默認值為“ UTF-8”。
webSetting?.defaultTextEncodingName = "utf-8"
// 設(shè)置默認字體大小。默認值為16。
webSetting?.defaultFontSize = 16
//設(shè)置最小字體大小。預(yù)設(shè)值為8。
webSetting?.minimumFontSize = 8
/**
* Webview在安卓5.0之前默認允許其加載混合網(wǎng)絡(luò)協(xié)議內(nèi)容
* 在安卓5.0之后,默認不允許加載http與https混合內(nèi)容,需要設(shè)置webview允許其加載混合網(wǎng)絡(luò)協(xié)議內(nèi)容
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSetting?.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
}
//獲取WebView是否支持多個窗口。
webSetting?.supportMultipleWindows()
在項目中一般設(shè)置這些WebSettings的API就行,當(dāng)然一些特殊需求場景可以自己另外設(shè)置。
// webSettings的通用設(shè)置
private fun initWebSettings(webView: WebView?) {
webView?.isEmpty() ?: return
val webSettings = webView.settings
webSettings.javaScriptEnabled = true //支持JS
webSettings.useWideViewPort =
true // 如果頁面有ViewPort meta tag 指定的寬度,則使用meta tag指定的值,否則默認使用寬屏的視圖窗口
webSettings.loadWithOverviewMode =
true //即縮小內(nèi)容以適合屏幕寬度。當(dāng)內(nèi)容寬度大于WebView控件的寬度時,例如{@link #getUseWideViewPort} 已啟用
webSettings.cacheMode = WebSettings.LOAD_DEFAULT //緩存模式
webSettings.domStorageEnabled = true //開啟 DOM storage API 功能
webSettings.databaseEnabled = true //開啟 database storage API 功能
webSettings.setAppCacheEnabled(true) //開啟 Application Caches 功能
webSettings.setAppCachePath(filesDir.absolutePath + "webcache") //設(shè)置 Application Caches 緩存目錄
webSettings.databasePath = filesDir.absolutePath + "webcache" //設(shè)置數(shù)據(jù)庫緩存路徑
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH) //設(shè)置渲染線程的優(yōu)先級。
webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS //適應(yīng)內(nèi)容大小
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 允許HTTPS和HTTP一起加載
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
}
// 為了處理一些WebView使用而被檢測到的漏洞
try {
// 設(shè)置WebView是否應(yīng)保存密碼。默認值為{@code true}。 @deprecated在將來的版本中將不支持在WebView中保存密碼。
webSettings.savePassword = false
// 同源繞過策略
webSettings.allowFileAccess = false
// 移除有風(fēng)險的webView系統(tǒng)隱藏接口漏洞
webView.removeJavascriptInterface("searchBoxJavaBridge_")
webView.removeJavascriptInterface("accessibility")
webView.removeJavascriptInterface("accessibilityTraversal")
} catch (e: Exception) {
e.printStackTrace()
}
//自定義UA
val ua = webSettings.userAgentString
if (ua.isNotEmpty() && ua.endsWith("自定義")) {
return
}
webSettings.userAgentString = ua + "自定義"
}
WebViewClient
WebViewClient的作用是處理各種通知和請求事件。比較常用的方法有shouldOverrideUrlLoading、onPageStarted、onPageFinished。以下是各方法講解:
class BaseWebViewClient : WebViewClient {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
//在網(wǎng)頁上的所有加載都經(jīng)過這個方法
//比如在本地WebView加載H5,而不是瀏覽器
return super.shouldOverrideUrlLoading(view, request)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
//這個事件就是開始載入頁面調(diào)用的,我們可以設(shè)定一個loading的頁面,告訴用戶程序在等待網(wǎng)絡(luò)響應(yīng)。
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
//在頁面加載結(jié)束時調(diào)用。同樣道理,我們可以關(guān)閉loading 條,切換程序動作。
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
// (報告錯誤信息,當(dāng)加載出錯的時候會回調(diào)此方法)
}
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
// 攔截替換網(wǎng)絡(luò)請求數(shù)據(jù)
return super.shouldInterceptRequest(view, request)
}
override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean {
//重寫此方法才能夠處理在瀏覽器中的按鍵事件。
return super.shouldOverrideKeyEvent(view, event)
}
override fun onLoadResource(view: WebView?, url: String?) {
super.onLoadResource(view, url)
// 在加載頁面資源時會調(diào)用,每一個資源(比如圖片)的加載都會調(diào)用一次。
}
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
super.doUpdateVisitedHistory(view, url, isReload)
//(更新歷史記錄)
}
override fun onFormResubmission(view: WebView?, dontResend: Message?, resend: Message?) {
super.onFormResubmission(view, dontResend, resend)
//(應(yīng)用程序重新請求網(wǎng)頁數(shù)據(jù))
}
override fun onReceivedHttpAuthRequest(
view: WebView?,
handler: HttpAuthHandler?,
host: String?,
realm: String?
) {
super.onReceivedHttpAuthRequest(view, handler, host, realm)
//(通知主機應(yīng)用程序WebView收到HTTP身份驗證請求。主機應(yīng)用程序可以使用提供的* {@link HttpAuthHandler}設(shè)置WebView對請求的響應(yīng)。 *默認行為是取消請求
}
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
super.onReceivedSslError(view, handler, error)
//SSL過程發(fā)生錯誤,重寫此方法可以讓webview處理https請求。
}
override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {
super.onScaleChanged(view, oldScale, newScale)
// (WebView縮放大小發(fā)生改變時調(diào)用)
}
override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) {
super.onUnhandledKeyEvent(view, event)
//(Key事件未被加載時調(diào)用)
}
}
項目中WebViewClient的通用配置:
webView?.webViewClient = CommonWebViewClient(this)
private class CommonWebViewClient(val context: Context) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
url ?: return false
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
}
} catch (e: Exception) {
return true
}
return super.shouldOverrideUrlLoading(view, url)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
// 開始Loading頁面
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
// 開始結(jié)束Loading頁面
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
// 開始結(jié)束Loading頁面
}
}
WebChromeClient
WebChromeClient是內(nèi)核處理類,主要用于網(wǎng)站的加載進度、標題、圖片文件選擇、JS彈窗
class BaseWebChromeClient : WebChromeClient {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
//當(dāng)前網(wǎng)頁加載的進度
}
override fun onReceivedTitle(view: WebView?, title: String?) {
super.onReceivedTitle(view, title)
//網(wǎng)頁title標題
}
override fun onReceivedIcon(view: WebView?, icon: Bitmap?) {
super.onReceivedIcon(view, icon)
//網(wǎng)頁圖標
}
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
//告訴客戶端顯示文件選擇器。響應(yīng)于用戶按下“選擇文件”按鈕,調(diào)用它來處理具有“文件”輸入類型的HTML表單。
// 要取消請求,請調(diào)用<code> filePathCallback.onReceiveValue(null)</ code>并返回{@code true}。
return super.onShowFileChooser(webView, filePathCallback, fileChooserParams)
}
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
super.onShowCustomView(view, callback)
// 通知主機應(yīng)用程序當(dāng)前頁面已進入全屏模式。調(diào)用之后,Web內(nèi)容將不再在WebView中呈現(xiàn),而是在{@code view}中呈現(xiàn)。
// 主機應(yīng)用程序應(yīng)將此視圖添加到配置了{@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN}標志的窗口中,以便實際全屏顯示此Web內(nèi)容。
}
override fun onHideCustomView() {
super.onHideCustomView()
// 通知主機應(yīng)用程序當(dāng)前頁面已退出全屏模式。
// 宿主應(yīng)用程序必須隱藏自定義視圖(以前傳遞給{@link #onShowCustomView(View,CustomViewCallback)onShowCustomView()}的視圖)。調(diào)用之后,Web內(nèi)容將再次在原始WebView中呈現(xiàn)
}
override fun getDefaultVideoPoster(): Bitmap? {
//webview視頻未播放時默認顯示占位圖
return super.getDefaultVideoPoster()
}
//通知主機應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code alert()}對話框。
//<p>如果此方法返回{@code false}或未被覆蓋,則默認行為是顯示一個包含警報消息的對話框并掛起JavaScript的執(zhí)行,直到關(guān)閉對話框為止。
//<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下,默認對話框?qū)⒉粫@示,并且JavaScript 執(zhí)行將被暫停。
// 該應(yīng)調(diào)用{@code JsResult.confirm()}(關(guān)閉自定義對話框時),以便可以恢復(fù)執(zhí)行JavaScript。
//<p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù),請立即調(diào)用{@code JsResult.confirm()},然后返回{@code true}。
//<p>請注意,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient},則默認對話框?qū)⒈蝗∠?,并且Javascript將立即繼續(xù)執(zhí)行。
//<p>請注意,默認對話框不會從父窗口繼承{@link android.view.Display#FLAG_SECURE}標志。
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
return super.onJsAlert(view, url, message, result)
}
// 通知主機應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code Confirm()}對話框。
//<p>如果此方法返回{@code false}或未重寫,則默認行為是顯示一個包含該消息的對話框并暫停JavaScript執(zhí)行,直到該對話框被關(guān)閉為止。
// 當(dāng)用戶按下“確認”按鈕時,默認對話框?qū)⒎祷貃@code true}到JavaScript {@code Confirm()}代碼,而當(dāng)用戶按下“ cancel”時將返回{@code false}到JavaScript代碼。 '按鈕或關(guān)閉對話框。
//<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下,默認對話框?qū)⒉粫@示,并且JavaScript
//執(zhí)行將被暫停。該應(yīng)用應(yīng)調(diào)用 自定義對話框關(guān)閉時,{@ code JsResult.confirm()}或{@code JsResult.cancel()}。
// <p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù),請立即調(diào)用{@code JsResult.confirm()}或{@code JsResult.cancel()},然后返回{@code true}。
//<p>請注意,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient},則會取消默認對話框,而默認值{@code false }將立即返回到JavaScript代碼。
//<p>請注意,默認對話框不會從父窗口繼承{@link android.view.Display#FLAG_SECURE}標志。
override fun onJsConfirm(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
return super.onJsConfirm(view, url, message, result)
}
// 通知主機應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code hint()}對話框。
// <p>如果此方法返回{@code false}或未被覆蓋,則默認行為是顯示一個包含該消息的對話框并暫停JavaScript執(zhí)行,直到該對話框被關(guān)閉為止。
// 取消該對話框后,JavaScript {@code hint()}將返回用戶鍵入的字符串,如果用戶按下“取消”按鈕,則返回null。
//<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下,默認對話框?qū)⒉粫@示,并且JavaScript 執(zhí)行將被暫停。
// 該應(yīng)用應(yīng)調(diào)用 取消自定義對話框后,{@ code JsPromptResult.confirm(result)}。
//<p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù),請立即調(diào)用{@code JsPromptResult.confirm(result)},然后返回{@code true}。
//<p>請注意,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient},則默認對話框?qū)⒈蝗∠⑶覍⒎祷貃@code null}立即添加到JavaScript代碼。
//<p>請注意,默認對話框不會從父窗口繼承{@link android.view.Display#FLAG_SECURE}標志。
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
return super.onJsPrompt(view, url, message, defaultValue, result)
}
}
在項目中WebChromeClient的通用配置:
// 給這個變量設(shè)置已選擇的Uri
var urlArrCallBack: ValueCallback<Array<Uri>>? = null
webView?.webChromeClient = CommonWebChromeClient()
private inner class CommonWebChromeClient : WebChromeClient() {
// 網(wǎng)頁加載的進度
override fun onProgressChanged(view: WebView?, newProgress: Int) {
if (newProgress >= 100) {
// 隱藏Loading頁面
}
}
// 設(shè)置標題
override fun onReceivedTitle(view: WebView?, title: String?) {
super.onReceivedTitle(view, title)
titleNameTv.text = title
}
// 跳用圖片和文件選擇器
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
urlArrCallBack = filePathCallback
// showDialog() 展示圖片/文件選擇器
return true
}
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
// 展示彈窗
AlertDialog.Builder(this@CommonWebViewActivity)
.setTitle("JsAlert")
.setMessage(message)
.setPositiveButton(
"OK"
) { dialog, which -> result?.confirm() }
.setCancelable(false)
.show()
return true
}
override fun onJsConfirm(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
// 展示彈窗
AlertDialog.Builder(this@CommonWebViewActivity)
.setTitle("JsConfirm")
.setMessage(message)
.setPositiveButton(
"OK"
) { dialog, which -> result?.confirm() }
.setNegativeButton(
"Cancel"
) { dialog, which -> result?.cancel() }
.setCancelable(false)
.show()
return true
}
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
val et = EditText(this@CommonWebViewActivity)
et.setText(defaultValue)
AlertDialog.Builder(this@CommonWebViewActivity)
.setTitle(message)
.setView(et)
.setPositiveButton(
"OK"
) { dialog, which -> result?.confirm(et.text.toString()) }
.setNegativeButton(
"Cancel"
) { dialog, which -> result?.cancel() }
.setCancelable(false)
.show()
return true
}
}
JavascriptInterface
可以通過addJavascriptInterface()設(shè)置JavascriptInterface,從而使JS可以直接調(diào)用JavascriptInterface設(shè)置好的方法,比如H5想要跳轉(zhuǎn)Activity、打開原生視頻等都可以通知App。
webView?.addJavascriptInterface(CommonWebJsInterface(), "className")
val MSG_OPEN_ACTIVITY = 1
var UIHandler: UIHandle = UIHandle()
class UIHandle : Handler() {
private var commonWebViewActivity: CommonWebViewActivity? = null
private var jsonStr: String? = null
fun setJsonStr(jsonStr: String?) {
this.jsonStr = jsonStr
}
override fun handleMessage(msg: Message) {
if (commonWebViewActivity == null || commonWebViewActivity?.isFinishing == true) {
removeCallbacksAndMessages(null)
return
}
commonWebViewActivity?.wrapHandleMessage(msg,jsonStr)
}
}
private fun wrapHandleMessage(msg: Message?, jsonStr: String?) {
msg ?: return
if (msg.what == MSG_OPEN_ACTIVITY) {
startActivity(Intent(this, WebViewActivity::class.java))
}
}
inner class CommonWebJsInterface : WebJsInterface {
// JS調(diào)用方法跳轉(zhuǎn)Activity
@JavascriptInterface
override fun openActivity(jsonStr: String?) {
UIHandler.setJsonStr(jsonStr)
val msg = Message.obtain()
msg.what = MSG_OPEN_ACTIVITY
UIHandler.sendMessage(msg)
}
// JS調(diào)用方法播放視頻
@JavascriptInterface
override fun playVideo(jsonStr: String?) {
}
}
private interface WebJsInterface {
@JavascriptInterface
fun openActivity(jsonStr: String?)
@JavascriptInterface
fun playVideo(jsonStr: String?)
}
當(dāng)然安卓也可以通過evaluateJavascript調(diào)用JS的方法。
簡單模版代碼:
class CommonWebViewActivity : AppCompatActivity() {
var webView: WebView? = null
// 給這個變量設(shè)置已選擇的Uri
var urlArrCallBack: ValueCallback<Array<Uri>>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.common_webview_activity)
containerFrameLayout.removeAllViews()
webView = WebView(applicationContext)
containerFrameLayout.addView(webView)
initWebSettings(webView)
webView?.webViewClient = CommonWebViewClient(this)
webView?.webChromeClient = CommonWebChromeClient()
webView?.addJavascriptInterface(CommonWebJsInterface(), "className")
}
// webSettings的通用設(shè)置
private fun initWebSettings(webView: WebView?) {
webView?.isEmpty() ?: return
val webSettings = webView.settings
webSettings.javaScriptEnabled = true //支持JS
webSettings.useWideViewPort =
true // 如果頁面有ViewPort meta tag 指定的寬度,則使用meta tag指定的值,否則默認使用寬屏的視圖窗口
webSettings.loadWithOverviewMode =
true //即縮小內(nèi)容以適合屏幕寬度。當(dāng)內(nèi)容寬度大于WebView控件的寬度時,例如{@link #getUseWideViewPort} 已啟用
webSettings.cacheMode = WebSettings.LOAD_DEFAULT //緩存模式
webSettings.domStorageEnabled = true //開啟 DOM storage API 功能
webSettings.databaseEnabled = true //開啟 database storage API 功能
webSettings.setAppCacheEnabled(true) //開啟 Application Caches 功能
webSettings.setAppCachePath(filesDir.absolutePath + "webcache") //設(shè)置 Application Caches 緩存目錄
webSettings.databasePath = filesDir.absolutePath + "webcache" //設(shè)置數(shù)據(jù)庫緩存路徑
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH) //設(shè)置渲染線程的優(yōu)先級。
webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS //適應(yīng)內(nèi)容大小
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 允許HTTPS和HTTP一起加載
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
}
// 為了處理一些WebView使用而被檢測到的漏洞
try {
// 設(shè)置WebView是否應(yīng)保存密碼。默認值為{@code true}。 @deprecated在將來的版本中將不支持在WebView中保存密碼。
webSettings.savePassword = false
// 同源繞過策略
webSettings.allowFileAccess = false
// 移除有風(fēng)險的webView系統(tǒng)隱藏接口漏洞
webView.removeJavascriptInterface("searchBoxJavaBridge_")
webView.removeJavascriptInterface("accessibility")
webView.removeJavascriptInterface("accessibilityTraversal")
} catch (e: Exception) {
e.printStackTrace()
}
//自定義UA
val ua = webSettings.userAgentString
if (ua.isNotEmpty() && ua.endsWith("自定義")) {
return
}
webSettings.userAgentString = ua + "自定義"
}
private inner class CommonWebViewClient(val context: Context) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
url ?: return false
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
}
} catch (e: Exception) {
return true
}
return super.shouldOverrideUrlLoading(view, url)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
// 開始Loading頁面
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
// 開始結(jié)束Loading頁面
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
// 開始結(jié)束Loading頁面
}
}
private inner class CommonWebChromeClient : WebChromeClient() {
// 網(wǎng)頁加載的進度
override fun onProgressChanged(view: WebView?, newProgress: Int) {
if (newProgress >= 100) {
// 隱藏Loading頁面
}
}
// 設(shè)置標題
override fun onReceivedTitle(view: WebView?, title: String?) {
super.onReceivedTitle(view, title)
titleNameTv.text = title
}
// 跳用圖片和文件選擇器
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
urlArrCallBack = filePathCallback
// showDialog() 展示圖片/文件選擇器
return true
}
//通知主機應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code alert()}對話框。
//<p>如果此方法返回{@code false}或未被覆蓋,則默認行為是顯示一個包含警報消息的對話框并掛起JavaScript的執(zhí)行,直到關(guān)閉對話框為止。
//<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下,默認對話框?qū)⒉粫@示,并且JavaScript 執(zhí)行將被暫停。
// 該應(yīng)調(diào)用{@code JsResult.confirm()}(關(guān)閉自定義對話框時),以便可以恢復(fù)執(zhí)行JavaScript。
//<p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù),請立即調(diào)用{@code JsResult.confirm()},然后返回{@code true}。
//<p>請注意,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient},則默認對話框?qū)⒈蝗∠?,并且Javascript將立即繼續(xù)執(zhí)行。
//<p>請注意,默認對話框不會從父窗口繼承{@link android.view.Display#FLAG_SECURE}標志。
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
// 展示彈窗
AlertDialog.Builder(this@CommonWebViewActivity)
.setTitle("JsAlert")
.setMessage(message)
.setPositiveButton(
"OK"
) { dialog, which -> result?.confirm() }
.setCancelable(false)
.show()
return true
}
// 通知主機應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code Confirm()}對話框。
//<p>如果此方法返回{@code false}或未重寫,則默認行為是顯示一個包含該消息的對話框并暫停JavaScript執(zhí)行,直到該對話框被關(guān)閉為止。
// 當(dāng)用戶按下“確認”按鈕時,默認對話框?qū)⒎祷貃@code true}到JavaScript {@code Confirm()}代碼,而當(dāng)用戶按下“ cancel”時將返回{@code false}到JavaScript代碼。 '按鈕或關(guān)閉對話框。
//<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下,默認對話框?qū)⒉粫@示,并且JavaScript
//執(zhí)行將被暫停。該應(yīng)用應(yīng)調(diào)用 自定義對話框關(guān)閉時,{@ code JsResult.confirm()}或{@code JsResult.cancel()}。
// <p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù),請立即調(diào)用{@code JsResult.confirm()}或{@code JsResult.cancel()},然后返回{@code true}。
//<p>請注意,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient},則會取消默認對話框,而默認值{@code false }將立即返回到JavaScript代碼。
//<p>請注意,默認對話框不會從父窗口繼承{@link android.view.Display#FLAG_SECURE}標志。
override fun onJsConfirm(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
// 展示彈窗
AlertDialog.Builder(this@CommonWebViewActivity)
.setTitle("JsConfirm")
.setMessage(message)
.setPositiveButton(
"OK"
) { dialog, which -> result?.confirm() }
.setNegativeButton(
"Cancel"
) { dialog, which -> result?.cancel() }
.setCancelable(false)
.show()
return true
}
// 通知主機應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code hint()}對話框。
// <p>如果此方法返回{@code false}或未被覆蓋,則默認行為是顯示一個包含該消息的對話框并暫停JavaScript執(zhí)行,直到該對話框被關(guān)閉為止。
// 取消該對話框后,JavaScript {@code hint()}將返回用戶鍵入的字符串,如果用戶按下“取消”按鈕,則返回null。
//<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下,默認對話框?qū)⒉粫@示,并且JavaScript 執(zhí)行將被暫停。
// 該應(yīng)用應(yīng)調(diào)用 取消自定義對話框后,{@ code JsPromptResult.confirm(result)}。
//<p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù),請立即調(diào)用{@code JsPromptResult.confirm(result)},然后返回{@code true}。
//<p>請注意,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient},則默認對話框?qū)⒈蝗∠?,并且將返回{@code null}立即添加到JavaScript代碼。
//<p>請注意,默認對話框不會從父窗口繼承{@link android.view.Display#FLAG_SECURE}標志。
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
val et = EditText(this@CommonWebViewActivity)
et.setText(defaultValue)
AlertDialog.Builder(this@CommonWebViewActivity)
.setTitle(message)
.setView(et)
.setPositiveButton(
"OK"
) { dialog, which -> result?.confirm(et.text.toString()) }
.setNegativeButton(
"Cancel"
) { dialog, which -> result?.cancel() }
.setCancelable(false)
.show()
return true
}
}
val MSG_OPEN_ACTIVITY = 1
var UIHandler: UIHandle? = UIHandle()
class UIHandle : Handler() {
private var commonWebViewActivity: CommonWebViewActivity? = null
private var jsonStr: String? = null
fun setJsonStr(jsonStr: String?) {
this.jsonStr = jsonStr
}
override fun handleMessage(msg: Message) {
if (commonWebViewActivity == null || commonWebViewActivity?.isFinishing == true) {
removeCallbacksAndMessages(null)
return
}
commonWebViewActivity?.wrapHandleMessage(msg, jsonStr)
}
}
private fun wrapHandleMessage(msg: Message?, jsonStr: String?) {
msg ?: return
if (msg.what == MSG_OPEN_ACTIVITY) {
startActivity(Intent(this, WebViewActivity::class.java))
}
}
inner class CommonWebJsInterface : WebJsInterface {
// JS調(diào)用方法跳轉(zhuǎn)Activity
@JavascriptInterface
override fun openActivity(jsonStr: String?) {
UIHandler?.setJsonStr(jsonStr)
val msg = Message.obtain()
msg.what = MSG_OPEN_ACTIVITY
UIHandler?.sendMessage(msg)
}
// JS調(diào)用方法播放視頻
@JavascriptInterface
override fun playVideo(jsonStr: String?) {
}
}
private interface WebJsInterface {
@JavascriptInterface
fun openActivity(jsonStr: String?)
@JavascriptInterface
fun playVideo(jsonStr: String?)
}
override fun onResume() {
super.onResume()
webView?.onResume()
}
override fun onPause() {
super.onPause()
webView?.onPause()
}
override fun onDestroy() {
try {
onWebViewDestroy()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
super.onDestroy()
UIHandler?.removeCallbacksAndMessages(null)
UIHandler = null
}
/**
* 銷毀WebView;
*/
private fun onWebViewDestroy() {
if (webView != null) {
webView?.webChromeClient = null
webView?.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
webView?.clearHistory()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (webView?.parent != null) {
(webView?.parent as ViewGroup).removeView(webView)
}
webView?.removeAllViews()
webView?.destroy()
} else {
webView?.removeAllViews()
webView?.destroy()
if (webView?.parent != null) {
(webView?.parent as ViewGroup).removeView(webView)
}
}
webView = null
}
}
/**
* 條件允許時,按下返回鍵可以在WebView中返回上一頁;
*/
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
//WebView內(nèi)導(dǎo)航
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_DOWN) {
if (onBackInWebClicked()) {
return true
}
}
return super.onKeyDown(keyCode, event)
}
/**
* @return 是否處理的事件
*/
protected fun onBackInWebClicked(): Boolean {
//WebView內(nèi)導(dǎo)航
if (canWebViewGoBack() && webView != null && webView?.canGoBack() == true) {
webView?.goBack()
return true
}
return false
}
/**
* @return 是否可以在WebView內(nèi)進行返回導(dǎo)航
*/
protected fun canWebViewGoBack(): Boolean {
return true
}
}