隨着移動浪潮的興起,各類APP層出不窮,極速的業務擴展提高了團隊對開發效率的要求,這個時候使用IOS&Andriod開發一個APP彷佛成本有點太高了,而H5的低成本、高效率、跨平臺等特性立刻被利用起來造成了一種新的開發模式:Hybrid APP。css
做爲一種混合開發的模式,Hybrid APP底層依賴於Native提供的容器(UIWebview),上層使用Html&Css&JS作業務開發,底層透明化、上層多多樣化,這種場景很是有利於前端介入,很是適合業務快速迭代,因而Hybrid火啦。html
原本我以爲這種開發模式既然你們都知道了,那麼Hybrid就沒有什麼探討的價值了,但令我詫異的是依舊有不少人對Hybrid這種模式感到陌生,這種狀況在二線城市很常見,因此我這裏嘗試從另外一個方面向各位介紹Hybrid,指望對各位正確的技術選型有所幫助。前端
Hybrid發家史ios
最初攜程的應用所有是Native的,H5站點只佔其流量很小的一部分,當時Native有200人紅紅火火,而H5開僅有5人左右在打醬油,後面無線團隊來了一個執行力十分強的服務器端出身的leader,他爲了瞭解前端開發,竟然親手使用jQuery Mobile開發了初版程序,雖然很快方案便被推翻,可是H5團隊開始發力,在短期內已經遇上了Native的業務進度:git
忽然有一天andriod同事跑過來告訴咱們andriod中有一個方法最大樹限制,可能一些頁面須要咱們內嵌H5的頁面,因而Native與H5框架團隊牽頭作了第一個Hybrid項目,攜程第一次出現了一套代碼兼容三端的狀況。這個開發效率槓槓的,團隊嚐到了甜頭,因而乎後續的頻道基本都開始了Hybrid開發,到我離開時,整個機制已經十分紅熟了,而前端也有幾百人了。github
場景重現web
狼廠有三大大流量APP,手機百度、百度地圖、糯米APP,最近接入糯米的時候,發現他們也在作Hybrid平臺化相關的推廣,將靜態資源打包至Native中,Native提供js調用原生應用的能力,從產品化和工程化來講作的很不錯,可是有兩個瑕疵:ajax
① 資源所有打包至Naive中APP尺寸會增大,就算以增量機制也避免不了APP的膨脹,由於如今接入的頻道較少一個頻道500K沒有感受,一旦平臺化後主APP尺寸會急劇增大數據庫
② 糯米前端框架團隊封裝了Native端的能力,可是沒有提供配套的前端框架,這個解決方案是不完整的。不少業務已經有H5站點了,爲了接入還得單獨開發一套程序;而就算是新業務接入,又會面臨嵌入資源必須是靜態資源的限制,作出來的項目沒有SEO,若是關注SEO的話仍是須要再開發,從工程角度來講是有問題的。json
但從產品可接入度與產品化來講,糯米Hybrid化的大方向是很樂觀的,也確實取得了一些成績,在短期就有不少頻道接入了,隨着推廣進行,明年可能會造成一個大型的Hybrid平臺。可是由於我也經歷過推廣框架,當聽到他們忽悠我說性能會提升70%,與Native體驗基本一致時,不知爲什麼我竟然笑了......
總結
若是讀了上面幾個故事你依舊不知道爲什麼要使用Hybrid技術的話,我這裏再作一個總結吧:
Hybrid開發效率高、跨平臺、底層本 Hybrid從業務開發上講,沒有版本問題,有BUG能及時修復
Hybrid是有缺點的,Hybrid體驗就確定比不上Native,因此使用有其場景,可是對於須要快速試錯、快速佔領市場的團隊來講,Hybrid必定是不二的選擇,團隊生存下來後仍是須要作體驗更好的原生APP。
好了,上面扯了那麼多沒用的東西,今天的目的實際上是爲你們介紹Hybrid的一些設計知識,若是你認真閱讀此文,可能在如下方面對你有所幫助:
① Hybrid中Native與前端各自的工做是什麼
② Hybrid的交互接口如何設計
③ Hybrid的Header如何設計
④ Hybrid的如何設計目錄結構以及增量機制如何實現
⑤ 資源緩存策略,白屏問題......
文中是我我的的一些開發經驗,但願對各位有用,也但願各位多多支持討論,指出文中不足以及提出您的一些建議。
而後文中Andriod相關代碼由個人同事明月提供,這裏特別感謝明月同窗對個人支持,這裏掃描二維碼能夠下載APP進行測試:
Andriod APP二維碼:
代碼地址:
https://github.com/yexiaochai/hybrid
在作Hybrid架構設計以前須要分清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的交互無非是Native調用前端頁面的JS方法,或者前端頁面經過JS調用Native提供的接口,二者交互的橋樑皆Webview:
app自身能夠自定義url schema,而且把自定義的url註冊在調度中心, 例如
咱們JS與Native通訊通常就是建立這類URL被Native捕獲處理,後續也出現了其它前端調用Native的方式,但能夠作底層封裝使其透明化,因此重點以及是如何進行前端與Native的交互設計。
Native在每一個版本會提供一些API,前端會有一個對應的框架團隊對其進行封裝,釋放業務接口。好比糯米對外的接口是這樣的:
1 BNJS.http.get();//向業務服務器拿請求據【1.0】 1.3版本接口有擴展 2 BNJS.http.post();//向業務服務器提交數據【1.0】 3 BNJS.http.sign();//計算簽名【1.0】 4 BNJS.http.getNA();//向NA服務器拿請求據【1.0】 1.3版本接口有擴展 5 BNJS.http.postNA();//向NA服務器提交數據【1.0】 6 BNJS.http.getCatgData();//從Native本地獲取篩選數據【1.1】
1 BNJSReady(function(){ 2 BNJS.http.post({ 3 url : 'http://cp01-testing-tuan02.cp01.baidu.com:8087/naserver/user/feedback', 4 params : { 5 msg : '測試post', 6 contact : '18721687903' 7 }, 8 onSuccess : function(res){ 9 alert('發送post請求成功!'); 10 }, 11 onFail : function(res){ 12 alert('發送post請求失敗!'); 13 } 14 }); 15 });
前端框架定義了一個全局變量BNJS做爲Native與前端交互的對象,只要引入了糯米提供的這個JS庫,而且在糯米封裝的Webview容器中,前端便得到了調用Native的能力,我揣測糯米這種設計是由於這樣便於第三方團隊的接入使用,手機百度有一款輕應用框架也走的這種路線:
clouda.mbaas.account //釋放了clouda全局變量
這樣作有一個前提是,Native自己已經十分穩定了,不多新增功能了,不然在直連狀況下就會面臨一個尷尬,由於web站點永遠保持最新的,就會在一些低版本容器中調用了沒有提供的Native能力而報錯。
手白、糯米底層如何作咱們無從得知,但咱們發現調用Native API接口的方式和咱們使用AJAX調用服務器端提供的接口是及其類似的:
這裏相似的微薄開放平臺的接口是這樣定義的:
粉絲服務(新手接入指南) | ||
---|---|---|
讀取接口 | 接收消息 | 接收用戶私信、關注、取消關注、@等消息接口 |
寫入接口 | 發送消息 | 向用戶回覆私信消息接口 |
生成帶參數的二維碼 | 生成帶參數的二維碼接口 |
咱們要作的就是經過一種方式建立ajax請求便可:
https://api.weibo.com/2/statuses/public_timeline.json
因此我在實際設計Hybrid交互模型時,是以接口爲單位進行設計的,好比獲取通信錄的整體交互是:
交互的第一步是設計數據格式,這裏分爲請求數據格式與響應數據格式,參考ajax的請求模型大概是:
$.ajax(options) ⇒ XMLHttpRequest type (默認值:"GET") HTTP的請求方法(「GET」, 「POST」, or other)。 url (默認值:當前url) 請求的url地址。 data (默認值:none) 請求中包含的數據,對於GET請求來講,這是包含查詢字符串的url地址,若是是包含的是object的話,$.param會將其轉化成string。
因此我這邊與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必定會用到。
跳轉是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的細節,這時就和糯米對外釋放的接口差很少了。
最初我實際上是抵制使用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是不容許重複的):
1 //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 });
由於Header左邊通常來講只有一個按鈕,因此其對象可使用這種形式:
1 this.header.set({ 2 back: function () { }, 3 title: '' 4 }); 5 //語法糖=> 6 this.header.set({ 7 left: [{ 8 tagname: 'back', 9 callback: function(){} 10 }], 11 title: '', 12 });
爲完成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 };
Native Header組件的實現:
1 define([], function () { 2 'use strict'; 3 4 return _.inherit({ 5 6 propertys: function () { 7 8 this.left = []; 9 this.right = []; 10 this.title = {}; 11 this.view = null; 12 13 this.hybridEventFlag = 'Header_Event'; 14 15 }, 16 17 //所有更新 18 set: function (opts) { 19 if (!opts) return; 20 21 var left = []; 22 var right = []; 23 var title = {}; 24 var tmp = {}; 25 26 //語法糖適配 27 if (opts.back) { 28 tmp = { tagname: 'back' }; 29 if (typeof opts.back == 'string') tmp.value = opts.back; 30 else if (typeof opts.back == 'function') tmp.callback = opts.back; 31 else if (typeof opts.back == 'object') _.extend(tmp, opts.back); 32 left.push(tmp); 33 } else { 34 if (opts.left) left = opts.left; 35 } 36 37 //右邊按鈕必須保持數據一致性 38 if (typeof opts.right == 'object' && opts.right.length) right = opts.right 39 40 if (typeof opts.title == 'string') { 41 title.title = opts.title; 42 } else if (_.isArray(opts.title) && opts.title.length > 1) { 43 title.title = opts.title[0]; 44 title.subtitle = opts.title[1]; 45 } else if (typeof opts.title == 'object') { 46 _.extend(title, opts.title); 47 } 48 49 this.left = left; 50 this.right = right; 51 this.title = title; 52 this.view = opts.view; 53 54 this.registerEvents(); 55 56 _.requestHybrid({ 57 tagname: 'updateheader', 58 param: { 59 left: this.left, 60 right: this.right, 61 title: this.title 62 } 63 }); 64 65 }, 66 67 //註冊事件,將事件存於本地 68 registerEvents: function () { 69 _.unRegisterHybridCallback(this.hybridEventFlag); 70 this._addEvent(this.left); 71 this._addEvent(this.right); 72 this._addEvent(this.title); 73 }, 74 75 _addEvent: function (data) { 76 if (!_.isArray(data)) data = [data]; 77 var i, len, tmp, fn, tagname; 78 var t = 'header_' + (new Date().getTime()); 79 80 for (i = 0, len = data.length; i < len; i++) { 81 tmp = data[i]; 82 tagname = tmp.tagname || ''; 83 if (tmp.callback) { 84 fn = $.proxy(tmp.callback, this.view); 85 tmp.callback = t; 86 _.registerHeaderCallback(this.hybridEventFlag, t + '_' + tagname, fn); 87 } 88 } 89 }, 90 91 //顯示header 92 show: function () { 93 _.requestHybrid({ 94 tagname: 'showheader' 95 }); 96 }, 97 98 //隱藏header 99 hide: function () { 100 _.requestHybrid({ 101 tagname: 'hideheader', 102 param: { 103 animate: true 104 } 105 }); 106 }, 107 108 //只更新title,不重置事件,不對header其它地方形成變化,僅僅最簡單的header能如此操做 109 update: function (title) { 110 _.requestHybrid({ 111 tagname: 'updateheadertitle', 112 param: { 113 title: 'aaaaa' 114 } 115 }); 116 }, 117 118 initialize: function () { 119 this.propertys(); 120 } 121 }); 122 123 });
雖然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 });
最後,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響應經過錯誤碼才能得到,這是動態語言與靜態語言作入口頁面的一個很大的區別。
以網頁的方式訪問,帳號登陸與否由是否帶有祕鑰cookie決定(這時並不能保證祕鑰的有效性),由於Native不關注業務實現,而每次載入都有多是登陸成功跳回來的結果,因此每次載入後都須要關注祕鑰cookie變化,以作到登陸態數據一致性。
以file的方式訪問內嵌資源的話,由於API請求控制方爲Native,因此鑑權的工做徹底由Native完成,接口訪問若是沒有登陸便彈出Native級別登陸框引導登陸便可,每次訪問webview將帳號信息種入到webview中,這裏有個矛盾點是Native種入webview的時機,由於有多是網頁註銷的狀況,因此這裏的邏輯是:
① webview載入結束
② Native檢測webview是否包含帳號cookie信息
③ 若是不包含則種入cookie,若是包含則檢測與Native帳號信息是否相同,不一樣則替換自身
④ 若是檢測到跳到了註銷帳戶的頁面,則須要清理自身帳號信息
若是登陸不統一會就會出現上述複雜的邏輯,因此真實狀況下咱們會對登陸接口收口。
簡單化帳號接口
平臺層面以爲上述操做過於複雜,便強制要求在Hybrid容器中只能使用Native接口進行登陸和登出,前端框架在底層作適配,保證上層業務的透明,這樣狀況會簡單不少:
① 使用Native代理作請求接口,若是沒有登陸直接Native層喚起登陸框
② 直連方式使用ajax請求接口,若是沒有登陸則在底層喚起登陸框(須要前端框架支持)
簡單的登陸登出接口實現:
1 /* 2 不管成功與否皆會關閉登陸框 3 參數包括: 4 success 登陸成功的回調 5 error 登陸失敗的回調 6 url 若是沒有設置success,或者success執行後沒有返回true,則默認跳往此url 7 */ 8 HybridUI.Login = function (opts) { 9 }; 10 //=> 11 requestHybrid({ 12 tagname: 'login', 13 param: { 14 success: function () { }, 15 error: function () { }, 16 url: '...' 17 } 18 }); 19 //與登陸接口一致,參數一致 20 HybridUI.logout = function () { 21 };
帳號信息獲取
在實際的業務開發中,判斷用戶是否登陸、獲取用戶基本信息的需求比比皆是,因此這裏必須保證Hybrid開發模式與H5開發模式保持統一,不然須要在業務代碼中作不少無謂的判斷,咱們在前端框架會封裝一個User模塊,主要接口包括:
1 var User = {}; 2 User.isLogin = function () { }; 3 User.getInfo = function () { };
這個代碼的底層實現分爲前端實現,Native實現,首先是前端的作法是:
當前端頁面載入後,會作一次異步請求,請求用戶相關數據,若是是登陸狀態便能獲取數據存於localstorage中,這裏必定不能存取敏感信息
前端使用localstorage的話須要考慮極端狀況下使用內存變量的方式替換localstorage的實現,不然會出現不可以使用的狀況,然後續的訪問皆是使用localstorage中的數據作判斷依據,如下狀況須要清理localstorage的帳號數據:
① 系統登出
② 訪問接口提示須要登陸
③ 調用登陸接口
這種模式多用於單頁應用,非單頁應用通常會在每次刷新頁面先清空帳號信息再異步拉取,可是若是當前頁面立刻就須要判斷用戶登陸數據的話,便不可靠了;處於Hybrid容器中時,由於Native自己就保存了用戶信息,封裝的接口直接由Native獲取便可,這塊比較靠譜。
Hybrid技術既然是將靜態資源存於Native,那麼就須要目錄設計,通過以前的經驗,目錄結構通常以2層目錄劃分:
若是咱們有兩個頻道酒店與機票,那麼目錄結構是這樣的:
1 webapp //根目錄 2 ├─flight 3 ├─hotel //酒店頻道 4 │ │ index.html //業務入口html資源,若是不是單頁應用會有多個入口 5 │ │ main.js //業務全部js資源打包 6 │ │ 7 │ └─static //靜態樣式資源 8 │ ├─css 9 │ ├─hybrid //存儲業務定製化類Native Header圖標 10 │ └─images 11 ├─libs 12 │ libs.js //框架全部js資源打包 13 │ 14 └─static 15 ├─css 16 └─images
最初設計的forward跳轉中的topage參數規則是:頻道/具體頁面=>channel/page,其他資源會由index.html這個入口文件帶出。
真實的增量機制須要服務器端的配合,我這裏只能簡單描述,Native端會有維護一個版本映射表:
{ flight: 1.0.0, hotel: 1.0.0, libs: 1.0.0, static: 1.0.0 }
這個映射表是每次大版本APP發佈時由服務器端生成的,若是酒店頻道須要在線作增量發佈的話,會打包一個與線上一致的文件目錄,走發佈平臺發佈,會在數據庫中造成一條記錄:
channel | ver | md5 |
flight | 1.0.0 | 1245355335 |
hotel | 1.0.1 | 455ettdggd |
當APP啓動時,APP會讀取版本信息,這裏發現hotel的本地版本號比線上的小,便會下載md5對應的zip文件,而後解壓之而且替換整個hotel文件,本次增量結束,由於全部的版本文件不會重複,APP回滾時可用回到任意想去的版本,也能夠對任意版本作BUG修復。
github上代碼會持續更新,如今界面反正不太好看,你們多多包涵吧,這裏是一些效果圖:
Hybrid方案是快速迭代項目,快速佔領市場的神器,但願此文能對準備接觸Hybrid技術的朋友提供一些幫助,而且再次感謝明月同窗的配合。
http://www.cnblogs.com/yexiaochai/p/4921635.html