成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

淺談Hybrid技術的設計與實現

開發 前端
隨著移動浪潮的興起,各種APP層出不窮,極速的業務擴展提升了團隊對開發效率的要求,這個時候使用IOS&Andriod開發一個APP似乎成本有點過高了,而H5的低成本、高效率、跨平臺等特性馬上被利用起來形成了一種新的開發模式:Hybrid APP。

前言

隨著移動浪潮的興起,各種APP層出不窮,極速的業務擴展提升了團隊對開發效率的要求,這個時候使用IOS&Andriod開發一個APP似乎成本有點過高了,而H5的低成本、高效率、跨平臺等特性馬上被利用起來形成了一種新的開發模式:Hybrid APP。

作為一種混合開發的模式,Hybrid APP底層依賴于Native提供的容器(UIWebview),上層使用Html&Css&JS做業務開發,底層透明化、上層多多樣化,這種場景非常有利于前端介入,非常適合業務快速迭代,于是Hybrid火啦。

本來我覺得這種開發模式既然大家都知道了,那么Hybrid就沒有什么探討的價值了,但令我詫異的是依舊有很多人對Hybrid這種模式感到陌生,這種情況在二線城市很常見,所以我這里嘗試從另一個方面向各位介紹Hybrid,期望對各位正確的技術選型有所幫助。

Hybrid發家史

最初攜程的應用全部是Native的,H5站點只占其流量很小的一部分,當時Native有200人紅紅火火,而H5開僅有5人左右在打醬油,后面 無線團隊來了一個執行力十分強的服務器端出身的leader,他為了了解前端開發,居然親手使用jQuery Mobile開發了第一版程序,雖然很快方案便被推翻,但是H5團隊開始發力,在短時間內已經趕上了Native的業務進度:

突然有一天andriod同事跑過來告訴我們andriod中有一個方法最大樹限制,可能一些頁面需要我們內嵌H5的頁面,于是Native與H5 框架團隊牽頭做了第一個Hybrid項目,攜程第一次出現了一套代碼兼容三端的情況。這個開發效率杠杠的,團隊嘗到了甜頭,于是乎后續的頻道基本都開始了 Hybrid開發,到我離開時,整個機制已經十分成熟了,而前端也有幾百人了。

場景重現

狼廠有三大大流量APP,手機百度、百度地圖、糯米APP,最近接入糯米的時候,發現他們也在做Hybrid平臺化相關的推廣,將靜態資源打包至Native中,Native提供js調用原生應用的能力,從產品化和工程化來說做的很不錯,但是有兩個瑕疵:

① 資源全部打包至Naive中APP尺寸會增大,就算以增量機制也避免不了APP的膨脹,因為現在接入的頻道較少一個頻道500K沒有感覺,一旦平臺化后主APP尺寸會急劇增大

② 糯米前端框架團隊封裝了Native端的能力,但是沒有提供配套的前端框架,這個解決方案是不完整的。很多業務已經有H5站點了,為了接入還得單獨開發一 套程序;而就算是新業務接入,又會面臨嵌入資源必須是靜態資源的限制,做出來的項目沒有SEO,如果關注SEO的話還是需要再開發,從工程角度來說是有問 題的。

但從產品可接入度與產品化來說,糯米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

#p#

Native與前端分工

在做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交互設計

Hybrid的交互無非是Native調用前端頁面的JS方法,或者前端頁面通過JS調用Native提供的接口,兩者交互的橋梁皆Webview:

app自身可以自定義url schema,并且把自定義的url注冊在調度中心, 例如

  • ctrip://wireless 打開攜程App

  • weixin:// 打開微信

我們JS與Native通信一般就是創建這類URL被Native捕獲處理,后續也出現了其它前端調用Native的方式,但可以做底層封裝使其透明化,所以重點以及是如何進行前端與Native的交互設計。

#p#

JS to 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能力而報錯。

API式交互

手白、糯米底層如何做我們無從得知,但我們發現調用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約定的請求模型是: 

  1. requestHybrid({ 
  2.   //創建一個新的webview對話框窗口 
  3.   tagname: 'hybridapi'
  4.   //請求參數,會被Native使用 
  5.   param: {}, 
  6.   //Native處理成功后回調前端的方法 
  7.   callback: function (data) { 
  8.   } 
  9. }); 

這個方法執行會形成一個URL,比如:

hybridschema://hybridapi?callback=hybrid_1446276509894&param=%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 += '&param=' + 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場景較少,這里不予關注了。

#p#

常用交互API

良好的交互設計是成功的第一步,在真實業務開發中有一些API一定會用到。

跳轉

跳轉是Hybrid必用API之一,對前端來說有以下跳轉:

① 頁面內跳轉,與Hybrid無關

② H5跳轉Native界面

③ H5新開Webview跳轉H5頁面,一般為做頁面動畫切換

如果要使用動畫,按業務來說有向前與向后兩種,forward&back,所以約定如下,首先是H5跳Native某一個頁面 

  1. //H5跳Native頁面 
  2. //=>baidubus://forward?t=1446297487682&param=%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&param=%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是不允許重復的): 

  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. }); 
  124.  
  125. Native Header組件的封裝 

#p#

請求類

雖然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響應通過錯誤碼才能獲得,這是動態語言與靜態語言做入口頁面的一個很大的區別。

以網頁的方式訪問,賬號登錄與否由是否帶有秘鑰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獲取即可,這塊比較靠譜。

#p#

Hybrid的資源

目錄結構

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技術的朋友提供一些幫助,并且再次感謝明月同學的配合。

責任編輯:王雪燕 來源: 博客園
相關推薦

2018-02-23 14:44:41

負載均衡技術分類

2015-06-16 10:44:42

2009-07-08 09:32:25

Java設計模式

2011-09-06 09:27:15

項目設計

2014-05-21 15:13:40

AppCanHybrid

2009-02-17 18:17:42

2011-12-26 15:19:20

聚合

2011-11-08 11:21:00

2022-09-20 07:02:20

網絡爬蟲反爬蟲

2022-10-09 14:15:42

短鏈設計

2009-05-04 13:19:27

2016-09-29 12:59:54

大數據采集系統

2019-08-29 10:21:07

IBM存儲IBM存儲

2011-05-23 11:17:42

2009-05-18 09:11:00

IPTV融合寬帶

2009-07-15 15:47:12

JDBC DAO

2019-09-18 08:19:42

DDLMySQL數據庫

2021-11-26 07:31:43

Java反射程序

2023-06-01 13:15:23

2023-02-27 09:10:57

前端組件設計
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品亚洲精品久久 | 男女视频在线看 | 欧美一区二区在线 | 亚洲精品乱码久久久久久按摩观 | 日韩一区在线播放 | 午夜激情免费 | 欧美aⅴ在线观看 | 国产在线一区二区三区 | 在线久草| 你懂的在线视频播放 | 免费视频久久久久 | 久久久久网站 | 国产91精品久久久久久久网曝门 | 久久成人免费视频 | 精品国产99 | 精品国产乱码久久久久久闺蜜 | 一区二区在线 | 正在播放国产精品 | 欧美日韩黄 | 极品国产视频 | 精品欧美色视频网站在线观看 | 懂色av蜜桃av | 麻豆av在线免费观看 | 欧美在线观看一区二区 | 大象视频一区二区 | 国产目拍亚洲精品99久久精品 | 午夜国产精品视频 | 亚洲男人的天堂网站 | 中文字幕不卡在线88 | 久久狠狠 | 亚洲综合天堂网 | 日韩中文一区二区三区 | 欧美久操网 | 91精品国产91久久久久久最新 | 午夜精品久久久久久久久久久久久 | 一级a爱片久久毛片 | 欧美日韩亚洲在线 | 视频三区 | 久久精品亚洲欧美日韩精品中文字幕 | 久久久久久久av | 亚洲精品乱码久久久久久9色 |