Hybrid開發-----H5與原生App互動
http://www.cnblogs.com/yexiaochai/p/5813248.html
http://www.cnblogs.com/nildog/p/5536081.html
Hybrid開發效率高、跨平臺、低層本
Hybrid從業務開發上講,沒有版本問題,有BUG能及時修復
Hybrid是有缺點的,Hybrid體驗就肯定比不上Native,所以使用有其場景,但是對於需要快速試錯、快速佔領市場的團隊來說,Hybrid一定是不二的選擇,團隊生存下來後還是需要做體驗更好的原生APP。
① Hybrid中Native與前端各自的工作是什麼
② Hybrid的互動介面如何設計
③ Hybrid的Header如何設計
④ Hybrid的如何設計目錄結構以及增量機制如何實現
⑤ 資源快取策略,白屏問題......
靜態資源打包至Native中,Native提供js呼叫原生應用的能力,從產品化和工程化來說做的很不錯,但是有兩個瑕疵:
① 資源全部打包至Naive中APP尺寸會增大,就算以增量機制也避免不了APP的膨脹,因為現在接入的頻道較少一個頻道500K沒有感覺,一旦平臺化後主APP尺寸會急劇增大
② 糯米前端框架團隊封裝了Native端的能力,但是沒有提供配套的前端框架,這個解決方案是不完整的。很多業務已經有H5站點了,為了接入還得單獨開發一套程式;而就算是新業務接入,又會面臨嵌入資源必須是靜態資源的限制,做出來的專案沒有SEO,如果關注SEO的話還是需要再開發,從工程角度來說是有問題的。
Native與前端分工
首先Native提供的是一宿主環境,要合理的利用Native提供的能力,要實現通用的Hybrid平臺架構,站在前端視角,我認為需要考慮以下核心設計問題。
互動設計
Hybrid架構設計第一個要考慮的問題是如何設計與前端的互動,如果這塊設計的不好會對後續開發、前端框架維護造成深遠的影響,並且這種影響往往是不可逆的,所以這裡需要前端與Native好好配合,提供通用的介面,比如:
① NativeUI元件,header元件、訊息類元件
② 通訊錄、系統、裝置資訊讀取介面
③ H5與Native的互相跳轉,比如H5如何跳到一個Native頁面,H5如何新開Webview做動畫跳到另一個H5頁面
資源訪問機制
Native首先需要考慮如何訪問H5資源,做到既能以file的方式訪問Native內部資源,又能使用url的方式訪問線上資源;需要提供前端資源增量替換機制,以擺脫APP迭代發版問題,避免使用者升級APP。這裡就會涉及到靜態資源在APP中的存放策略,更新策略的設計,複雜的話還會涉及到伺服器端的支援。
賬號資訊設計
賬號系統是重要並且無法避免的,Native需要設計良好安全的身份驗證機制,保證這塊對業務開發者足夠透明,打通賬戶資訊。
Hybrid開發除錯
功能設計完並不是結束,Native與前端需要商量出一套可開發除錯的模型,不然很多業務開發的工作將難以繼續,這個很多文章已經接受過了,本文不贅述。
至於Native還會關注的一些通訊設計、併發設計、異常處理、日誌監控以及安全模組因為不是我涉及的領域便不予關注了(事實上是想關注不得其門),而前端要做的事情就是封裝Native提供的各種能力,整體架構是這樣的:
真實業務開發時,Native除了會關注登入模組之外還會封裝支付等重要模組,這裡視業務而定。
Hybrid互動設計
Hybrid的互動無非是Native呼叫前端頁面的JS方法,或者前端頁面通過JS呼叫Native提供的介面,兩者互動的橋樑皆Webview:
app自身可以自定義url schema,並且把自定義的url註冊在排程中心, 例如
- ctrip://wireless 開啟攜程App
- weixin:// 開啟微信
我們JS與Native通訊一般就是建立這類URL被Native捕獲處理,後續也出現了其它前端呼叫Native的方式,但可以做底層封裝使其透明化,所以重點以及是如何進行前端與Native的互動設計。
JS to Native
Native在每個版本會提供一些API,前端會有一個對應的框架團隊對其進行封裝,釋放業務介面。
前端框架定義了一個全域性變數BNJS作為Native與前端互動的物件,只要引入了糯米提供的這個JS庫,並且在糯米封裝的Webview容器中,前端便獲得了呼叫Native的能力,我揣測糯米這種設計是因為這樣便於第三方團隊的接入使用,手機百度有一款輕應用框架也走的這種路線:
clouda.mbaas.account //釋放了clouda全域性變數
API式互動
手白、糯米底層如何做我們無從得知,但我們發現呼叫Native API介面的方式和我們使用AJAX呼叫伺服器端提供的介面是及其相似的:
這裡類似的微薄開放平臺的介面是這樣定義的:
讀取介面 | 接收訊息 | 接收使用者私信、關注、取消關注、@等訊息介面 |
寫入介面 | 傳送訊息 | 向用戶回覆私信訊息介面 |
我們要做的就是通過一種方式建立ajax請求即可:
https://api.weibo.com/2/statuses/public_timeline.json
所以我在實際設計Hybrid互動模型時,是以介面為單位進行設計的,比如獲取通訊錄的總體互動是:
格式約定
所以我這邊與Native約定的請求模型是:
requestHybrid({ //建立一個新的webview對話方塊視窗 tagname: 'hybridapi', //請求引數,會被Native使用 param: {}, //Native處理成功後回撥前端的方法 callback: function (data) { } });
這個方法執行會形成一個URL,比如:
hybridschema://hybridapi?callback=hybrid_1446276509894¶m=%7B%22data1%22%3A1%2C%22data2%22%3A2%7D
這裡提一點,APP安裝後會在手機上註冊一個schema,比如淘寶是taobao://,Native會有一個程序監控Webview發出的所有schema://請求,然後分發到“控制器”hybridapi處理程式,Native控制器處理時會需要param提供的引數(encode過),處理結束後將攜帶資料獲取Webview window物件中的callback(hybrid_1446276509894)呼叫之
資料返回的格式約定是:
{ data: {}, errno: 0, msg: "success" }
真實的資料在data物件中,如果errno不為0的話,便需要提示msg,這裡舉個例子如果錯誤碼1代表該介面需要升級app才能使用的話:
{ data: {}, errno: 1, msg: "APP版本過低,請升級APP版本" }
程式碼實現
這裡給一個簡單的程式碼實現,真實程式碼在APP中會有所變化:
1 window.Hybrid = window.Hybrid || {}; 2 var bridgePostMsg = function (url) { 3 if ($.os.ios) { 4 window.location = url; 5 } else { 6 var ifr = $('<iframe style="display: none;" src="' + url + '"/>'); 7 $('body').append(ifr); 8 setTimeout(function () { 9 ifr.remove(); 10 }, 1000) 11 } 12 }; 13 var _getHybridUrl = function (params) { 14 var k, paramStr = '', url = 'scheme://'; 15 url += params.tagname + '?t=' + new Date().getTime(); //時間戳,防止url不起效 16 if (params.callback) { 17 url += '&callback=' + params.callback; 18 delete params.callback; 19 } 20 if (params.param) { 21 paramStr = typeof params.param == 'object' ? JSON.stringify(params.param) : params.param; 22 url += '¶m=' + encodeURIComponent(paramStr); 23 } 24 return url; 25 }; 26 var requestHybrid = function (params) { 27 //生成唯一執行函式,執行後銷燬 28 var tt = (new Date().getTime()); 29 var t = 'hybrid_' + tt; 30 var tmpFn; 31 32 //處理有回撥的情況 33 if (params.callback) { 34 tmpFn = params.callback; 35 params.callback = t; 36 window.Hybrid[t] = function (data) { 37 tmpFn(data); 38 delete window.Hybrid[t]; 39 } 40 } 41 bridgePostMsg(_getHybridUrl(params)); 42 }; 43 //獲取版本資訊,約定APP的navigator.userAgent版本包含版本資訊:scheme/xx.xx.xx 44 var getHybridInfo = function () { 45 var platform_version = {}; 46 var na = navigator.userAgent; 47 var info = na.match(/scheme\/\d\.\d\.\d/); 48 49 if (info && info[0]) { 50 info = info[0].split('/'); 51 if (info && info.length == 2) { 52 platform_version.platform = info[0]; 53 platform_version.version = info[1]; 54 } 55 } 56 return platform_version; 57 };
因為Native對於H5來是底層,框架&底層一般來說是不會關注業務實現的,所以真實業務中Native呼叫H5場景較少,這裡不予關注了。
常用互動API
良好的互動設計是成功的第一步,在真實業務開發中有一些API一定會用到。
跳轉
跳轉是Hybrid必用API之一,對前端來說有以下跳轉:
① 頁面內跳轉,與Hybrid無關
② H5跳轉Native介面
③ H5新開Webview跳轉H5頁面,一般為做頁面動畫切換
如果要使用動畫,按業務來說有向前與向後兩種,forward&back,所以約定如下,首先是H5跳Native某一個頁面
1 //H5跳Native頁面 2 //=>baidubus://forward?t=1446297487682¶m=%7B%22topage%22%3A%22home%22%2C%22type%22%3A%22h2n%22%2C%22data2%22%3A2%7D 3 requestHybrid({ 4 tagname: 'forward', 5 param: { 6 //要去到的頁面 7 topage: 'home', 8 //跳轉方式,H5跳Native 9 type: 'native', 10 //其它引數 11 data2: 2 12 } 13 });
比如攜程H5頁面要去到酒店Native某一個頁面可以這樣:
1 //=>schema://forward?t=1446297653344¶m=%7B%22topage%22%3A%22hotel%2Fdetail%20%20%22%2C%22type%22%3A%22h2n%22%2C%22id%22%3A20151031%7D 2 requestHybrid({ 3 tagname: 'forward', 4 param: { 5 //要去到的頁面 6 topage: 'hotel/detail', 7 //跳轉方式,H5跳Native 8 type: 'native', 9 //其它引數 10 id: 20151031 11 } 12 });
比如H5新開Webview的方式跳轉H5頁面便可以這樣:
1 requestHybrid({ 2 tagname: 'forward', 3 param: { 4 //要去到的頁面,首先找到hotel頻道,然後定位到detail模組 5 topage: 'hotel/detail ', 6 //跳轉方式,H5新開Webview跳轉,最後裝載H5頁面 7 type: 'webview', 8 //其它引數 9 id: 20151031 10 } 11 });
back與forward一致,我們甚至會有animattype引數決定切換頁面時的動畫效果,真實使用時可能會封裝全域性方法略去tagname的細節,這時就和糯米對外釋放的介面差不多了。
Header 元件的設計
最初我其實是抵制使用Native提供的UI元件的,尤其是Header,因為平臺化後,Native每次改動都很慎重並且響應很慢,但是出於兩點核心因素考慮,我基本放棄了抵抗:
① 其它主流容器都是這麼做的,比如微信、手機百度、攜程
② 沒有header一旦網路出錯出現白屏,APP將陷入假死狀態,這是不可接受的,而一般的解決方案都太業務了
PS:Native吊起Native時,如果300ms沒有響應需要出loading元件,避免白屏
因為H5站點本來就有Header元件,站在前端框架層來說,需要確保業務的程式碼是一致的,所有的差異需要在框架層做到透明化,簡單來說Header的設計需要遵循:
① H5 header元件與Native提供的header元件使用呼叫層介面一致
② 前端框架層根據環境判斷選擇應該使用H5的header元件抑或Native的header元件
一般來說header元件需要完成以下功能:
① header左側與右側可配置,顯示為文字或者圖示(這裡要求header實現主流圖示,並且也可由業務控制圖示),並需要控制其點選回撥
② header的title可設定為單標題或者主標題、子標題型別,並且可配置lefticon與righticon(icon居中)
③ 滿足一些特殊配置,比如標籤類header
所以,站在前端業務方來說,header的使用方式為(其中tagname是不允許重複的):
//Native以及前端框架會對特殊tagname的標識做預設回撥,如果未註冊callback,或者點選回撥callback無返回則執行預設方法 2 // back前端預設執行History.back,如果不可後退則回到指定URL,Native如果檢測到不可後退則返回Naive大首頁 3 // home前端預設返回指定URL,Native預設返回大首頁 4 this.header.set({ 5 left: [ 6 { 7 //如果出現value欄位,則預設不使用icon 8 tagname: 'back', 9 value: '回退', 10 //如果設定了lefticon或者righticon,則顯示icon 11 //native會提供常用圖示icon對映,如果找不到,便會去當前業務頻道專用目錄獲取圖示 12 lefticon: 'back', 13 callback: function () { } 14 } 15 ], 16 right: [ 17 { 18 //預設icon為tagname,這裡為icon 19 tagname: 'search', 20 callback: function () { } 21 }, 22 //自定義圖示 23 { 24 tagname: 'me', 25 //會去hotel頻道儲存靜態header圖示資源目錄搜尋該圖示,沒有便使用預設圖示 26 icon: 'hotel/me.png', 27 callback: function () { } 28 } 29 ], 30 title: 'title', 31 //顯示主標題,子標題的場景 32 title: ['title', 'subtitle'], 33 34 //定製化title 35 title: { 36 value: 'title', 37 //標題右邊圖示 38 righticon: 'down', //也可以設定lefticon 39 //標題型別,預設為空,設定的話需要特殊處理 40 //type: 'tabs', 41 //點選標題時的回撥,預設為空 42 callback: function () { } 43 } 44 });
為完成Native端的實現,這裡會新增兩個介面,向Native註冊事件,以及登出事件:
1 var registerHybridCallback = function (ns, name, callback) { 2 if(!window.Hybrid[ns]) window.Hybrid[ns] = {}; 3 window.Hybrid[ns][name] = callback; 4 }; 5 6 var unRegisterHybridCallback = function (ns) { 7 if(!window.Hybrid[ns]) return; 8 delete window.Hybrid[ns]; 9 };
請求類
雖然get類請求可以用jsonp的方式繞過跨域問題,但是post請求卻是真正的攔路虎,為了安全性伺服器設定cors會僅僅針對幾個域名,Hybrid內嵌靜態資源是通過file的方式讀取,這種場景使用cors就不好使了,所以每個請求需要經過Native做一層代理髮出去。
這個使用場景與Header元件一致,前端框架層必須做到對業務透明化,業務事實上不必關心這個請求是由瀏覽器發出還是由Native發出:
1 HybridGet = function (url, param, callback) { 2 }; 3 HybridPost = function (url, param, callback) { 4 };
真實的業務場景,會將之封裝到資料請求模組,在底層做適配,在H5站點下使用ajax請求,在Native內嵌時使用代理髮出,與Native的約定為:
1 requestHybrid({ 2 tagname: 'ajax', 3 param: { 4 url: 'hotel/detail', 5 param: {}, 6 //預設為get 7 type: 'post' 8 }, 9 //響應後的回撥 10 callback: function (data) { } 11 });
常用NativeUI元件
最後,Native會提供幾個常用的Native級別的UI,比如loading載入層,比如toast訊息框:
1 var HybridUI = {}; 2 HybridUI.showLoading(); 3 //=> 4 requestHybrid({ 5 tagname: 'showLoading' 6 }); 7 8 HybridUI.showToast({ 9 title: '111', 10 //幾秒後自動關閉提示框,-1需要點選才會關閉 11 hidesec: 3, 12 //彈出層關閉時的回撥 13 callback: function () { } 14 }); 15 //=> 16 requestHybrid({ 17 tagname: 'showToast', 18 param: { 19 title: '111', 20 hidesec: 3, 21 callback: function () { } 22 } 23 });
Native UI與前端UI不容易打通,所以在真實業務開發過程中,一般只會使用幾個關鍵的Native UI。
賬號系統的設計
根據上面的設計,我們約定在Hybrid中請求有兩種發出方式:
① 如果是webview訪問線上站點的話,直接使用傳統ajax發出
② 如果是file的形式讀取Native本地資源的話,請求由Native代理髮出
因為靜態html資源沒有鑑權的問題,真正的許可權驗證需要請求伺服器api響