Android的WebView的API詳解

WebView

WebView是谷歌提供的一個(gè)加載H5的控件骂远,WebView這個(gè)控件又包含四大部分:WebSettings媒楼、WebViewClient、WebChromeClient、JavascriptInterface营密。通過四個(gè)類,我們可以為WebView設(shè)置基礎(chǔ)功能和監(jiān)聽目锭,下面會(huì)逐一介紹四個(gè)類的方法评汰。除了這四部分,WebView還有自己的API痢虹,先來介紹WebView的API被去。

WebView的API介紹

列出WebView一些常用的API,如加載url奖唯、前進(jìn)后退惨缆、清理緩存、狀態(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)

      // 后退丰捷、前進(jìn):
      webView.canGoBack() //是否可以后退
      webView.goBack() //后退一頁面
      webView.canGoForward() //是否可以前進(jìn)
      webView.goForward() //前進(jìn)一頁面
      webView.goBackOrForward(-1) //后退或前進(jìn)多少步坯墨,正前負(fù)退

      //清除緩存數(shù)據(jù):
      // 清除網(wǎng)頁訪問留下的緩存,由于內(nèi)核緩存是全局的因此這個(gè)方法不僅僅針對webview而是針對整個(gè)應(yīng)用程序.
      webView.clearCache(true)
      // 清除當(dāng)前webview訪問的歷史記錄病往,只會(huì)webview訪問歷史記錄里的所有記錄除了當(dāng)前訪問記錄.
      webView.clearHistory()
      // 這個(gè)api僅僅清除自動(dòng)完成填充的表單數(shù)據(jù)捣染,并不會(huì)清除WebView存儲(chǔ)到本地的數(shù)據(jù)。
      webView.clearFormData()

      //WebView的狀態(tài)
      webView.onResume() // 可見狀態(tài)
      webView.onPause() // 頁面失去焦點(diǎn)變成不可見狀態(tài)
      webView.pauseTimers() // 頁面失去焦點(diǎn)變成不可見狀態(tài)停巷,對整個(gè)應(yīng)用的webview起作用
      webView.resumeTimers() //恢復(fù)pauseTimers時(shí)的動(dòng)作
      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)使用的特定縮放機(jī)制示损。
      //此設(shè)置不會(huì)影響使用{@link WebView#zoomIn()}和{@link WebView#zoomOut()}方法執(zhí)行的縮放渗磅。默認(rèn)值為{@code true}。
      webSetting?.setSupportZoom(true)
      //設(shè)置WebView是否應(yīng)使用其內(nèi)置的縮放機(jī)制检访。內(nèi)置的縮放機(jī)制包括屏幕縮放控件(顯示在WebView的內(nèi)容上)以及使用捏合手勢控制縮放始鱼。可以使用{@link #setDisplayZoomControls}設(shè)置是否顯示這些屏幕上的控件脆贵。默認(rèn)值為{@code false}
      webSetting?.builtInZoomControls = false

      // 是否支持ViewPort的meta tag屬性医清,
      // 如果頁面有ViewPort meta tag 指定的寬度,則使用meta tag指定的值卖氨,否則默認(rèn)使用寬屏的視圖窗口
      webSetting?.useWideViewPort = true
      // 設(shè)置WebView是否以概述模式加載頁面会烙,即縮小內(nèi)容以適合屏幕寬度。
      // 當(dāng)內(nèi)容寬度大于WebView控件的寬度時(shí)筒捺,例如{@link #getUseWideViewPort} 已啟用柏腻。默認(rèn)值為{@code false}
      webSetting?.loadWithOverviewMode = true

      //設(shè)置渲染線程的優(yōu)先級。與其他設(shè)置不同系吭,此設(shè)置每個(gè)過程僅需要調(diào)用一次五嫂。默認(rèn)值為{@link RenderPriority#NORMAL}。
      webSetting?.setRenderPriority(WebSettings.RenderPriority.HIGH)

      // 設(shè)置基礎(chǔ)布局算法肯尺。這將導(dǎo)致WebView的重新布局沃缘。默認(rèn)值為{@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)}時(shí)是否需要將節(jié)點(diǎn)設(shè)置為具有焦點(diǎn)。默認(rèn)值為{@code true}寨昙。
      webSetting?.setNeedInitialFocus(true)

      //告訴JavaScript自動(dòng)打開窗口。
      // 這適用于JavaScript函數(shù){@code window.open()}掀亩。默認(rèn)值為{@code false}
      webSetting?.javaScriptCanOpenWindowsAutomatically = true

      //支持自動(dòng)加載圖片
      webSetting?.loadsImagesAutomatically = true

      //設(shè)置解碼html頁面時(shí)使用的默認(rèn)文本編碼名稱舔哪。 *默認(rèn)值為“ UTF-8”。
      webSetting?.defaultTextEncodingName = "utf-8"

      // 設(shè)置默認(rèn)字體大小槽棍。默認(rèn)值為16捉蚤。
      webSetting?.defaultFontSize = 16

      //設(shè)置最小字體大小抬驴。預(yù)設(shè)值為8。
      webSetting?.minimumFontSize = 8

      /**
       *  Webview在安卓5.0之前默認(rèn)允許其加載混合網(wǎng)絡(luò)協(xié)議內(nèi)容
       *  在安卓5.0之后缆巧,默認(rèn)不允許加載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是否支持多個(gè)窗口。
      webSetting?.supportMultipleWindows()

在項(xiàng)目中一般設(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指定的值捉超,否則默認(rèn)使用寬屏的視圖窗口
      webSettings.loadWithOverviewMode =
          true //即縮小內(nèi)容以適合屏幕寬度胧卤。當(dāng)內(nèi)容寬度大于WebView控件的寬度時(shí),例如{@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)保存密碼。默認(rèn)值為{@code true}补君。  @deprecated在將來的版本中將不支持在WebView中保存密碼猪勇。
          webSettings.savePassword = false
          // 同源繞過策略
          webSettings.allowFileAccess = false
          // 移除有風(fēng)險(xiǎn)的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)過這個(gè)方法
      //比如在本地WebView加載H5祠够,而不是瀏覽器
      return super.shouldOverrideUrlLoading(view, request)
  }

  override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
      super.onPageStarted(view, url, favicon)
      //這個(gè)事件就是開始載入頁面調(diào)用的,我們可以設(shè)定一個(gè)loading的頁面椭更,告訴用戶程序在等待網(wǎng)絡(luò)響應(yīng)哪审。
  }

  override fun onPageFinished(view: WebView?, url: String?) {
      super.onPageFinished(view, url)
      //在頁面加載結(jié)束時(shí)調(diào)用。同樣道理虑瀑,我們可以關(guān)閉loading 條湿滓,切換程序動(dòng)作。
  }

  override fun onReceivedError(
      view: WebView?,
      request: WebResourceRequest?,
      error: WebResourceError?
  ) {
      super.onReceivedError(view, request, error)
      // (報(bào)告錯(cuò)誤信息舌狗,當(dāng)加載出錯(cuò)的時(shí)候會(huì)回調(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)
      // 在加載頁面資源時(shí)會(huì)調(diào)用,每一個(gè)資源(比如圖片)的加載都會(huì)調(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)
      //(通知主機(jī)應(yīng)用程序WebView收到HTTP身份驗(yàn)證請求朝氓。主機(jī)應(yīng)用程序可以使用提供的* {@link HttpAuthHandler}設(shè)置WebView對請求的響應(yīng)。 *默認(rèn)行為是取消請求
  }

  override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
      super.onReceivedSslError(view, handler, error)
      //SSL過程發(fā)生錯(cuò)誤主届,重寫此方法可以讓webview處理https請求赵哲。
  }

  override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {
      super.onScaleChanged(view, oldScale, newScale)
      // (WebView縮放大小發(fā)生改變時(shí)調(diào)用)
  }

  override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) {
      super.onUnhandledKeyEvent(view, event)
      //(Key事件未被加載時(shí)調(diào)用)
  }

}

項(xiàng)目中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)站的加載進(jìn)度君丁、標(biāo)題枫夺、圖片文件選擇、JS彈窗

class BaseWebChromeClient : WebChromeClient {

  override fun onProgressChanged(view: WebView?, newProgress: Int) {
      super.onProgressChanged(view, newProgress)
      //當(dāng)前網(wǎng)頁加載的進(jìn)度
  }

  override fun onReceivedTitle(view: WebView?, title: String?) {
      super.onReceivedTitle(view, title)
      //網(wǎng)頁title標(biāo)題
  }

  override fun onReceivedIcon(view: WebView?, icon: Bitmap?) {
      super.onReceivedIcon(view, icon)
      //網(wǎng)頁圖標(biāo)
  }

  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)
      // 通知主機(jī)應(yīng)用程序當(dāng)前頁面已進(jìn)入全屏模式丑勤。調(diào)用之后,Web內(nèi)容將不再在WebView中呈現(xiàn)吧趣,而是在{@code view}中呈現(xiàn)法竞。
      // 主機(jī)應(yīng)用程序應(yīng)將此視圖添加到配置了{(lán)@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN}標(biāo)志的窗口中,以便實(shí)際全屏顯示此Web內(nèi)容再菊。
  }

  override fun onHideCustomView() {
      super.onHideCustomView()
      // 通知主機(jī)應(yīng)用程序當(dāng)前頁面已退出全屏模式爪喘。
      // 宿主應(yīng)用程序必須隱藏自定義視圖(以前傳遞給{@link #onShowCustomView(View,CustomViewCallback)onShowCustomView()}的視圖)纠拔。調(diào)用之后秉剑,Web內(nèi)容將再次在原始WebView中呈現(xiàn)
  }

  override fun getDefaultVideoPoster(): Bitmap? {
      //webview視頻未播放時(shí)默認(rèn)顯示占位圖
      return super.getDefaultVideoPoster()
  }

  //通知主機(jī)應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code alert()}對話框。
  //<p>如果此方法返回{@code false}或未被覆蓋稠诲,則默認(rèn)行為是顯示一個(gè)包含警報(bào)消息的對話框并掛起JavaScript的執(zhí)行侦鹏,直到關(guān)閉對話框?yàn)橹埂?  //<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true}臀叙,在這種情況下略水,默認(rèn)對話框?qū)⒉粫?huì)顯示,并且JavaScript 執(zhí)行將被暫停劝萤。
  // 該應(yīng)調(diào)用{@code JsResult.confirm()}(關(guān)閉自定義對話框時(shí))渊涝,以便可以恢復(fù)執(zhí)行JavaScript。
  //<p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù)床嫌,請立即調(diào)用{@code JsResult.confirm()}跨释,然后返回{@code true}。
  //<p>請注意厌处,如果將{@link WebChromeClient}設(shè)置為{@code null}鳖谈,或者根本沒有設(shè)置{@link WebChromeClient},則默認(rèn)對話框?qū)⒈蝗∠妫⑶襃avascript將立即繼續(xù)執(zhí)行缆娃。
  //<p>請注意,默認(rèn)對話框不會(huì)從父窗口繼承{@link android.view.Display#FLAG_SECURE}標(biāo)志瑰排。
  override fun onJsAlert(
      view: WebView?,
      url: String?,
      message: String?,
      result: JsResult?
  ): Boolean {
      return super.onJsAlert(view, url, message, result)
  }

  // 通知主機(jī)應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code Confirm()}對話框贯要。
  //<p>如果此方法返回{@code false}或未重寫,則默認(rèn)行為是顯示一個(gè)包含該消息的對話框并暫停JavaScript執(zhí)行椭住,直到該對話框被關(guān)閉為止郭毕。
  // 當(dāng)用戶按下“確認(rèn)”按鈕時(shí),默認(rèn)對話框?qū)⒎祷貃@code true}到JavaScript {@code Confirm()}代碼函荣,而當(dāng)用戶按下“ cancel”時(shí)將返回{@code false}到JavaScript代碼显押。 '按鈕或關(guān)閉對話框。
  //<p>要顯示自定義對話框傻挂,應(yīng)用應(yīng)通過此方法返回{@code true}乘碑,在這種情況下,默認(rèn)對話框?qū)⒉粫?huì)顯示金拒,并且JavaScript
  //執(zhí)行將被暫停兽肤。該應(yīng)用應(yīng)調(diào)用 自定義對話框關(guān)閉時(shí),{@ 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}症副,則會(huì)取消默認(rèn)對話框店雅,而默認(rèn)值{@code false }將立即返回到JavaScript代碼。
  //<p>請注意贞铣,默認(rèn)對話框不會(huì)從父窗口繼承{@link android.view.Display#FLAG_SECURE}標(biāo)志闹啦。
  override fun onJsConfirm(
      view: WebView?,
      url: String?,
      message: String?,
      result: JsResult?
  ): Boolean {
      return super.onJsConfirm(view, url, message, result)
  }

  // 通知主機(jī)應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code hint()}對話框。
  // <p>如果此方法返回{@code false}或未被覆蓋辕坝,則默認(rèn)行為是顯示一個(gè)包含該消息的對話框并暫停JavaScript執(zhí)行窍奋,直到該對話框被關(guān)閉為止。
  // 取消該對話框后酱畅,JavaScript {@code hint()}將返回用戶鍵入的字符串琳袄,如果用戶按下“取消”按鈕,則返回null圣贸。
  //<p>要顯示自定義對話框挚歧,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下吁峻,默認(rèn)對話框?qū)⒉粫?huì)顯示滑负,并且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}痪寻,則默認(rèn)對話框?qū)⒈蝗∠菥洌⑶覍⒎祷貃@code null}立即添加到JavaScript代碼。
  //<p>請注意橡类,默認(rèn)對話框不會(huì)從父窗口繼承{@link android.view.Display#FLAG_SECURE}標(biāo)志蛇尚。
  override fun onJsPrompt(
      view: WebView?,
      url: String?,
      message: String?,
      defaultValue: String?,
      result: JsPromptResult?
  ): Boolean {
      return super.onJsPrompt(view, url, message, defaultValue, result)
  }

}

在項(xiàng)目中WebChromeClient的通用配置:

    // 給這個(gè)變量設(shè)置已選擇的Uri
    var urlArrCallBack: ValueCallback<Array<Uri>>? = null

    webView?.webChromeClient = CommonWebChromeClient()

    private inner class CommonWebChromeClient : WebChromeClient() {

      // 網(wǎng)頁加載的進(jìn)度
      override fun onProgressChanged(view: WebView?, newProgress: Int) {
          if (newProgress >= 100) {
              // 隱藏Loading頁面
          }
      }

      // 設(shè)置標(biāo)題
      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

  // 給這個(gè)變量設(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指定的值,否則默認(rèn)使用寬屏的視圖窗口
      webSettings.loadWithOverviewMode =
          true //即縮小內(nèi)容以適合屏幕寬度庶诡。當(dāng)內(nèi)容寬度大于WebView控件的寬度時(shí)惦银,例如{@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)保存密碼灌砖。默認(rèn)值為{@code true}璧函。  @deprecated在將來的版本中將不支持在WebView中保存密碼。
          webSettings.savePassword = false
          // 同源繞過策略
          webSettings.allowFileAccess = false
          // 移除有風(fēng)險(xiǎn)的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)頁加載的進(jìn)度
      override fun onProgressChanged(view: WebView?, newProgress: Int) {
          if (newProgress >= 100) {
              // 隱藏Loading頁面
          }
      }

      // 設(shè)置標(biāo)題
      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
      }

      //通知主機(jī)應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code alert()}對話框基显。
      //<p>如果此方法返回{@code false}或未被覆蓋蘸吓,則默認(rèn)行為是顯示一個(gè)包含警報(bào)消息的對話框并掛起JavaScript的執(zhí)行,直到關(guān)閉對話框?yàn)橹埂?      //<p>要顯示自定義對話框撩幽,應(yīng)用應(yīng)通過此方法返回{@code true}库继,在這種情況下,默認(rèn)對話框?qū)⒉粫?huì)顯示窜醉,并且JavaScript 執(zhí)行將被暫停宪萄。
      // 該應(yīng)調(diào)用{@code JsResult.confirm()}(關(guān)閉自定義對話框時(shí)),以便可以恢復(fù)執(zhí)行JavaScript榨惰。
      //<p>要取消顯示對話框并允許JavaScript執(zhí)行繼續(xù)拜英,請立即調(diào)用{@code JsResult.confirm()},然后返回{@code true}琅催。
      //<p>請注意居凶,如果將{@link WebChromeClient}設(shè)置為{@code null},或者根本沒有設(shè)置{@link WebChromeClient}藤抡,則默認(rèn)對話框?qū)⒈蝗∠辣蹋⑶襃avascript將立即繼續(xù)執(zhí)行。
      //<p>請注意缠黍,默認(rèn)對話框不會(huì)從父窗口繼承{@link android.view.Display#FLAG_SECURE}標(biāo)志弄兜。
      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
      }

      // 通知主機(jī)應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code Confirm()}對話框。
      //<p>如果此方法返回{@code false}或未重寫,則默認(rèn)行為是顯示一個(gè)包含該消息的對話框并暫停JavaScript執(zhí)行替饿,直到該對話框被關(guān)閉為止语泽。
      // 當(dāng)用戶按下“確認(rèn)”按鈕時(shí),默認(rèn)對話框?qū)⒎祷貃@code true}到JavaScript {@code Confirm()}代碼盛垦,而當(dāng)用戶按下“ cancel”時(shí)將返回{@code false}到JavaScript代碼湿弦。 '按鈕或關(guān)閉對話框。
      //<p>要顯示自定義對話框腾夯,應(yīng)用應(yīng)通過此方法返回{@code true},在這種情況下蔬充,默認(rèn)對話框?qū)⒉粫?huì)顯示蝶俱,并且JavaScript
      //執(zhí)行將被暫停。該應(yīng)用應(yīng)調(diào)用 自定義對話框關(guān)閉時(shí)饥漫,{@ 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}竿拆,則會(huì)取消默認(rèn)對話框,而默認(rèn)值{@code false }將立即返回到JavaScript代碼宾尚。
      //<p>請注意丙笋,默認(rèn)對話框不會(huì)從父窗口繼承{@link android.view.Display#FLAG_SECURE}標(biāo)志。
      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
      }

      // 通知主機(jī)應(yīng)用程序該網(wǎng)頁要顯示JavaScript {@code hint()}對話框煌贴。
      // <p>如果此方法返回{@code false}或未被覆蓋御板,則默認(rèn)行為是顯示一個(gè)包含該消息的對話框并暫停JavaScript執(zhí)行,直到該對話框被關(guān)閉為止牛郑。
      // 取消該對話框后怠肋,JavaScript {@code hint()}將返回用戶鍵入的字符串,如果用戶按下“取消”按鈕淹朋,則返回null笙各。
      //<p>要顯示自定義對話框,應(yīng)用應(yīng)通過此方法返回{@code true}瑞你,在這種情況下酪惭,默認(rèn)對話框?qū)⒉粫?huì)顯示,并且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}甲献,則默認(rèn)對話框?qū)⒈蝗∠⑶覍⒎祷貃@code null}立即添加到JavaScript代碼颂翼。
      //<p>請注意晃洒,默認(rèn)對話框不會(huì)從父窗口繼承{@link android.view.Display#FLAG_SECURE}標(biāo)志。
      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
      }
  }

  /**
   * 條件允許時(shí)朦乏,按下返回鍵可以在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)進(jìn)行返回導(dǎo)航
   */
  protected fun canWebViewGoBack(): Boolean {
      return true
  }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呻疹,隨后出現(xiàn)的幾起案子吃引,更是在濱河造成了極大的恐慌,老刑警劉巖刽锤,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镊尺,死亡現(xiàn)場離奇詭異,居然都是意外死亡并思,警方通過查閱死者的電腦和手機(jī)庐氮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纺荧,“玉大人旭愧,你說我怎么就攤上這事≈嫦荆” “怎么了输枯?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長占贫。 經(jīng)常有香客問我桃熄,道長,這世上最難降的妖魔是什么型奥? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任瞳收,我火速辦了婚禮,結(jié)果婚禮上厢汹,老公的妹妹穿的比我還像新娘螟深。我一直安慰自己,他們只是感情好烫葬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布界弧。 她就那樣靜靜地躺著凡蜻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垢箕。 梳的紋絲不亂的頭發(fā)上划栓,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機(jī)與錄音条获,去河邊找鬼忠荞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帅掘,可吹牛的內(nèi)容都是我干的委煤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼锄开,長吁一口氣:“原來是場噩夢啊……” “哼素标!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萍悴,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寓免,沒想到半個(gè)月后癣诱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袜香,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年撕予,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜈首。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡实抡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出欢策,到底是詐尸還是另有隱情吆寨,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布踩寇,位于F島的核電站啄清,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俺孙。R本人自食惡果不足惜辣卒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睛榄。 院中可真熱鬧荣茫,春花似錦、人聲如沸场靴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至票罐,卻和暖如春叉趣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背该押。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工疗杉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚕礼。 一個(gè)月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓烟具,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奠蹬。 傳聞我的和親對象是個(gè)殘疾皇子朝聋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容