1. 程式人生 > >【騰訊bugly乾貨分享】解耦---Hybrid H5跨平臺性思考

【騰訊bugly乾貨分享】解耦---Hybrid H5跨平臺性思考

跨平臺,是H5最重要的能力之一。而 Hybrid H5 因強依賴於具體 app,往往不具有跨平臺性。這時,將強依賴關係解耦,即可恢復 H5 的跨平臺能力。近期本人負責 手Q 紅包打賞專案的前端開發,因專案涉及到多 app 跨平臺相容,對 hybrid H5 的跨平臺性有了一定的感悟和思考。在這裡做下總結分享,希望能對大家有所收穫。

Hybrid H5 跨平臺性

進入正題之前,先解釋下本文主題的兩個名詞:

①Hybrid H5,即混合了原生能力的 H5。區別於純粹 web 端的 H5,它可呼叫原生的能力,強依賴於具體原生 app,與原生共同構建整個 app 的 UI 層,是 app UI 層很好的靈活性補充。微信和 手Q 上的 H5 業務一般都屬於 Hybrid H5 的範疇。

② 跨平臺性,即一個 H5 頁面可同時執行在多個平臺上。可執行平臺越多,跨平臺性就越強。在如今移動網際網路的發展大潮中,H5 能與體驗更優的原生終端齊步並進,其跨平臺性可謂功不可沒。

因強依賴於具體 app,Hybrid H5 往往不具有跨平臺性。

本文將從 Hybrid H5 與原生的通訊原理出發,逐步探討如何通過解耦來恢復 Hybrid H5 的跨平臺性。

Hybrid H5 與原生的通訊原理

原理圖

從原理圖中,有4個關鍵點:

1個通訊媒介——原生自定義的通訊協議;

以及圍繞著通訊媒介執行的3個通訊行為——觸發、呼叫、回撥。

關鍵點詳解

①通訊媒介——原生通訊協議

:原生自定義的偽協議,一般會定義成與 http 協議類似的格式:

協議名://介面路徑?引數1=XXX&引數2=XXX&引數3=XXX#callback

其中:

a、協議名:app 自定義的協議名,用於H5觸發行為的監控捕獲,如 手Q 使用的 jsbridge://;
b、介面路徑:原生具體能力路徑,不同原生能力路徑不同;
c、引數1=XXX&引數2=XXX&引數3=XXX#callback:H5傳參與回撥方法標識;

根據通訊協議規範,即可針對不同的原生能力給H5提供不同的呼叫地址,如:

jsbridge://method?a=2&b=3#h5MethodTag

②通訊行為——觸發:能被原生監聽並捕獲截攔的H5行為,都可以作為原生通訊協議的觸發行為。

Hybrid H5 的這類行為有 console.log、alert、confirm、prompt、location.href 等。將原生協議內容通過其中的某一行為觸發,即可被原生正確捕獲並解析。如:

location.href ='jsbridge://method?a=2&b=3#h5MethodTag'

H5呼叫後,原生終端會捕獲到內容:jsbridge://method?a=2&b=3#h5MethodTag

③通訊行為——呼叫:原生終端根據 H5 傳過來的內容,解析匹配後會路由到具體處理方法,執行原生能力邏輯。以 ios 為例(swift 語言),“呼叫”邏輯如下:

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) - Bool {
    let url = request.URL //url
    let scheme = url ? .scheme //協議名
    let method = url ? .host //介面路徑
    let query = url ? .query //引數

    if url != nil && scheme == "jsbridge" {
        /*根據method路由*/
        switch method!{
            case "method":
                    self.method()
            case "openTenpayView":
                    self.openTenpayView()
            ...其他方法...
            default:
        }
        return false
    } else {
        return true
    }
}

原生終端根據捕獲到的協議內容,進行解析獲取,若偽協議為原生指定的偽協議(“jsbridge”),就會根據 method 內容和 query 引數進行路由操作,尋找具體的方法執行邏輯。否則,忽略處理,按照 webview 原有跳轉邏輯處理。以第②步觸發的偽協議內容為例,在本例“呼叫”程式碼中被原生捕獲後,會路由執行邏輯:self.method();

④通訊行為——回撥:原生根據 H5 傳過來的內容,捕獲 js 回撥函式方法名,在原生邏輯執行結束後,將執行結果帶到回撥函式中並執行 js 回撥函式。通過在第③步“呼叫”執行完後,ios 會呼叫 js 回撥函式 H5MethodTag:

/*解析到H5的回撥函式名為H5MethodTag(#號後內容),回撥執行js的方法*/
webview.stringByEvaluatingJavaScriptFromString("H5MethodTag(data)") 

通過以上4個關鍵點,即可做到 H5 與原生終端的相互通訊,完成H5對原生能力的呼叫。

初次解耦:app 內跨平臺——建立jsapi解耦通訊邏輯、封裝平臺差異

由上述通訊原理了解到,Hybrid H5 直接呼叫定義好的原生通訊協議,即可完成通訊全過程。但這裡有一個明顯的問題,即 Hybrid H5 會強耦合於當前平臺。不說跨 app 了,app 內跨平臺(android/ios/wp)都會顯示吃力。這裡面有很多原因,其中一個較明顯的原因在於,不同平臺 app 開發團隊通訊協議規範定義存在不一致。再者,H5 業務程式碼上滿滿的類似 jsonp 的協議呼叫,也並不好維護。

要達到 Hybrid H5 在 app 內跨平臺,業界常見做法是 app 對外提供 jsapi。通過 jsapi 將各平臺協議規範差異進行封裝,解耦通訊邏輯,並以函式介面的方式提供給 Hybrid H5 呼叫。jsapi 介面一般會定義成如下格式:

ns.method({
    /*cfg引數物件*/
}, function(data) {
    /*回撥*/
})

原理圖

**原理核心:**H5 與原生通訊之間增加一層 jsapi,jsapi 完成三大行為:api 介面建立、協議 url 組裝、建立 iframe 發起偽協議請求;

因 手Q 的 jsapi 相對比較成熟,下面會以 手Q jsapi 中的核心原始碼進行分析。

**①api介面建立:**js 函式介面封裝、平臺差異處理,方便H5函式呼叫

mqq.build('mqq.tenpay.openTenpayView', {
    iOS: function(options, callback) {
        var callbackName = callback ? mqq.callback(callback) : null;
        mqq.invokeClient('pay', 'openTenpayView', {
            'params': options,
            'callback': callbackName
        });
    },
    android: function(params, callback) {
        mqq.invokeClient('pay', 'openTenpayView', JSON.stringify(params), callback);
    },
    supportInvoke: true,
    support: {
        iOS: '4.6.1',
        android: '4.6.1'
    }
});

mqq.build 方法為 api 介面建立方法。通過傳入待建立的 jsapi 方法名(mqq.tenpay.openTenpayView)和不同平臺(android/ios)的差異處理配置。最終會生成H5可呼叫方法:

mqq.tenpay.openTenpayView({
    /*data*/
},function(ret){
    /*callback*/
})

②協議url組裝:從介面到 url 協議的轉換、回撥處理,完成協議 url 建立

第①步中,不同平臺差異處理都會呼叫 mqq.invokeClient 方法,該方法實際處理的就是原理圖中與原生通訊的過程。我們先來看協議 url 組裝的過程。

/*生成回撥索引*/
sn = storeCallback(callback);
/*協議路徑組裝*/
url = 'jsbridge://' + encodeURIComponent(ns) + '/' + encodeURIComponent(method);
/*引數組裝*/
argus.forEach(function(a, i) {
    if (exports.isObject(a)) {
    a = JSON.stringify(a);
    }
    if (i === 0) {
        url += '?p=';
    } else {
        url += '&p' + i + '=';
    }
    url += encodeURIComponent(String(a));
});
/*回撥函式索引組裝*/
url += '#' + sn;
/*連結呼叫*/
result = openURL(url, ns, method);

協議 url 組裝的過程實際上是對傳入引數按協議規範進行拼串的過程,其中包括匿名回撥函式的回撥索引建立、協議名&協議路徑拼串、傳參迴圈遍歷拼串。

③建立 iframe 發起偽協議請求:請求觸發

/*建立隱藏iframe*/
var iframe = document.createElement('iframe');
iframe.style.cssText = 'display:none;width:0px;height:0px;';

function failCallback() {
    /*錯誤處理*/
}
/*iframe協議呼叫*/
iframe.onload = failCallback;
iframe.src = url;
(document.body || document.documentElement).appendChild(iframe);

/*刪除iframe*/
setTimeout(function() {
    iframe && iframe.parentNode && iframe.parentNode.removeChild(iframe);
}, 0);

通過建立 iframe 來完成協議呼叫,並在呼叫結束後將 iframe 刪除,即可在不影響原 H5 流程的情況下完成呼叫全過程。

再次解耦:app 間跨平臺—— jsapi 細化,封裝 app 差異

通過上述的解耦處理,Hybrid H5 已經可以在 app 內各平臺運行了。但往往這種 jsapi 是 app 級提供的 jsapi(下面簡稱 app jsapi),app jsapi 並不會去相容別的 app 的差異。而實際情況具體到某一 Hybrid H5,尤其是與 app 外部合作的 Hybrid H5,則並不僅僅只執行在一個 app上。比如信用卡還款業務,微信有,手Q 也有,功能都一樣。這種情況就需要進一步的解耦,從業務側再抽離一層 jsapi(下面簡稱 H5 jsapi)來處理 app 間的差異,而非每個 app 各自一套 H5。

原理圖

**原理核心:**Hybrid H5 業務上增加多一層自維護的 H5 jsapi,H5 jsapi 完成兩大行為:app jsapi 差異請求、app jsapi 差異封裝。

①app jsapi 差異請求:根據當前執行環境 app 請求具體的 app jsapi

下面以 Hybrid H5 需同時執行在手Q和空間獨立版的 app jsapi 差異請求處理邏輯。

<script type="text/javascript" >
    (function() {
        var ua = navigator.userAgent || "",
            isQQ = ua.match(/QQ\/([\d\.]+)/),
            isQzone = ua.match("Qzone");
        if (isQQ) {
            document.write("<script src='https://open.mobile.qq.com/sdk/qqapi.js?_bid=152'><\x2Fscript>");
        } else if (isQzone) {
            document.write("<script src='https://qzonestyle.gtimg.cn/qzone/phone/m/v4/widget/mobile/jsbridge.js'><\x2Fscript>");
        } else {
            // 不是已相容app,跳轉到相容app上執行
            var currentHref = window.location.href;
            /*跳轉到手Q開啟本頁面*/
            window.location.href = 'mqqapi://forward/url?url_prefix=' + btoa(currentHref) + '&version=1&src_type=web';
            /*該頁面支援自定義彈層*/
            setTimeout(function() {
                var _tempBox = confirm('請在手機QQ中使用~');
                if (_tempBox == true) {
                    /*跳至手Q開啟*/
                    window.location.href = 'mqqapi://forward/url?url_prefix=' + btoa(currentHref) + '&version=1&src_type=web';
                }
            }, 0)
        }
    })()
</script>

除了對需相容的 app 進行差異請求外,還應對在不相容的 app 執行時做跳轉到主相容 app 開啟當前頁面的邏輯處理,並做引導性提示,保障頁面的完整可用性。

②app jsapi 差異封裝:根據當前具體執行的平臺呼叫相應的 app jsapi 介面。H5 jsapi 的介面形式儘量與主執行 app 的 jsapi 保持一致

下面以開啟 QQ 錢包原生頁和原生頁面跳轉能力為例,做 app jsapi 的差異封裝。

var mod = {
    ...
    openTenpayView: function(param, callback) {
        if (isQQ) {
            var param = $.extend({
                userId: $.getCookie('uin').replace(/^o0*/, '')
            }, param);
            mqq.tenpay.openTenpayView(param, callback);
        } else {
            /*調起手Q開啟中轉頁jump.html,由中轉頁開啟原生功能頁*/
            var targetHref = 'http://testhost.com/jump.html?go=' + param.viewTag + '&_wv=' + (1 + 2 + 1024 + 2097152 + 33554432); //跳轉url
            /*跳到手Q*/
            window.location.href = 'mqqapi://forward/url?url_prefix=' + btoa(targetHref) + '&version=1&src_type=web';
        }
    },
    openUrl: function(paramObj) {
        if (isQQ) {
            mqq.ui.openUrl({
                url: paramObj.url,
                target: 1
            });
        } else if (isQzone) {
            mqq.invoke("ui", "openUrl", {
                url: paramObj.url,
                target: 1,
                style: 1
            });
        } else {
            /*相容處理*/
            location.href = paramObj.url
        }
    },
    ...其他介面...
};
return mod;

呼叫 openTenpayView,頁面能在 手Q 中正常呼叫,而在非 手Q 時則跳轉回 手Q 開啟處理;

呼叫 openUrl,對於 手Q 和空間獨立版做相應的介面呼叫,而其他平臺則直接使用 H5 的 location 跳轉。

總結

H5 本質是具有跨平臺性的。Hybrid H5 因混合了原生能力,強耦合於原生,不再具有跨平臺性。要恢復其跨平臺能力,關鍵在解耦,將其耦合於原生的部分解耦封裝起來。

解耦是開發很重要的一項能力,Hybrid H5 跨平臺性的迴歸正得益於解耦的處理。

因耦合而導致某項能力減弱的情況還有很多,比如 H5 的靈活性等等。遇到這種情況大家不妨也試試解耦,或許會收到意想不到的驚喜。

更多精彩內容歡迎關注bugly的微信公眾賬號:

騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智慧合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的程式碼行,實時上報可以在釋出後快速的瞭解應用的質量情況,適配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!

相關推薦

bugly乾貨分享---Hybrid H5跨平臺思考

跨平臺,是H5最重要的能力之一。而 Hybrid H5 因強依賴於具體 app,往往不具有跨平臺性。這時,將強依賴關係解耦,即可恢復 H5 的跨平臺能力。近期本人負責 手Q 紅包打賞專案的前端開發,因專案涉及到多 app 跨平臺相容,對 hybrid H5

Bugly乾貨分享Android程序保活招式大全

【騰訊Bugly乾貨分享】Android程序保活招式大全 本文來自於騰訊bugly開發者社群,非經作者同意,請勿轉載,原文地址:http://dev.qq.com/topic/57ac4a0ea374c75371c08ce8 作者:騰訊——張興華 目前市面上的應用,貌似除了微信和手Q都會

Bugly乾貨分享移動網際網路測試到質量的轉變

Dev Club 是一個交流移動開發技術,結交朋友,擴充套件人脈的社群,成員都是經過稽核的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。 本期,我們邀請了 TesterHome 測試技術社群聯合創始人“陳曄”,為大家分享《移動網際網路測試到質量的轉

Bugly乾貨分享聊聊蘋果的Bug

作者:張三華 導語 精神哥最近發現, 很多開發者在 iOS10 上遇到了一類堆疊為nano_free字樣的Crash,也有很多人向我們Bugly客服反饋遇到了這類問題,但並沒有好的解決方案。正當大家都束手無策的時候,微信強大的技術團隊針對這類

Bugly乾貨分享總結一個技術總監的教訓和經驗

導語 2017年來了,新年開篇,就不跟大家聊技術啦,給大家分享一篇鵝廠技術總監在多年工作中總結出的教訓和經驗。 這篇文章自從在騰訊內部論壇發表後,精神哥每年都會拿出來重新研讀一番,每次都有新的感悟和收穫,所以強烈推薦給大家。 正文 資深程式設

Bugly乾貨分享打造“微信小程式”元件化開發框架

作者:Gcaufy 導語 Bugly 之前發了一篇關於微信小程式的開發經驗分享(點選閱讀),小夥伴們在公眾賬號後臺問了很多關於小程式開發方面的問題,精神哥在查閱相關內容的時候,發現了龔澄同學自己寫了一個小程式開發框架,真的怒贊,趕緊安利給大家

Bugly乾貨分享美團大眾點評 Hybrid 化建設

本期 T 沙龍探討了移動端熱更新相關的話題。由於沙龍時間的限制,本期我們選取了美團的 Hybrid 化建設、去哪兒的跨平臺 ListView 效能優化、微博 Android 端熱更新踩過的坑話題。還期待熱更新、熱修復哪些話題?歡迎留言給我們。也歡迎報名參加

Bugly乾貨分享Android減包 - 減少APK大小

本文是對Google官方文件 Reduce APK Size 的翻譯,點選“閱讀原文”可以檢視英文原文。 譯者簡介:damonxia(夏正冬),天天P圖Android工程師 使用者經常會避免下載看起來體積較大的應用,特別是在不穩定的2G、3G

Bugly乾貨分享跨平臺 ListView 效能優化

導語 精神哥前陣子去參加了好友小青在北京辦的T沙龍,探討移動端熱更新相關的話題。Bugly 曾為大家介紹過不少騰訊內部的熱更新的框架,正好這次看到了美團,去哪兒以及微博同學在應用熱更新方面的實踐。 上週為大家整理了《美團大眾點評 Hybrid 化建設

Bugly乾貨分享Android ListView與RecyclerView對比淺析--快取機制

作者:黃寧源 一,背景 RecyclerView是谷歌官方出的一個用於大量資料展示的新控制元件,可以用來代替傳統的ListView,更加強大和靈活。 最近,自己負責的業務,也遇到這樣的一個問題,關於是否要將ListView替換為Recycl

Bugly乾貨分享Android 新一代多渠道打包神器

關於作者: 李濤,騰訊Android工程師,14年加入騰訊SNG增值產品部,期間主要負責手Q動漫、企鵝電競等專案的功能開發和技術優化。業務時間喜歡折騰新技術,寫一些技術文章,個人技術部落格:www.ltlovezh.com 。 ApkChanne

Bugly乾貨分享WKWebView 那些坑

導語 WKWebView 是蘋果在 WWDC 2014 上推出的新一代 webView 元件,用以替代 UIKit 中笨重難用、記憶體洩漏的 UIWebView, 擁有60fps滾動重新整理率、和 safari 相同的 JavaScript 引擎。簡單

Bugly乾貨分享你為什麼需要 Kotlin

一、往事 曾經你有段時間研究 Intellij 的外掛開發,企圖編譯 Intellij Idea Community Edition (ICE)的原始碼,結果發現有個奇怪的東西讓你的程式碼無法編譯。。什麼鬼,kt 是什麼玩意兒? 怎麼又有

bugly乾貨分享精神哥手把手教你如何智鬥ANR

上帝說要有ANR,於是Bugly就有了ANR上報,那麼ANR到底是什麼? 最近很多童鞋問起精神哥ANR的問題,那麼這次就來聊一下,雞爪怎麼泡才好吃,噢不,是如何快速定位ANR。 ANR是什麼 簡單說,通常就是App執行的時候,duang~卡住了,怎麼搞都動不

Bugly乾貨分享WebSocket 淺析

前言 在WebSocket API尚未被眾多瀏覽器實現和釋出的時期,開發者在開發需要接收來自伺服器的實時通知應用程式時,不得不求助於一些“hacks”來模擬實時連線以實現實時通訊,最流行的一種方式是長輪詢 。 長輪詢主要是發出一個HTTP請求到伺服器,

Bugly乾貨分享基於RxJava的一種MVP實現

Dev Club 是一個交流移動開發技術,結交朋友,擴充套件人脈的社群,成員都是經過稽核的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。 本期,我們邀請了騰訊IEG Android 開發工程師——戴俊,為大家分享《基於RxJava的一種MVP實現》

Bugly乾貨分享RecyclerView 必知必會

導語 RecyclerView是Android 5.0提出的新UI控制元件,可以用來代替傳統的ListView。 Bugly之前也發過一篇相關文章,講解了 RecyclerView 與 ListView 在快取機制上的一些區別: 今天精神哥來給

Bugly乾貨分享深入理解 ButterKnife,讓你的程式學會寫程式碼

0、引子 話說我們做程式設計師的,都應該多少是個懶人,我們總是想辦法驅使我們的電腦幫我們幹活,所以我們學會了各式各樣的語言來告訴電腦該做什麼——儘管,他們有時候也會誤會我們的意思。 突然有一天,我覺得有些程式碼其實,可以按照某種規則生成,但你又不能不

Bugly乾貨分享Android 程序保活招式大全

本文來自於騰訊bugly開發者社群, ,原文地址:https://segmentfault.com/a/1190000006251859作者:騰訊——張興華目前市面上的應用,貌似除了微信和手Q都會比較擔心被使用者或者系統(廠商)殺死問題。本文對 Android 程序拉活進行一

Bugly乾貨分享WebVR如此近-three.js的WebVR示例解析

作者:蘇晏燁 關於WebVR 最近VR的發展十分吸引人們的眼球,很多同學應該也心癢癢的想體驗VR裝置,然而現在的專業硬體價格還比較高,入手一個估計就要吃土了。但是,對於我們前端開發者來說,我們不僅可以簡單地在手機上進行視覺上的VR體驗,還可以