攜程小程式內嵌webview實踐指南

陶然陶然發表於2023-04-13

   一、背景

  這篇文章將向大家分享團隊在小程式 webview 方面的開發心得,以微信小程式為主要環境,介紹在業務開發中處理小程式webview內嵌H5所遇到的問題及解決方案。具體將從小程式平臺與H5差異、小程式內嵌webview通訊、小程式webview常見問題展開敘述。

   二、平臺差異

  下面將淺析並回顧一下小程式和H5在渲染方面的幾點差異。  

  2.1 小程式方面

  以微信小程式為例,相信今天大部分的讀者對微信小程式的系統架構都比較熟悉了,總體來講分為兩部分:

  iew 檢視端透過小程式的框架,將使用者採用 WXML 和 WXSS 描述的UI資訊處理成 H5 元素,最終交給 WebView 去渲染;

  邏輯層執行JS邏輯,並且可以呼叫具有微信開放能力的 JSAPI。邏輯和檢視分離,透過事件和資料彼此之間建立聯絡。  

  微信小程式使用 WebView 渲染,與原生客戶端的是兩套不同的檢視渲染體系。一個小程式存在多個介面,所以渲染層存在多個 WebView。邏輯層採用 JSCore 執行緒執行 JavaScript 指令碼。這兩個執行緒間的通訊經由小程式 Native 側中轉,邏輯層傳送網路請求也經由 Native 側轉發。

  如此設計的初衷是為了管控和安全,微信小程式阻止開發者使用一些瀏覽器提供的,諸如跳轉頁面、操作 DOM、動態執行指令碼的開放性介面。將邏輯層與檢視層進行分離,檢視層和邏輯層之間只有資料的通訊,可以防止開發者隨意操作介面,更好地保證了使用者資料安全。同時小程式設計一套元件框架—— Exparser ,基於這個框架內建了一套元件,以涵蓋小程式的基礎功能,便於開發者快速搭建出任何介面,同時也提供了自定義元件的能力,開發者可以自行擴充套件更多的元件,以實現程式碼複用。

  值得一提的是,內建元件有一部分較複雜元件是用客戶端原生渲染的,同時微信團隊又透過結合 Flutter 和 LV-CPP,把實現程式碼收斂在 C++ 和 Dart 上,進一步簡化了基於小程式技術棧實現跨平臺業務開發的框架維護成本,以提供更好的效能。

  2.2 小程式webview內嵌H5

  H5頁面投放在小程式WebView,在配置完合法域名後,即可在小程式應用中展示。那麼,針對不同廠商小程式,可能法務、廠商合規有所差異,需要H5判斷所在的環境,去呼叫不同 api 方法,展示不同的業務頁面。

  在攜程內部封裝了小程式CWX的SDK,小程式端主要採用原生+Taro框架,H5這塊主要是NFES(React)和Vue,無論是哪一段端都透過一個CWX來連線,內部封裝了各端通用的功能比如登入、釋出、支付、個人中心等功能,這些功能都可以直接透過CWX這個中介軟體進行呼叫。

  並且,H5在檢測到當前處於小程式webview環境下時,會根據環境非同步載入SDK檔案、及其廠商的JS-SDK,初始化小程式版本wx.config。這裡的關鍵點是我們要做個api呼叫的佇列,因為sdk載入非同步的過程,如果期間頁面內發生了api呼叫,那肯定得不到正確的響應。因此要做個呼叫佇列,當sdk初始化完畢之後再處理這些呼叫。其實CWX原理很純粹,如果你想實現多端適配,那麼只需要根據所在的環境去載入不同的sdk就可以了。

  > 下面簡要列舉一下工作中常用的幾個小程式環境判斷:  

  使用時的注意事項:

  使用前,最好查閱相應小程式的文件,因為各個小程式對API的支援程度不同。引用bridge.js的方式視情況而定,因為 bridge.js 引入JSSDK的方式是 為 head標籤新增 script標籤,若在 head標籤中引入bridge.js,就會報錯若開啟h5,顯示“頁面訪問受限”之類的提示資訊,可嘗試下方的操作:(這種情況,一般是開啟測試環境的h5 url 時出現)勾選IDE中的“忽略webview域名合法性檢查” 和 “忽略request域名合法性檢查”。

  【快應用相關】

  目前Vivo、Oppo、華為三家廠商已支援新版快應用,Vivo、OPPO已上線,華為正在測試中,小米不支援。對於新版快應用,若H5頁面需要呼叫新版快應用JS-SDK中提供的API,需要提前將該H5連結的域名配置到可信任的網址裡(應寫成正規表示式的形式進行配置)。

  【頭條相關】

  頭條小程式的redirectTo、navigateTo 等頁面跳轉的 api 只支援 url 為 / 開始的絕對路徑。

  【支付寶相關】

  目前的1.0.73版 bridge.js 判斷是否處於支付寶小程式的方法,會將h5處於支付寶小程式、h5處於支付寶內建瀏覽器都判斷為處於支付寶小程式內。因此,在調my.XXXX之前,需要先調環境工具函式判斷一下,確保確實是處於支付寶小程式內,而非支付寶內建瀏覽器內。

   三、小程式內嵌webview通訊

  3.1 小程式中h5頁面onShow和跨頁面通訊的實現

  首先想到的是onShow方法的實現,之前有人提議用visibilitychange來實現onShow方法,但調研過後,發現這種方式在ios中表現符合預期,但是在安卓手機裡,是不能按預期觸發的。

  於是就有了下面的方案,這個方案需要h5和小程式的webview都做處理。核心思想:利用webview的hash特性。

  小程式透過hash傳參,頁面不會更新(這個和瀏覽器一樣)

  h5可以透過hashchange捕獲最新引數,進行自定義邏輯處理

  最後執行window.history.go(-1)

  為什麼要執行window.history.go(-1) ? 因為hash變更會導致webview歷史棧長度+1,使用者需要多一次返回操作。但這一步明顯是多餘的。同時window.history.go(-1)後,會把webview在hash中新增的引數去掉,還能保證和之前的url一致。

  3.2 注意點

  出於平滑接入的考慮,不能上來搞一刀切,要保證現有頁面不再做任何修改的情況下繼續訪問。新能力要透過額外引數區分,如:檢測url中的query部分,帶有 __isonshowpro=1 再進行透過hash方式傳參。改造原有邏輯,讓__isonshowpro=1時,hash處理邏輯優先順序最高引數定義,在前面加入了兩個下劃線,目的是為了區分url中正常的引數。我們來看看h5端的sdk是怎麼實現的。  

  總結下來是兩點:

  onShow方法的實現

  繫結一個hashchange事件(這裡做了防止重複繫結事件的處理),將傳入的onShow自定義事件快取在一個陣列中,hashchange觸發時,根據特有的標誌位__isonshow和__wachangehash確定是否觸發。

  serviceDone方法的實現

  觸發條件:immediately表示最近的一次onShow觸發,或者自己指定url透過wx.miniProgram.postMessage傳送資料。

  瀏覽器訪問資源是透過 URL 地址,如果內嵌 H5 的地址不發生變化,那麼 web-view 訪問資源會從快取裡取,而快取裡並沒有最新的資料,這就導致了服務端的最新資源根本無法到達瀏覽器,這也解釋了為什麼修改 Nginx 的 Cache-Control 配置也無法生效的原因。

  所以,要想徹底解決及時重新整理,必須讓 web-view 去訪問新的地址。我們假定小程式訪問的 URL 地址為: ,其中 101 就是構建的一個版本號,每次遞增,保證次次不同即可。

   四、webview常見難題與解決方案

  小程式和h5 之間的通訊基本上常用兩種方式,一個是postMessage,這個方法大家都知道,只有在三種情況才可以觸發,後退、銷燬和分享。但也有個問題,就是需要注意這個方法是基礎庫1.7.1才開始支援的,1.7.1以下就只能透過第二種方法來傳遞資料,也就是設定和檢測webview元件的url變化,類似pc時代的iframe的通訊方式。

  sdk這塊怎麼做呢,定義一個share方法,首先需要檢測下基礎庫版本,看是否支援postMessage,如果支援直接呼叫,如果不支援,把分享引數拼接到url當中,然後進行一次過載。也就是說,透過url傳遞資料有個缺點,就是頁面可能需要重新整理一次才能設定成功。

  目前在webview環境下支援支援的幾種通用業務:

  4.1 左上角返回

  在訪問小程式webview頁面時,首先進入的是一個空白的中轉頁,然後進入h5頁面,這樣左上角就會出現返回按鈕了,當使用者按左上角的返回按鈕時候,頁面會被過載到小程式首頁去,這個看似簡單又微小的動作,對業務其實有很大的影響。

  經過我們的資料統計發現,左上角返回按鈕點選率高達70%以上,因為這種落地頁一般是被使用者分享出來的,以前純h5的時候只能透過左上角返回,所以在小程式裡使用者也習慣如此;第二個數字,過載到首頁以後,後續頁面訪問率有10%以上,這兩個數字對業務提升其實蠻大的。其實現原理很簡單,都是透過第二次觸發onShow時進行處理。

  4.2 H5和小程式登入態同步問題

  分兩種情況,接入的H5可能一開始就需要登入,也可能開始不需要登入態中途需要登入,這兩種情況我們約定了h5透過自己的url上一個引數進行控制。  

  一開始就需要登入態的情況,具體來講就是在載入webview之前,首先進行授權登入,然後把登入資訊拼接到url裡面,再去來載入webview,在h5裡面透過adapter來把登入資訊提取出來並且存到cookie裡,這樣h5一進來就是有登入態的。

  一開始不需要登入態的情況,一進入小程式就直接透過webview載入h5,h5呼叫login方法的時候,把needLogin這個引數拼接到url中,然後利用api進行過載,就走了第一種情況進行授權登入了。

  Q:可能出現的登入同步問題

  A: 跳到個人頁登入完成,此時是新開的webview同步兩端登入態,點返回,到上一個webview,此時這個webview巢狀的首頁,沒有觸發react-imvc onshow事件。這個頁面是老的,退出登入也是一樣,所以在首頁會去跳h5的登入而不是小程式登入,導致登入不同步。

  解決思路:需要返回首頁刷一下h5頁面。

  誤區:直接在個人登入之後,relaunch到首頁,會導致沒有直接呼叫登出webview把token置換,無法退出。

  解決方案:判斷從個人頁返回的時候,設定webview的url加個引數,重新刷一下。

  4.3 Webviwe分享  

  在沒接入websocket之前,小程式主要透過bind。首先透過bindmessage事件接收h5傳回來的資料,然後在使用者分享的時候onShareAppMessage判斷有沒有回傳的資料,如果沒有就到webviewurl當中取,否則就是用預設分享資料。

  4.4 支付

  1)webview頁面重新整理問題

  因為小程式webview裡面不支援直接調起微信支付,所以基本上需要支付的時候,都需要來到小程式裡面,支付完再回去。上面做好了以後,在h5這塊呼叫一句話就可以了。

  針對產品有大量內嵌H5頁面的情況下,最好根據業務分兩種支付頁面,一是有的業務h5有自己完善的交易體系,下單動作在h5裡面就可以完成,他們只需要小程式付款,因此我們有一個精簡的支付頁,進來直接就拉起微信支付。

  還有一種情況是業務需要小程式提供完整的下單支付流程,透過直接進入小程式的收銀臺來,圖上是sdk裡面的基本邏輯,透過payOnly這個引數來決定進到哪個頁面。再看下小程式裡面精簡支付怎麼實現的,onload之後直接呼叫api拉起微信支付,支付成功以後根據h5傳回來的引數,如果是個小程式頁面,直接跳轉過去,否則就重新整理上一個webview頁面,然後返回回去。

  新的問題與挑戰:webview返回上一頁資料重新整理問題

  有客戶反饋在A頁面點選任務後跳轉到B頁面,待任務完成後,手機手勢左滑返回或點選預設導航欄的左上角返回,上一個頁面不會觸發任務的更新。原因是上一個頁面已經初始化並沒有執行重渲染,在APP環境下JSBridge 沒有提供偵聽手勢左滑返回、左上角物理返回的回撥事件,且在小程式webview頁面也會遇到上述同樣的情況。

  由於微信並沒有提供偵聽手勢左滑返回、左上角物理返回的,且webview頁面也不支援自定義導航欄,這導致下一個頁面觸發的新事件,在返回上個頁面時 無法做到針對性的更新。前期可以簡單粗暴地透過約定引數 doRefreshWhileBack=true 作為options,來透過webview頁面每次onShow重新整理頁面,但是重新整理整個頁面的成本太大,且使用者體驗不好。

  2)引入websocket

  帶著這些疑問,我們進行一系列的嘗試與試驗,最終採用了 websocket 的方式,解決並封裝出我們市場業務的輕量的websocket服務,主要用於解決webview跨頁面通訊和遊戲方面的業務。

  在這個過程中,我們總結出了一些經驗,希望能給從事相關研究的同學帶來一些幫助。上述做法是針對不同的應用環境,分別使用或約定不同的api派發給各自的事件系統,從而解決頁面物理回退時頁面不主動重新整理的方案。

  簡要介紹一下websocket,websocket標準誕生於2011年,RFC 文件編號是 6455。TML 5 規範定義了 WebSocket 協議,它可以透過 HTTP 的埠(或者 HTTPS 的埠)來完成,從而最大程度上對 HTTP 協議通透的防火牆保持友好。但是,它是真正的雙向、全雙工協議,也就是說,客戶端和服務端都可以主動發起請求,回覆響應,而且兩邊的傳輸都互相獨立。和上文的 Comet 不同,WebSocket 的服務端推送是完全可以由服務端獨立、主動發起的,因此它是服務端的"真 Push"。

  WebSocket 是一個可謂"科班出身"的二進位制協議,也沒有那麼大的頭部開銷,這樣就解決了接線員要反覆解析HTTP協議,還要檢視identity info的資訊,因此它的傳輸效率更高。同時,和 HTTP 不一樣的是,它是一個帶有狀態的協議,雙方可以約定好一些狀態,而不用在傳輸的過程中帶來帶去。而且,WebSocket 相比於 HTTP,它沒有同源的限制,服務端的地址可以完全和源頁面地址無關,即不會出現的瀏覽器"跨域問題"。  

  優勢:

  訊息實時:真正的雙向、全雙工協議,完全的服務端推送保證了資料的時效性。

  通訊高效:可以由客戶端和服務端主動傳送請求,不會像輪詢那樣產生大量無效傳輸報文。

  協議支援:標準誕生較早,瀏覽器支援度高,且沒有同源策略的限制。

  劣勢:

  開發與維護成本:伺服器長期維護長連線需要一定的成本,且受網路限制比較大,需要處理好重連。

  藉助websocket的輔助,在小程式webview內嵌H5的業務場景中,可做的事情就更多了。在市場的webview容器載入流程中。  

  3)websocket背景下的webview通訊實踐

  小程式webview初始化並在onLoad階段透過 options.useMktsocket 判斷是否需要載入 socket,同時判斷應用環境透過 wx.connectSocket api 連線不同的 socket 服務;

  初始化webview socket服務,接受伺服器訊息-對伺服器訊息進行甄別,如果H5頁面透過socket傳遞給webview容器的資料data格式符合預期,且H5環境下登入態中的openId與小程式環境一致,則認為此次通訊合法;

  webview容器中繫結了 小程式分享miniShare 、小程式訂閱openScribe、 健康檢查health等常用業務API,用於處理廣告、訂閱、任務更新等業務實時回撥;H5業務可透過此介面設定觸發小程式原生頁面的一些原生功能,為上層業務提供服務。

  H5頁面就可以透過 socket 通訊更改並呼叫小程式的膠囊欄分享、通知webview容器頁面呼叫小程式廣告、也可以呼叫喚起小程式頁面中的分享元件皮膚、觸發左上角物理返回時及時通知H5頁面觸發回撥等諸多業務;同時小程式容器頁面原生事件完成後(比如廣告、分享)再次透過socket返回給H5頁面的回撥,實現小程式webview跨頁面的實時通訊。

  在websocket加持下,此時的小程式webview賦予了更多和H5通訊的功能。

  4.5 自定義分享皮膚

  H5頁面可以透過 websocket 通訊更改並呼叫小程式的分享引數,不再依賴於頁面options引數,可以呼叫在webview頁面封裝的分享皮膚,提供更加靈活的分享方式。  

  

  4.6 H5呼叫小程式原生的激勵廣告

  H5頁面可以透過 websocket 通訊呼叫小程式原生的激勵廣告。  

  4.7 任務體系中使用者任務元件狀態的更新

  使用者在訪問載入了webview-h5的頁面會與websocket的server A伺服器連線、小程式原生頁面與server B連線時,這兩個頁面因為在不同的容器下,所以無法通訊和告知;但是隻要這兩個頁面載入的是同一個市場的websocket服務,服務端可以設定共享一個redis,透過redis的釋出訂閱功能,連通叢集內部各個機器,那麼在頁面前進、回退時都可以繫結對應的回撥事件,實現任務元件的靈活更新,給使用者展示最新的任務狀態。

   五、總結

  在處理小程式webview的業務方面,可以透過封裝一個包含各端環境的SDK,在H5初始化時載入,打通H5和小程式webview之間的通道,實現H5控制分享、登入態同步、支付資訊同步等功能。

  在遇到跨頁面資料重新整理問題時,藉助了websocket這把利器,透過redix的釋出訂閱通知連結了websocket伺服器的頁面,實現小程式webview物理返回上一頁而資料不重新整理的問題,同時websocket使得H5與webview的通訊更加便捷靈活,擴充了H5呼叫小程式原生激勵廣告、封裝並呼叫小程式原生的分享皮膚等功能。

來自 “ 攜程技術 ”, 原文作者:市場前端;原文連結:http://server.it168.com/a2023/0413/6798/000006798815.shtml,如有侵權,請聯絡管理員刪除。

相關文章