整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          前端H5與客戶端Native交互原理 - JSBridge


          在混合應用開發中,一種常見且成熟的技術方案是將原生應用與 WebView 結合,使得復雜的業務邏輯可以通過網頁技術實現。實現這種類型的混合應用時,就需要解決H5與Native之間的雙向通信。JSBridge 是一種在混合應用中實現 Web 和原生代碼之間通信的重要機制。


          混合開發

          混合開發(Hybrid)是一種開發模式,指使用多種開發模型開發App,通常會涉及到兩大類技術:原生 Native、Web H5。

          • 原生技術主要指iOS、Android,原生開發效率較低,開發完成需要重新打包整個App,發布依賴用戶的更新,性能較高功能覆蓋率更高
          • Web H5可以更好的實現發布更新,跨平臺也更加優秀,但性能較低,特性也受限

          混合開發的意義就在于吸取兩者的優點,而且隨著手機硬件的升級迭代、系統(Android 5.0+、ISO 9.0+)對于Web特性的較好支持,H5的劣勢被逐漸縮小。


          JSBridge 的概念和作用

          • 通信橋梁:JSBridge 充當了 Web 應用和原生應用之間的通信橋梁。通過 JSBridge,我們可以在 web 和原生代碼之間進行雙向通信,使這兩者能夠互相調用和傳遞數據。
          • 原生功能調用:使用 JSBridge,我們可以在 JavaScript 中調用原生應用中的功能。我們可以通過 web 來觸發原生應用中的特定操作,如打開相機、發送通知、調用硬件設備等。
          • 數據傳遞:JSBridge 使得 JavaScript 和原生代碼之間可以方便地傳遞數據。意味著我們可以在 web 和原生代碼之間傳遞復雜的數據結構,如對象、數組等,以滿足應用的功能需求。
          • 回調機制:JSBridge 支持回調機制,使得在原生代碼執行完某些操作后可以通知 JavaScript,并傳遞相應的結果。


          為什么在混合應用開發中 JSBridge 如此重要

          • 跨平臺開發:JSBridge 允許我們在混合應用中使用一套代碼同時運行在不同的平臺上。這意味著我們可以使用 Web 技術來開發應用的核心邏輯,并在需要時通過 JSBridge 調用原生功能,從而實現跨平臺開發,提高開發效率。
          • 原生功能擴展:使用 JSBridge,我們可以充分利用原生平臺提供的功能和能力,例如訪問硬件設備、調用系統 API 等。這使得我們可以為應用添加更多豐富的功能,提升用戶體驗。
          • 靈活性和擴展性:JSBridge 提供了一種靈活和可擴展的方式來實現 Web 和原生代碼之間的通信。開發人員可以根據應用的需求隨時添加新的原生功能,并通過 JSBridge 在 JavaScript 中調用這些功能,從而實現應用的功能擴展和升級。

          JSBridge 做了什么

          在Hybrid模式下,H5會需要使用Native的功能,比如打開二維碼掃描、調用原生頁面、獲取用戶信息等,同時Native也需要向Web端發送推送、更新狀態等,而JavaScript是運行在單獨的 JS Context 中(Webview容器)與原生有運行環境的隔離,所以需要有一種機制實現Native端和Web端的 雙向通信 ,這就是JSBridge:以JavaScript引擎或Webview容器作為媒介,通過協定協議進行通信,實現Native端和Web端雙向通信的一種機制。


          通過JSBridge,Web端可以調用Native端的Java接口,同樣Native端也可以通過JSBridge調用Web端的JavaScript接口,實現彼此的雙向調用。


          JSBridge 實現原理

          把 Web 端和 Native 端的通信比作 Client/Server 模式。JSBridge 充當了類似于 HTTP 協議的角色,實現了 Web 端和 Native 端之間的通信。


          將 Native 端原生接口封裝成 JavaScript 接口:在 Native 端將需要被調用的原生功能封裝成 JavaScript 接口,讓 JavaScript 代碼可以調用。JavaScript 接口會被注冊到全局對象中,以供 JavaScript 代碼調用。


          將 Web 端 JavaScript 接口封裝成原生接口:這一步是在 Web 端將需要被調用的 JavaScript 功能封裝成原生接口。這些原生接口會通過 WebView 的某些機制暴露給原生代碼,以供原生代碼調用。


          Native -> Web

          Native端調用Web端,JavaScript作為解釋性語言,最大的一個特性就是可以隨時隨地地通過解釋器執行一段JS代碼,所以可以將拼接的JavaScript代碼字符串,傳入JS解析器執行就可以,JS解析器在這里就是webView。


          1. Android

          Android 提供了 evaluateJavascript 來執行JS代碼,并且可以獲取返回值執行回調:

          String jsCode = String.format("window.showWebDialog('%s')", text);
          webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
          
          
            }
          });


          2. IOS

          IOS的 WKWebView 使用 evaluateJavaScript:

          [webView evaluateJavaScript:@"執行的JS代碼" 
            completionHandler:^(id _Nullable response, NSError * _Nullable error) {
            // 
          }];


          Web -> Native

          Web調用Native端主要有兩種方式:

          1. URL Schema

          URL Schema是類URL的一種請求格式,格式如下:

          <protocol>://<host>/<path>?<qeury>#fragment
            
          // 我們可以自定義JSBridge通信的URL Schema,比如:
          hellobike://showToast?text=hello


          Native加載WebView之后,Web發送的所有請求都會經過WebView組件,所以Native可以重寫WebView里的方法,從來攔截Web發起的請求,我們對請求的格式進行判斷:

          • 符合我們自定義的URL Schema,對URL進行解析,拿到相關操作、操作,進而調用原生Native的方法
          • 不符合我們自定義的URL Schema,我們直接轉發,請求真正的服務


          例如:

          get existOrderRedirect() {
              let url: string;
              if (this.env.isHelloBikeApp) {
                url = 'hellobike://hellobike.com/xxxxx_xxx?from_type=xxxx&selected_tab=xxxxx';
              } else if (this.env.isSFCApp) {
                url = 'hellohitch://hellohitch.com/xxx/xxxx?bottomTab=xxxx';
              }
              return url;
            }


          這種方式從早期就存在,兼容性很好,但是由于是基于URL的方式,長度受到限制而且不太直觀,數據格式有限制,而且建立請求有時間耗時。


          2. 在Webview中注入JS API

          通過webView提供的接口,App將Native的相關接口注入到JS的Context(window)的對象中

          Web端就可以直接在全局 window 下使用這個暴露的全局JS對象,進而調用原生端的方法。


          Android注入方法:

          • 4.2 前,Android 注入 JavaScript 對象的接口是 addJavascriptInterface 但是這個接口有漏洞
          • 4.2 之后,Android引入新的接口 @JavascriptInterface 以解決安全問題,所以 Android 注入對對象的方式是有兼容性問題的。


          IOS注入方法:

          • iOS的UIWebView:JavaSciptCore 支持 iOS 7.0 及以上系統
          • iOS的WKWebView:WKScriptMessageHandler 支持 iOS 8.0 及以上系統


          例如:

          • 注入全局對象
          // 注入全局JS對象
          webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
          
          
          class NativeBridge {
              private Context ctx;
              NativeBridge(Context ctx) {
                  this.ctx = ctx;
              }
          
          
              // 綁定方法
              @JavascriptInterface
              public void showNativeDialog(String text) {
                  new AlertDialog.Builder(ctx).setMessage(text).create().show();
              }
          }
          
          


          • Web調用方法:
          // 調用nativeBridge的方法
          window.NativeBridge.showNativeDialog('hello');


          H5具體實現

          將功能抽象為一個 AppBridge 類,封裝兩個方法,處理交互和回調。


          具體步驟:

          • 首先需要定義一個 JavaScript 類或者對象來封裝 JSBridge 方法。
          • 在 JavaScript 類或對象的構造函數中,初始化橋接回調的方法。這個方法負責接收來自原生應用的回調數據,并根據回調數據中的信息執行相應的操作。
          • 調用原生方法:定義一個方法,用于在 JavaScript 中調用原生方法。這個方法需要接收原生類的映射、要調用的原生方法名以及傳遞給原生方法的參數,并將這些信息傳遞給原生應用。
          • 處理原生回調:在初始化橋接回調的方法中,需要定義處理原生回調的邏輯。當收到原生應用的回調數據時,根據回調數據中的信息執行相應的操作,比如調用 JavaScript 中注冊的回調函數,并傳遞執行結果或錯誤信息等。


          具體實現代碼:

          • 調用原生方法:
          // 定義一個名為 callNative 的方法,用于在 JavaScript 中調用原生方法
          callNative<P, R>(classMap: string, method: string, params: P): Promise<R> {
              return new Promise<R>((resolve, reject) => {
                  // 生成一個唯一的回調 ID
                  const id = v4();
                  // 將當前的回調函數保存到 __callbacks 對象中,以 callbackId 作為鍵
                  this.__callbacks[id] = { resolve, reject, method: `${classMap} - ${method}` };
                  // 構造通信數據,包括原生類映射、要調用的方法、參數和 callbackId 
                  const data = {
                      classMap,
                      method,
                      params: params === null ? '' : JSON.stringify(params),
                      callbackId: id,
                  };
                  const dataStr = JSON.stringify(data);
                  // 根據當前環境判斷是 iOS 還是 Android,并調用相應平臺的原生方法
                  if (this.env.isIOS && isFunction(window?.webkit?.messageHandlers?.callNative?.postMessage)) {
                      // 如果是 iOS 平臺,則調用 iOS 的原生方法
                      window.webkit.messageHandlers.callNative.postMessage(dataStr);
                  } else if (this.env.isAndroid && isFunction(window?.AppFunctions?.callNative)) {
                      // 如果是 Android 平臺,則調用 Android 的原生方法
                      window.AppFunctions.callNative(dataStr);
                  }
              });
          }


          • 回調處理:
          // 初始化橋接回調函數,該參數在 constructor 中調用
          private initBridgeCallback() {
              // 保存舊的回調函數到 oldCallback 變量中
              const oldCallback = window.callBack;
              // 重新定義 window.callBack 方法,用于處理原生應用的回調數據
              window.callBack = (data) => {
                  // 如果存在舊的回調函數,則調用舊的回調函數
                  if (isFunction(oldCallback)) {
                      oldCallback(data);
                  }
                  // 獲取原生應用的回調信息,包括數據和回調 ID
                  console.info('native callback', data, data.callbackId);
                  // 從回調數據中獲取回調 ID
                  const { callbackId } = data;
                  // 根據回調 ID 查找對應的回調函數
                  const callback = this.__callbacks[callbackId];
                  // 如果找到了對應的回調函數
                  if (callback) {
                      // 如果回調數據中的 code 為 0,則表示執行成功,調用 resolve 方法處理成功的結果
                      if (data.code === 0) {
                          callback.resolve(data.data);
                      } else {
                          // 否則,表示執行失敗,構造一個錯誤對象并調用 reject 方法處理錯誤信息
                          const error = new Error(data.msg) as Error & {response:unknown};
                          error.response = data;
                          callback.reject(error);
                      }
                      // 刪除已經處理過的回調函數
                      delete this.__callbacks[callbackId];
                  }
              };
          }


          • 使用:
          // 調用原生方法的封裝函數
          callNative<P, R>(classMap: string, method: string, params: P) {
              // 從容器中解析出 AppBridge 實例
              const bridge = container.resolve<AppBridge>(AppBridge);
              // 使用 bind 方法將 AppBridge 實例中的 callNative 方法綁定到 bridge 對象上,并保存到 func 變量中
              const func = bridge.callNative.bind(bridge);
              // 調用 func 方法,并傳入 classMap、method 和 params 參數,實現調用原生方法的功能
              return func<P, R>(classMap, method, params);
          }
          
          
          
          
          // 打開 webview
          // 調用 callNative 方法,傳入參數 url,classMap 為 'xxxxx/hitch',method 為 'openWebview'
          openWebView(url: string): Promise<void> {
              return this.callNative<{url:string}, void>('xxxxx/hitch', 'openWebview', { url });
          }
          
          
          
          
          // 獲取駕駛證 OCR 信息
          getDriverLicenseOcrInfo(
              params: HBNative.getDriverLicenseOcrInfo.Params,
          ): Promise<HBNative.getDriverLicenseOcrInfo.Result> {
              // 調用 callNative 方法,傳入參數 params,classMap 為 'xxxxx/hitch',method 為 'getOcrInfo'
              // 返回一個 Promise 對象,該 Promise 對象用于處理異步結果
              return this.callNative<
                  HBNative.getDriverLicenseOcrInfo.Params,
                  HBNative.getDriverLicenseOcrInfo.Result>(
                      'xxxxx/hitch', 'getOcrInfo', params,
                  );
          }



          作者:佟健

          來源-微信公眾號:哈啰技術

          出處:https://mp.weixin.qq.com/s/h6vlkf5rgyI9GpEpPxO9cg

          素顯示模式就是元素(標簽)以什么方式進行顯示,比如div標簽自己占一行,一行可以放多個span標簽。

          HTML 元素分為塊元素、行內元素、行內塊元素,它們具體的用法和區別如下:

          塊元素:

          塊元素是用于定義一個獨立的區域,比如一個段落、一個標題、一個表格等。

          常見的塊元素有:h1-h6、div(最典型)、p、ol、ul、li等。

          特點

          • 塊元素獨占一行。
          • 高度、寬度、外邊距、內邊距都可以控制。
          • 寬度默認是容器(父級寬度)的100%。
          • 是一個容器及盒子,里面可以放行內或者塊級元素。
          • 文字類的塊元素內不能使用塊級元素,如h1-h6 和 p 標簽,里面不能放其他類型的塊級元素。

          行內元素:

          行內元素是用于在同一行內顯示文本,并不需要包含在其他元素內。

          常用的行內元素有:a、strong、em、del、ins、b、i、u、span(最典型)等。

          特點:

          • 相鄰行內元素在一行上,一行可以顯示多個行內元素。
          • 寬、高直接設置是無效的。
          • 默認寬度就是它本身內容的寬度。
          • 行內元素只能容納文本和其他行內元素。

          注意:

          • 鏈接里面不能再放鏈接。
          • 特殊情況下a標簽中可以放塊級元素,但給a標簽轉換一下塊級模式最安全。

          行內塊元素:

          行內塊元素是用于在同一行內顯示文本,并不需要包含在其他元素內,但是可以包含圖片或其他多個文本元素。

          常用的行內塊元素有:

          • <blockquote>:用于引用其他文檔中的文本,可以設置引用文本的樣式。
          • <figure>:用于在同一行內顯示圖片或其他多個文本元素,可以設置圖片或其他多個文本元素的樣式。

          特點:

          在行內元素中有幾個特殊的標簽,img、input、td,它們同時具有行內元素和塊元素的特點。

          這三種元素的主要區別是:

          1.塊元素定義了一個獨立的區域,不會和其他元素融合在一起,而行內元素和行內塊元素都屬于行內元素,它們都可以包含其他元素。

          2.塊元素可以設置高度和外邊距,而行內元素和行內塊元素都不能設置高度和外邊距。

          3.塊元素只能包含文本或其他圖片等元素,而行內元素和行內塊元素都可以包含其他任意類型的元素。

          4.塊元素的外邊距、填充、寬度等屬性可以根據實際需求設置,而行內元素和行內塊元素的樣式則完全根據實際需求設置。

          顯示模式轉換

          特殊情況下,我們需要元素模式的轉換,比如要增加a標簽鏈接的觸發范圍。

          • 轉換為塊元素:display:block;
          • 轉換為行內元素:display:inline;
          • 轉換為行內塊:display:inline-block;

          今開發出一款成功的APP已經成為公司運營的重要一環,但APP的開發模式很多,不同模式的實現機理不同,因而會在APP的開發成本、運行性能、升級維護和用戶體驗等方面造成不同的影響。


          目前來說,APP的主要開發模式主要分為四類:


          第一類是原生應用開發,即NativeApp。基于Android平臺的Java語言開發和基于iOS平臺的Objective-C語言/Swift語言開發。


          第二類是移動網頁應用開發,即WebApp。網頁應用開發是利用Web技術,使用HTML、CSS和JavaScript開發用于移動端顯示的網頁。


          第三類是把NativeApp和WebApp結合的混合開發模式,即HybridApp。


          第四類是基于ReactNative框架的JavaScript語言開發的類原生應用,即ReactNativeApp。

          下面主要是對比較常用到的NativeApp開發模式做一個具體的分析,希望有助于大家對于APP開發有一個了解!


          NativeApp是本地開發方式,基于手機操作系統進行開發,利用Java、Objective-C或Swift語言進行程序開發,然后編譯成字節碼或機器碼后經操作系統調度運行。


          由于操作系統不同以及開發語言的不同,當今最流行的兩大移動端平臺Android和iOS各有自己的一套獨立的開發模式,兩大平臺差異較大。


          現以iOS平臺為例簡述下NativeApp的開發模式。


          首先需要在MacOSX系統上安裝蘋果公司開發的IDE――Xcode。Xcode內擁有開發iOSNativeApp快捷高效的CocoaTouch框架,是開發原生iOS程序的不二選擇。


          在Xcode中創建一個SingleViewApplication工程后選用Swift語言開發,會自動生成以Main.storyboard、AppDelegate.swift、ViewController.swift開發核心的若干文件。其中Main.storyboard用于構建各個頁面之間跳轉關系和具體頁面布局,在Xcode的右下角有可拖拽的若干控件,拖拽控件到Main.storyboard后結合AutoLayout為各個控件添加各種布局約束,來保證界面在不同大小的屏幕上的適配。


          AppDelegate.swift是為iOS程序做一些初始化設置,主要用于在APP啟動時為重要的數據結構進行初始化,以及響應APP運行時事件,如程序啟動、程序運行內存不足、程序切換等。程序員可在AppDelegate.swift中的相關函數里對事件響應進行操作。


          ViewController.swift就是iOS程序中的初始界面,是UI控件和程序邏輯的控制器。程序員主要通過ViewController.swift中的viewDidLoad和viewWillAppear等方法對iOS程序的數據層和視圖層進行控制,來表達APP的業務邏輯。


          NativeApp開發模式的優勢在于NativeApp是編譯后的文件,執行速度快,界面動畫十分流暢,對網絡的依賴性小,用戶體驗很好。基于平臺層可以非常方便地調用操作系統提供的各種功能,如調用攝像頭、推送信息和讀取本地通訊錄等。


          NativeApp開發模式的劣勢主要是:開發周期長,兩套獨立的知識體系復雜且學習成本高,依賴操作系統而無法進行跨平臺開發,APP版本升級繁瑣需要重新把源文件編譯打包再由用戶下載覆蓋安裝。


          移動互聯網的浪潮,推動著移動開發技術的不斷發展,移動App的開發模式也豐富了起來。每個開發模式都有自身的優勢與不足,在實際開發前應仔細權衡開發人員的知識體系和開發成本。


          延伸閱讀

          ————————


          “廣州優匠科技”是一家由經驗豐富的技術設計開發團隊創辦的軟件外包公司。專注于小程序、公眾號、APP、軟件系統等相關技術的定制與開發。


          優匠科技深耕在互聯?服務領域多年,有足夠的成功案例與資深專業的技術團隊,能夠幫助企業解決很多技術上的難題。多年來一直都是用“匠心精神”去做好技術和產品服務。


          如有興趣合作或是產品開發需求的朋友,歡迎前來咨詢了解!!


          主站蜘蛛池模板: 美女免费视频一区二区| 久久精品岛国av一区二区无码| 波多野结衣中文一区二区免费| 蜜桃无码一区二区三区| 亚洲爽爽一区二区三区| 国产精品综合一区二区三区| 精品国产AV一区二区三区| 无码人妻精品一区二区三区99仓本 | 亚洲一区中文字幕在线电影网| 视频一区在线免费观看| 国产一区二区草草影院| 亚洲一区二区三区乱码在线欧洲| 奇米精品视频一区二区三区| 亚洲av鲁丝一区二区三区| 中文字幕精品无码一区二区三区| 精品国产不卡一区二区三区| 精品成人一区二区三区免费视频| 无码人妻精一区二区三区| 国产一区二区不卡老阿姨| 亚洲愉拍一区二区三区| 无码夜色一区二区三区| 国产福利酱国产一区二区| 在线观看精品视频一区二区三区| 无码一区二区三区爆白浆| 亚洲AV成人一区二区三区观看| 亚洲爆乳精品无码一区二区| 好爽毛片一区二区三区四无码三飞| 日本高清一区二区三区| 少妇人妻精品一区二区| 国产一区韩国女主播| 一区二区三区四区在线观看视频| 日本精品一区二区三区在线视频一| 国产成人高清亚洲一区91| 视频一区二区三区免费观看| 亚洲国产成人久久一区WWW | 久久精品国产一区二区三区| 99精品高清视频一区二区| 亚洲乱码国产一区三区| 亚洲bt加勒比一区二区| 51视频国产精品一区二区| 国产乱码精品一区二区三区香蕉|