<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端工程師所需要了解的WebView

          共 8324字,需瀏覽 17分鐘

           ·

          2021-05-15 18:04

          | 導(dǎo)語 現(xiàn)如今,在做移動端 H5 開發(fā)時(shí),少不了與 Native 之間進(jìn)行交互。而在Native中,H5的承載容器為 WebView,其核心是使用 WebView 控件實(shí)現(xiàn)加載 url。

          WebView的概念

          WebView 用來展示網(wǎng)頁的 view 組件,該組件是你運(yùn)行自己的瀏覽器或者在你的線程中展示線上內(nèi)容的基礎(chǔ)。使用 Webkit 渲染引擎來展示,并且支持前進(jìn)后退等基于瀏覽歷史,放大縮小,等更多功能。


          簡單來說 WebView 是手機(jī)中內(nèi)置了一款高性能 Webkit 內(nèi)核瀏覽器,在 SDK 中封裝的一個(gè)組件。不過沒有提供地址欄和導(dǎo)航欄,只是單純的展示一個(gè)網(wǎng)頁界面。

          以上是一個(gè)客戶端開發(fā)者描述的,而站在一個(gè)前端開發(fā)者的角度,使用過后的感受就是:

          WebView 可以簡單理解為頁面里的 iframe 。原生app與 WebView 的交互可以簡單看作是頁面與頁面內(nèi) iframe 頁面進(jìn)行的交互。就如頁面與頁面內(nèi)的 iframe 共用一個(gè) Window  一樣,原生與  WebView  也共用了一套原生的方法。

          既然我們使用了 WebView 來承載 H5 ,那么便少不了與 Native 之間發(fā)生交互, WebView 所承載的頁面,通過 JS 與 Native 進(jìn)行通信,我們將這個(gè)通信“橋梁”為 JSBridge 。如果你參與過微信內(nèi)置瀏覽器的 H5 開發(fā),會發(fā)現(xiàn)一個(gè)經(jīng)常出現(xiàn)的東西,叫做 WeixinJSBridge

          JSBridge

          JSBridge 簡單來講,主要是 給 JavaScript 提供調(diào)用 Native 功能的接口,讓混合開發(fā)中的『前端部分』可以方便地使用地址位置、攝像頭甚至支付等 Native 功能。

          既然是『簡單來講』,那么 JSBridge 的用途肯定不只『調(diào)用 Native 功能』這么簡單寬泛。實(shí)際上,JSBridge 就像其名稱中的『Bridge』的意義一樣,是 Native 和非 Native 之間的橋梁,它的核心是 構(gòu)建 Native 和非 Native 間消息通信的通道,而且是 雙向通信的通道

          所謂 雙向通信的通道:

          • JS 向 Native 發(fā)送消息 : 調(diào)用相關(guān)功能、通知 Native 當(dāng)前 JS 的相關(guān)狀態(tài)等。

          • Native 向 JS 發(fā)送消息 : 回溯調(diào)用結(jié)果、消息推送、通知 JS 當(dāng)前 Native 的狀態(tài)等。

          JavaScript 是運(yùn)行在一個(gè)單獨(dú)的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于這些 Context 與原生運(yùn)行環(huán)境的天然隔離,我們可以將這種情況與 RPC(Remote Procedure Call,遠(yuǎn)程過程調(diào)用)通信進(jìn)行類比,將 Native 與  JavaScript 的每次互相調(diào)用看做一次 RPC 調(diào)用。如此一來我們可以按照通常的 RPC 方式來進(jìn)行設(shè)計(jì)和實(shí)現(xiàn)。

          在 JSBridge 的設(shè)計(jì)中,可以把前端看做 RPC 的客戶端,把 Native 端看做 RPC 的服務(wù)器端,從而 JSBridge 要實(shí)現(xiàn)的主要邏輯就出現(xiàn)了:通信調(diào)用(Native 與 JS 通信 和 句柄解析調(diào)用。(如果你是個(gè)前端,而且并不熟悉 RPC 的話,你也可以把這個(gè)流程類比成 JSONP 的流程。)

          通過以上的分析,可以清楚地知曉 JSBridge 主要的功能和職責(zé),接下來,就分析一下在 Android WebView 和 iOS WebView 中實(shí)現(xiàn) Native 與 JS 通信的原理。

          Android WebView

          Android 4.4前:Android WebView在低版本 & 高版本采用了不同的Webkit版本的內(nèi)核(正因?yàn)槿绱耍琀5的很多新特性,在Android版本小于4.4的安卓機(jī)上,都不支持)

          Android 4.4后:原本基于Webkit的WebView開始基于 Chromium內(nèi)核,這一改動大大提升了 WebView組件的性能以及對 HTML5, CSS3, JavaScript的支持。不過它的API卻沒有很大的改動,在兼容低版本的同時(shí)只引進(jìn)了少部分新的API,并不需要你做很大的改動。

          在 Android WebView,要實(shí)現(xiàn) JS 調(diào)用 Java,有 3 種方法:

          • JavascriptInterface

          • WebViewClient.shouldOverrideUrlLoading()

          • WebChromeClient.onXXX()

          1、JavascriptInterface

          這是 Android 提供的 JS 與 Native 通信的官方解決方案。

          首先 Native 端需要實(shí)現(xiàn)這么一個(gè)類,給 JavaScript 調(diào)用。

          public class WebAppInterface {    @JavascriptInterface    public void showToast(String toast) {        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();    }}
          然后將這個(gè) WebAppInterface類,通過以下代碼,添加到 WebView 的 JavaScriptInterface 中。
          WebView webView = (WebView) findViewById(R.id.webview);webView.addJavascriptInterface(new WebAppInterface(this), "Android"); // 這里的Android會被當(dāng)做一個(gè)變量,注入到頁面的window中。

          接著就可以在 JS 中調(diào)用 Native 了。

          function showAndroidToast(toast) {    Android.showToast(toast);}

          2、WebViewClient.shouldOverrideUrlLoading()

          這個(gè)方法的作用是攔截所有 WebView 的 URL Scheme 。

          URL Scheme 是一種類似于 url 的鏈接,是為了方便 app 直接互相調(diào)用設(shè)計(jì)的,形式和普通的 url 近似,主要區(qū)別是 protocol 和 host 一般是自定義的。

          攔截 URL Scheme 的主要流程是:Web 端通過某種方式(例如 iframe.src/location.href)發(fā)送 URL Scheme 請求,之后 Native 攔截到請求并根據(jù) URL Scheme(包括所帶的參數(shù))進(jìn)行相關(guān)操作。

          頁面可以構(gòu)造一個(gè)特殊格式的 URL Scheme 觸發(fā),shouldOverrideUrlLoading 攔截 URL 后判斷其格式,然后 Native 就能執(zhí)行自身的邏輯了。

          public class CustomWebViewClient extends WebViewClient {  @Override  public boolean shouldOverrideUrlLoading(    WebView view,    String url    ) {      if (isJsBridgeUrl(url)) {        // JSbridge的處理邏輯        return true;      }      return super.shouldOverrideUrlLoading(view, url);    }}

          3、WebChromeClient.onXXX()

          通過修改原來瀏覽器的 window某些方法,然后攔截固定規(guī)則的參數(shù),然后分發(fā)給Java 對應(yīng)的方法去處理

          • alert,可以被 WebView 的 WebChromeClient.onJsAlert() 監(jiān)聽

          • confirm,可以被 WebView 的 WebChromeClient.onJsConfirm() 監(jiān)聽

          • console.log,可以被 WebView 的 WebChromeClient.onConsoleMessage() 監(jiān)聽

          • prompt,可以被 WebView 的 WebChromeClient.onJsPrompt()監(jiān)聽

          prompt 簡單舉例說明,Web 頁面通過調(diào)用 prompt()方法,安卓客戶端通過監(jiān)聽WebChromeClient.onJsPrompt()事件,攔截傳入的參數(shù),如果參數(shù)符合一定協(xié)議規(guī)范,那么就解析參數(shù),扔給后續(xù)的 Java 去處理。

          window.prompt(message, value);

          WebChromeClient.onJsPrompt()就會受到回調(diào)。onJsPrompt()方法的 message參數(shù)的值正是JS的方法 window.prompt()的 message的值。


          public class CustomWebChromeClient extends WebChromeClient {  @Override  public boolean onJsPrompt(    WebView view,    String url,    String message,    String defaultValue,    JsPromptResult result    ) {        // 處理JS 的調(diào)用邏輯        result.confirm();        return true;    }}

          Java 調(diào)用 JavaScript

          Android,在 Kitkat(4.4)只能用 loadUrl 一段 JavaScript 代碼。

          webView.loadUrl("javascript:" + javaScriptString);

          而 Kitkat 之后的版本,也可以用 evaluateJavascript 方法實(shí)現(xiàn):

          webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {    @Override    public void onReceiveValue(String value) {      // native代碼    }  });

          IOS WebView

          In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView. Additionally, consider setting the WKPreferences property javaScriptEnabled to NO if you render files that are not supposed to run JavaScript.

          在 IOS8 之前,蘋果手機(jī)的 WebView 使用的 UIWebViewUIWebView長期以來存在某些問題:

          • 加載速度慢

          • 存在內(nèi)存泄漏

          • 內(nèi)存占用多,內(nèi)存優(yōu)化困難

          • 如果內(nèi)存占用過多還可能因?yàn)檎加眠^多被系統(tǒng)kill掉

          在 WWDC 2014 大會上,IOS8推出了 WKWebViewWKWebView 是現(xiàn)代 Webkit API 在 iOS 8 和 OS X Yosemite 應(yīng)用中的核心部分。它代替了 UIKit 中的 UIWebView 和 AppKit 中的 WebView,提供了統(tǒng)一的跨雙平臺 API。擁有 60fps 滾動刷新率、內(nèi)置手勢、高效的 app 和 web 信息交換通道、和 Safari 相同的 JavaScript 引擎。

          JavaScript ?? Swift 對話機(jī)制

          使用用戶腳本來注入 JavaScript

          WKUserScript 允許在正文加載之前或之后注入到頁面中。這個(gè)強(qiáng)大的功能允許在頁面中以安全且唯一的方式操作網(wǎng)頁內(nèi)容。

          一個(gè)簡單的例子如下,用戶改變背景的用戶腳本被插入到網(wǎng)頁中:

          let source = "document.body.style.background = \"#777;// 注入腳本 在文檔加載完成后執(zhí)行let userScript = WKUserScript()let userScript = WKUserScript(source: source, injectionTime: .AtDocumentEnd, forMainFrameOnly: true)let userContentController = WKUserContentController()userContentController.addUserScript(userScript)
          let configuration = WKWebViewConfiguration()configuration.userContentController = userContentControllerself.webView =WKWebView(frame: self.view.bounds, configuration: configuration)

           對象可以以 JavaScript 源碼形式初始化,初始化時(shí)還可以傳入是在加載之前還是結(jié)束時(shí)注入,以及腳本影響的是這個(gè)布局還是僅主要布局。于是用戶腳本被加入到 WKUserContentController 中,并且以 WKWebViewConfiguration 屬性傳入到 WKWebView 的初始化過程中。

          這個(gè)樣例可以簡單擴(kuò)展為更為高級的頁面修改方法,例如去除廣告、隱藏評論等。

          Message Handlers

          利用以下代碼,可以跟Native進(jìn)行通信

          window.webkit.messageHandlers.{NAME}.postMessage()

          Handler的name可以通過 WKScriptMessageHandler 協(xié)議中的 addScriptMessageHandler() 接口函數(shù)設(shè)置:

          class NotificationScriptMessageHandler: NSObject, WKScriptMessageHandler {  func userContentController(    userContentController: WKUserContentController,    didReceiveScriptMessage message: WKScriptMessage!   ) {     println(message.body)  }}let userContentController = WKUserContentController()let handler = NotificationScriptMessageHandler()userContentController.addScriptMessageHandler(handler, name: "notification")

          于是當(dāng)通知進(jìn)入 app 的時(shí)候,比如說在頁面中創(chuàng)建一個(gè)新對象,相關(guān)信息就可以這樣傳遞:

          window.webkit.messageHandlers.notification.postMessage({body: '發(fā)送給Native'});

          添加用戶腳本來對 web 事件監(jiān)聽并用 Message Handler 將信息傳回 app。

          總結(jié)

          通信原理是 JSBridge 實(shí)現(xiàn)的核心,實(shí)現(xiàn)方式可以各種各樣,但是萬變不離其宗。這里,推薦的實(shí)現(xiàn)方式如下:

          • JavaScript 調(diào)用 Native 推薦使用 注入 API 的方式。( iOS6 忽略,Android 4.2以下使用 WebViewClient 的 onJsPrompt 方式。)

          • Native 調(diào)用 JavaScript 則直接執(zhí)行拼接好的 JavaScript 代碼即可。

          對于其他方式,諸如 React Native、微信小程序 的通信方式都與上描述的近似,并根據(jù)實(shí)際情況進(jìn)行優(yōu)化。

          以 React Native 的 iOS 端舉例:JavaScript 運(yùn)行在 JSCore 中,實(shí)際上可以與上面的方式一樣,利用注入 API 來實(shí)現(xiàn) JavaScript 調(diào)用 Native 功能。不過 React Native 并沒有設(shè)計(jì)成 JavaScript 直接調(diào)用 Object-C,而是 為了與 Native 開發(fā)里事件響應(yīng)機(jī)制一致,設(shè)計(jì)成 需要在 Object-C 去調(diào) JavaScript 時(shí)才通過返回值觸發(fā)調(diào)用。原理基本一樣,只是實(shí)現(xiàn)方式不同。

          如果覺得這篇文章還不錯(cuò)
          點(diǎn)擊下面卡片關(guān)注我
          來個(gè)【分享、點(diǎn)贊、在看】三連支持一下吧

             “分享、點(diǎn)贊在看” 支持一波  


          瀏覽 60
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  视频二区在线 | 91丨豆花丨国产熟女 | 无码精品人妻喷潮一区二区三区白浆 | 精品视频在线婷婷 | 一区二区三区在线 | 欧 |